Linux socket编程(2):socket函数介绍及C/S模型代码实现

news2024/12/23 5:45:39

上一节简单介绍了一下套接字、字节序和地址结构体的概念,算是对socket有一个入门的了解。这一节就实现一个客户端-服务端的代码,从这个例子中来学习socket函数的使用。

文章目录

  • 1 客户端/服务端模型
  • 2 套接字函数
    • 2.1 socket:创建套接字
    • 2.2 bind:绑定套接字
    • 2.3 listen:监听套接字
    • 2.4 accpet:接受客户端连接请求
    • 2.5 connect:与服务器套接字建立连接
    • 2.6 close:关闭套接字
    • 2.7 write和send:写数据
    • 2.8 read和recv:读数据
  • 3 客户端/服务端模型实现
    • 3.1 代码
    • 3.2 运行结果
    • 3.3 Bind failed:Address already in use

1 客户端/服务端模型

在客户端-服务器模型中,客户端和服务器之间的协作和通信是通过网络实现的,允许用户从远程位置访问和利用服务器上的资源和服务。这种模型的广泛应用使得它成为网络应用程序设计的基本框架之一。对于Linux的socket来说,建立通信的整体流程如下:

在这里插入图片描述

2 套接字函数

首先先来学习一下前面的流程图中出现的一些套接字函数

2.1 socket:创建套接字

我们可以使用socket函数创建一个套接字,套接字可以是不同类型,例如TCP套接字或UDP套接字。

int socket(int domain, int type, int protocol);
  1. domain(地址族,Address Family):常用的有AF_INET(IPv4地址族)、AF_INET6(IPv6地址族)和AF_UNIX(Unix域套接字,用于本地进程之间的通信)
  2. type(套接字类型,Socket Type),常用的有:
    • SOCK_STREAM:流式套接字,也称为面向连接的套接字。用于可靠的、面向连接的数据传输,例如TCP。
    • SOCK_DGRAM:数据报套接字,也称为无连接的套接字。用于无连接、不可靠的数据传输,例如UDP。
  3. protocol(协议):设置0时系统会自动选择适当的协议,例如,创建TCP套接字会使用IPPROTO_TCP协议。

2.2 bind:绑定套接字

bind函数用于将一个套接字绑定到一个特定的IP地址和端口号,以便套接字可以在该地址上接受传入的连接请求或数据包。这是在创建服务器程序时常用的操作,因为服务器通常需要监听特定的地址和端口以等待客户端的连接。

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • sockfd:要绑定的套接字描述符
  • addr:指向一个struct sockaddr类型的结构体,其中包含了要绑定的IP地址和端口信息。在上一篇文章中有介绍,这个结构体可以是通用的,也可以是特定的(如IPV4、IPV6特定的)
  • addrlenaddr结构体的长度,通常使用sizeof(struct sockaddr)来获取

2.3 listen:监听套接字

listen函数用于将服务器套接字(通常是TCP套接字)设置为监听状态,以便它可以接受客户端的连接请求。listen函数通常与bindaccept函数一起使用,以创建一个典型的服务器程序,用于等待客户端连接。

int listen(int sockfd, int backlog);
  • sockfd:要监听的套接字描述符,通常是一个已经通过bind绑定到特定地址和端口的套接字

  • backlog:指定在等待队列中可以排队等待的连接请求的最大数量,即服务器可以同时处理的连接请求的数量

SOMAXCONN 可以作为backlog的参数,它是一个系统常量,表示系统所支持的最大队列长度。SOMAXCONN 的具体值在内核编译时就已经指定,通常是一个相对较大的正整数。使用 SOMAXCONN 可以确保使用系统支持的最大队列长度,以应对高负载的服务器程序。

在这里插入图片描述

如果用户设置的backlog大于SOMAXCONN,则backlog就会设置为SOMAXCONN

2.4 accpet:接受客户端连接请求

accept 函数用于在服务器端套接字(通常是被动套接字)上接受客户端的连接请求,创建一个新的套接字,并在新的套接字上与客户端进行通信。


主动套接字和被动套接字?

主动套接字是客户端套接字,使用 connect 主动连接到服务器。被动套接字是服务器套接字,使用 accept 来接受客户端的连接请求。


accept 是套接字编程中非常重要的函数之一,用于建立服务器与客户端之间的通信通道。

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
  • sockfd:要接受连接请求的套接字描述符,通常是服务器端套接字,即被动套接字
  • addr:一个指向struct sockaddr类型的结构体指针,用于存储客户端的地址信息(IP地址和端口号)。传入 NULL表示不关心客户端地址信息。
  • addrlen:一个指向socklen_t类型的指针,用于传入addr结构体的长度。通常,可以传入NULL

2.5 connect:与服务器套接字建立连接

connect函数用于客户端套接字建立与服务器套接字的连接。它是网络编程中非常重要的函数,用于建立通信链路,以便客户端可以与服务器进行数据传输。

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • sockfd:要连接的客户端套接字的描述符
  • addr:指向一个struct sockaddr类型的结构体指针,其中包含了服务器的地址信息,包括 IP 地址和端口号。这个结构体的类型(AF_INETAF_INET6 等)应与客户端套接字的类型相匹配。
  • addrlenaddr 结构体的长度。

2.6 close:关闭套接字

当不再需要使用套接字时,使用close函数可以释放相关资源,包括文件描述符和系统内核资源。

int close(int sockfd);
  • sockfd:要关闭的套接字的文件描述符。

2.7 write和send:写数据

write函数:通用的文件写入函数,可以用于向文件描述符写入数据,也可以用于套接字。

ssize_t write(int fd, const void *buf, size_t count);
  • fd 是文件描述符,可以是套接字描述符,也可以是其他文件描述符。
  • buf 是包含要写入数据的缓冲区的指针。
  • count 是要写入的数据字节数。
  • 返回值是发送成功的字节数,如果发送失败则返回-1,并使用errno设置错误代码。

send函数:专门用于套接字通信的函数,它提供了更多的选项和控制来处理套接字数据发送。

ssize_t send(int sockfd, const void *buf, size_t len, int flags);
  • sockfd 是套接字描述符。

  • buf 是包含要发送数据的缓冲区的指针。

  • len 是要发送的数据字节数。

  • flags 是一组标志,可以用来控制发送操作的行为,例如设置非阻塞发送等。(后续用到再介绍)

  • 返回值是发送成功的字节数,如果发送失败则返回-1。

2.8 read和recv:读数据

read函数:通用的文件读取函数,可以用于从文件描述符读取数据,也可以用于套接字。

ssize_t read(int fd, void *buf, size_t count);
  • fd 是文件描述符,可以是套接字描述符,也可以是其他文件描述符。
  • buf 是接收数据的缓冲区的指针。
  • count是要读取的数据字节数。
  • 返回值是接收成功的字节数,如果接收失败则返回-1,并使用全局变量errno设置错误代码。

recv函数:专门用于套接字通信的函数,它提供了更多的选项和控制来处理套接字数据接收。

ssize_t recv(int sockfd, void *buf, size_t len, int flags);
  • sockfd是套接字描述符。
  • buf是接收数据的缓冲区的指针。
  • len是要接收的数据字节数。
  • flags是一组标志,可以用来控制接收操作的行为,例如设置非阻塞接收等。(后续用到再介绍)
  • 返回值是接收成功的字节数,如果接收失败则返回-1。

3 客户端/服务端模型实现

下面实现一个客户端/服务端模型的例子,具体功能为:客户端发一个字符串给服务端,服务端收到后打印。

3.1 代码

(1)服务端代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>

int main() {
    // 创建套接字
    int server_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (server_socket == -1) {
        perror("Socket creation failed");
        exit(1);
    }

    // 绑定套接字到IP地址和端口
    struct sockaddr_in server_address;
    server_address.sin_family = AF_INET;
    server_address.sin_port = htons(8080);
    server_address.sin_addr.s_addr = INADDR_ANY;
    //server_address.sin_addr.s_addr = inet_addr("127.0.0.1");
    if (bind(server_socket, (struct sockaddr *)&server_address, sizeof(server_address)) == -1) {
        perror("Bind failed");
        close(server_socket);
        exit(1);
    }

    // 设置服务器套接字为监听状态
    if (listen(server_socket, 5) == -1) {
        perror("Listen failed");
        close(server_socket);
        exit(1);
    }

    printf("Server listening on port 8080...\n");

    // 接受客户端连接
    struct sockaddr_in client_address;
    socklen_t client_len = sizeof(client_address);
    int client_socket = accept(server_socket, (struct sockaddr *)&client_address, &client_len);
    if (client_socket == -1) {
        perror("Accept failed");
        close(server_socket);
        exit(1);
    }
    char buffer[1024];
    memset(buffer, 0, sizeof(buffer));
    // 从客户端接收数据
    ssize_t bytes_received = recv(client_socket, buffer, sizeof(buffer), 0);
    if (bytes_received == -1) {
        perror("Receive failed");
    } else {
        printf("Client says: %s\n", buffer);
    }
    // 发送响应给客户端
    const char *response = "Hello from server!";
    send(client_socket, response, strlen(response), 0);
    // 关闭套接字
    close(client_socket);
    close(server_socket);

    return 0;
}

(2)客户端代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>

int main() {
    // 创建套接字
    int client_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (client_socket == -1) {
        perror("Socket creation failed");
        exit(1);
    }

    // 设置服务器地址和端口
    struct sockaddr_in server_address;
    server_address.sin_family = AF_INET;
    server_address.sin_port = htons(8080);
    //server_address.sin_addr.s_addr = INADDR_ANY;
	server_address.sin_addr.s_addr = inet_addr("127.0.0.1");
    // 连接到服务器
    if (connect(client_socket, (struct sockaddr *)&server_address, sizeof(server_address)) == -1) {
        perror("Connection failed");
        close(client_socket);
        exit(1);
    }

    // 发送数据给服务器
    const char *message = "Hello from client!";
    send(client_socket, message, strlen(message), 0);

    // 接收服务器的响应
    char buffer[1024];
    memset(buffer, 0, sizeof(buffer));
    ssize_t bytes_received = recv(client_socket, buffer, sizeof(buffer), 0);
    if (bytes_received == -1) {
        perror("Receive failed");
    } else {
        printf("Server says: %s\n", buffer);
    }

    // 关闭套接字
    close(client_socket);

    return 0;
}

服务端IP地址设置为INADDR_ANY

当服务器端将IP地址设置为 INADDR_ANY 时,假设服务器上有多个网卡,那么它将绑定到所有可用的网络接口和IP地址。这意味着服务器将监听所有网络接口上的传入连接请求,而不限制于特定的IP地址。这通常用于创建一个通用的服务器,它可以接受来自任何网络接口和任何IP地址的连接请求。

对于客户端来说,通常不会将IP地址设置为 INADDR_ANY,因为客户端通常是主动发起连接请求的一方,而不是绑定到特定的IP地址。客户端通常不需要关心绑定到哪个IP地址,而是根据服务器的IP地址和端口号来连接到服务器。

3.2 运行结果

编译后,运行server程序,服务端监听8080端口:

在这里插入图片描述

运行client程序后,client与server建立连接,客户端发送的Hello from client!,服务端收到后打印出来:

在这里插入图片描述

3.3 Bind failed:Address already in use

如下图所示,在服务端程序退出后马上再运行服务端,会提示Adress already in use。
在这里插入图片描述
当一个套接字绑定到一个特定的IP地址和端口后,如果你关闭该套接字,操作系统通常会保留一段时间,称为TIME_WAIT状态,以确保任何挂起的数据包都能够到达其目的地。这样可以避免在相同的地址和端口上立即重新绑定套接字时出现问题。

解决
在创建套接字时使用setsockopt函数设置SO_REUSEADDR选项,以允许在TIME_WAIT状态下重新绑定相同的地址和端口。

int reuse = 1;
setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));

在前面的服务端代码中增加上面的代码:
在这里插入图片描述

可以看到,在服务端退出后,再马上运行服务端的程序不会出现Address already in use的提示
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1205942.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

设计模式之适配器(Adapter)

Adapter Wapper 接口转换器 如果一个类不能直接访问另一个类的时候&#xff0c;中间加一个Adapter转换器就能访问了 常见例子: 电压转接头 java.io jdbc-odbc bridge(不是桥接模式) ASM Transformer java io里面的读文件操作: FileInputStream是字节流读文件&#xff0c;就像…

Python高级语法----Python C扩展与性能优化

文章目录 1. 编写Python C扩展模块示例代码编译和运行运行结果2. 利用Cython优化性能示例代码编译和运行运行结果3. Python性能分析工具示例代码分析结果1. 编写Python C扩展模块 Python C扩展模块允许你将C语言代码集成到Python程序中,以提高性能。这对于计算密集型任务特别…

ppt中的字体,如何批量替换?

想要将PPT中的文字全部更换&#xff0c;有什么方便的方法吗&#xff1f;今天分享两个方法&#xff0c;一键修改ppt文件字体。 方法一&#xff1a; 找到功能栏中的编辑选项卡&#xff0c;点击替换 – 替换字体&#xff0c;在里面选择我们想要替换的字体就可以了。 方法二&…

逐帧动画demo

用这一张图实现一个在跑的猎豹的动画 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><meta http-equiv"X…

Centos7安装mysql8.0.35(亲测)

今天在centos7上安装了mysql8&#xff0c;特此记录以作备忘。 说明&#xff1a; - 我安装的mysql版本&#xff1a;8.0.35 - centos版本&#xff1a;7 - 我的虚拟机没安装过mysql,如果之前安装过mysql记得卸载干净 - 卸载步骤&#xff1a; - rpm -qa|grep mysql (搜索mysql)比如…

Java Stream 的常用API

Java Stream 的常用API 遍历&#xff08;forEach&#xff09; package com.liudashuai;import java.util.ArrayList; import java.util.List;public class Test {public static void main(String[] args) {List<Person> userList new ArrayList<>();userList.ad…

银行支付凭证截图生成器在线,工商邮政农业招商建设,画板+透明标签+图片框

用易语言设计了一个非常牛X的截图生成器&#xff0c;娱乐使用哈&#xff0c;软件我在这里也不会分享&#xff0c;模版网上找的&#xff0c;百度图库搜到的&#xff0c;上面的LOGO用的是一个在线生成器&#xff0c;然后标签用的黑月透明标签&#xff0c;加一个通用对话框读取图片…

《QT从基础到进阶·二十一》QGraphicsView、QGraphicsScene和QGraphicsItem坐标关系和应用

前言&#xff1a; 我们需要先由一个 QGraphicsView&#xff0c;这个是UI显示的地方&#xff0c;也就是装满可见原色的Scene&#xff0c;然后需要一个QGraphicsScene 用来管理所有可见的界面元素&#xff0c;要实现UI功能&#xff0c;我们需要用各种从QGraphicsItem拼装成UI控件…

Python编程爬虫代码

这是一个基本的爬虫程序的示例&#xff0c;按照你的需求进行了修改&#xff1a; typescript import * as request from request; import * as cheerio from cheerio; const proxyHost ; const proxyPort ; // 创建一个request实例&#xff0c;使用 const requestWithProxy…

大数据系统建模方法论简谈

1.根据业务需求构建总线矩阵--明确需求 构建总线矩阵的目的&#xff1a;总线矩阵也是BI核心之一&#xff0c;基本上只要详细了解企业业务战略线就能得出总线矩阵&#xff0c;它对应着企业每一个业务单元&#xff0c;提取业务单元中的一致性维度和事实量值组 组合成企业总线矩阵…

从关键新闻和最新技术看AI行业发展(2023.10.23-11.5第九期) |【WeThinkIn老实人报】

Rocky Ding 公众号&#xff1a;WeThinkIn 写在前面 【WeThinkIn老实人报】旨在整理&挖掘AI行业的关键新闻和最新技术&#xff0c;同时Rocky会对这些关键信息进行解读&#xff0c;力求让读者们能从容跟随AI科技潮流。也欢迎大家提出宝贵的优化建议&#xff0c;一起交流学习&…

2023亚太杯数学建模B题思路解析

文章目录 0 赛题思路1 竞赛信息2 竞赛时间3 建模常见问题类型3.1 分类问题3.2 优化问题3.3 预测问题3.4 评价问题 4 建模资料5 最后 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 竞赛信息 2023年第十三…

程序运行前后内存分区存储

程序运行前是源码 在程序运行后&#xff0c;生成了exe可执行程序 分为代码区和全局区 代码区&#xff1a; 存放CPU执行的机器指令代码区是共享的&#xff0c;共享的目的是对于频繁被执行的程序&#xff0c;只需要在内存中有一份代码就可以了代码区是只读的&#xff0c;其只读…

BEVFusion简介、环境配置与安装以及遇到的各种报错处理

BEVFusion简介、环境配置与安装以及遇到的各种报错处理 BEVFusion简介BEVFusion环境配置与安装报错解决 BEVFusion简介 针对点云投射到图像的多模态融合和图像投射到点云的多模态融合&#xff0c;前者会损失空间几何信息&#xff0c;后者会损失图像语义信息&#xff0c;这两种…

janus 安装部署

本文使用docker进行安装&#xff0c;还没有安装docker和docker-compose的&#xff0c;请自行安装&#xff0c;这里就不介绍了 环境 janus-gateway镜像版本:anyan/janus-gateway:0.10.7 linux版本: Ubuntu 18.04.6 LTS coturn/coturn 镜像版本: coturn/coturn:latest 镜像ID 8…

java算法学习索引之动态规划

一 斐波那契数列问题的递归和动态规划 【题目】给定整数N&#xff0c;返回斐波那契数列的第N项。 补充问题 1&#xff1a;给定整数 N&#xff0c;代表台阶数&#xff0c;一次可以跨 2个或者 1个台阶&#xff0c;返回有多少种走法。 【举例】N3&#xff0c;可以三次都跨1个台…

【ARM Trace32(劳特巴赫) 使用介绍 4 - Trace32 Discovery 详细介绍】

请阅读【ARM Coresight SoC-400/SoC-600 专栏导读】 文章目录 1.1 SYS.Detect1.2 AHBAPn/AXIAPnAPBAPn.Base1.1 SYS.Detect 在 TRACE32 中, SYS.Detect 是一个用来检测目标系统配置的命令。 当你执行 SYS.Detect DAP 命令时,TRACE32 将自动检测和识别目标系统上的 ARM De…

VScode不打开浏览器实时预览html

下载Microsoft官方的Live Preview就行了 点击预览按钮即可预览

前端---CSS的样式汇总

文章目录 CSS的样式元素的属性设置字体设置文字的粗细设置文字的颜色文本对齐文本修饰文本缩进行高设置背景背景的颜色背景的图片图片的属性平铺位置大小 圆角矩形 元素的显示模式行内元素和块级元素的转化弹性布局水平方向排列方式&#xff1a;justify-content垂直方向排序方式…