Listen第二个参数的意义

本文深入解析Linux socket函数listen的第二个参数,解释其作为未完成队列大小的作用,以及如何影响并发请求处理。同时,讨论在不同Linux版本中参数行为的变化,并介绍syncookies机制如何优化连接管理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

      今天主要回顾下listen的第二个参数的意义。 话说现在现在都是用框架写业务代码。真的很少在去关注最基本的socket函数的意义了。该忘得都忘得差不多了。~~~  要慢慢捡起来。  主要是在看redis网络这块的时候它的第二个参数设置的是500多。  而且突然想到当初几年前腾讯电话面试问过我第二个参数是什么意思。当时我一股脑就说成connet的连接数量了。哈哈  2b.

照例 先上图,再解析

首先这个图是展示的TCP 三次握手。这里就不吸收三次握手了。我们主要是关注 两个队列

1:未完成队列:每个这样的SYN分节对应其中一项,已有某个客户端发出并到达服务器,而服务器正在等待完成相应的TCP三路握手过程。这些套接口处于SYN_RCVD

2:已完成队列:每个已经完成TCP三路握手过程的客户对应其中一项。 这些套接口处于ESTABLISHED

这里说下对着两个队列的理解

假设未完成队列设置为100,  有并发1000个请求过来。假如系统处理过慢。那么系统会做以下几件事

1) 因为未完成队列只有100个。先放100个请求过来处理三次握手。其他的请求直接拒绝。TCP会忽略该分节,也就是不送RST,这样客户端将重发SYN,期望在未完成队列中找到位置

2)每处理完一个三次握手动作。就放一个SYN请求过来。并站住未完成队列一个位置。除非三次握手完成(成功或者失败)、或者超时就把该请求连接移除未完成队列。如果系统性能好。这个动作会很快。

3)完成TCP三次握手的请求放到已完成队列里。

这个时候再去看 listen的第二个参数。

其实这里man listen的时候已经解释的很清楚了

   The backlog argument defines the maximum length to which the queue of pending connections for sockfd may grow.  If a connection request arrives when  the  queue  is full,  the client may receive an error with an indication of ECONNREFUSED or, if the underlying protocol supports retransmission, the request may be ignored so that a later reattempt at connection succeeds.

    第二个参数就是未完成队列的大小

 

但是继续man listen函数 看到如下解释

The  behavior  of  the  backlog  argument on TCP sockets changed with Linux 2.2.  Now it specifies the queue length for completely established sockets waiting to be accepted,  instead  of  the  number  of  incomplete  connection  requests.   The  maximum  length  of  the  queue  for  incomplete  sockets   can   be   set   using  /proc/sys/net/ipv4/tcp_max_syn_backlog.   When  syncookies are enabled there is no logical maximum length and this setting is ignored.  See tcp(7) for more information.

        在linux 2.2以后  listen的第二个参数。指的是在完成TCP三次握手后的队列。即在系统accept之前的队列。已经完成的队列。如果系统没有调用accpet把这个队列的数据拿出来。一旦这个队列满了。未连接队列的请求过不来。导致未连接队列里的请求会超时或者拒绝。如果系统调用了accpet队列接受请求数据。那么就会把接受到请求移除已完成队列。 这时候已完成队列又可以使用了。

最后 说了如果开启了syncookies  忽略listen的第二个参数。 

syncookie 主要是为了防止syn flood攻击。这里暂时先不说。以后再补充

总结

1,这里可以感受到  内核用了两个队列。 可以针对并发请求的时候。及时拒绝掉一部分处理不过来的请求。防止盲等待。类似有点雪崩处理的感觉。

2,listen的第二个参数。跟系统的链接数量没有任何关系。相当于设置一个瞬间能够处理的阈值。

3,一般情况下都会去开启 syncookie。所有其实现在已经可以不太关系listen的第二个值了

<think>我们之前已经提供了完整的Socket通信代码(TCP)并逐行分析了代码。现在,用户要求详细解释每一行的变量以及对应的参数意义。由于代码较长,我们将对关键部分进行详细解释,包括函数参数和变量的具体含义。 由于代码量较大,我们将选择一些关键函数和代码段进行详细解释。如果全部逐行解释会非常冗长,因此我们重点解释核心部分。 ### 服务器端关键代码详解 #### 1. 创建套接字 ```c int server_fd = socket(AF_INET, SOCK_STREAM, 0); ``` - **函数**: `socket(int domain, int type, int protocol)` - `domain`: 地址族,`AF_INET` 表示IPv4协议。 - `type`: 套接字类型,`SOCK_STREAM` 表示面向连接的流套接字(TCP)。 - `protocol`: 协议,0表示默认协议(对于流套接字是TCP)。 - **返回值**: 成功返回非负文件描述符(`server_fd`),失败返回-1。 #### 2. 配置服务器地址结构 ```c struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = htons(PORT); ``` - `struct sockaddr_in`: IPv4地址结构,包含: - `sin_family`: 地址族(同socket的domain)。 - `sin_addr.s_addr`: IP地址,`INADDR_ANY`表示绑定到所有本地接口。 - `sin_port`: 端口号,使用`htons`将主机字节序(小端)转换为网络字节序(大端)。 #### 3. 绑定套接字 ```c bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)); ``` - **函数**: `bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)` - `sockfd`: 套接字描述符(`server_fd`)。 - `addr`: 指向地址结构的指针,这里将`sockaddr_in`强制转换为`sockaddr`。 - `addrlen`: 地址结构的大小(`sizeof(server_addr)`)。 #### 4. 监听连接 ```c listen(server_fd, 1); ``` - **函数**: `listen(int sockfd, int backlog)` - `sockfd`: 套接字描述符。 - `backlog`: 等待连接队列的最大长度,这里设置为1(只能有一个客户端连接)。 #### 5. 接受连接 ```c int client_socket = accept(server_fd, (struct sockaddr *)&client_addr, &addr_len); ``` - **函数**: `accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)` - `sockfd`: 监听套接字描述符(`server_fd`)。 - `addr`: 用于存放客户端地址信息的结构体指针(`client_addr`)。 - `addrlen`: 指向地址结构长度的指针,调用前需初始化为`client_addr`的大小,返回时是实际长度。 - **返回值**: 成功返回一个新的套接字描述符(`client_socket`),用于与客户端通信。 #### 6. 创建接收线程 ```c pthread_create(&recv_thread, NULL, receive_handler, NULL); ``` - **函数**: `pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg)` - `thread`: 指向线程标识符的指针(`recv_thread`)。 - `attr`: 线程属性,NULL表示默认属性。 - `start_routine`: 线程函数入口(`receive_handler`)。 - `arg`: 传递给线程函数的参数,这里为NULL。 #### 7. 接收线程函数 ```c int bytes_received = recv(client_socket, buffer, BUFFER_SIZE, 0); ``` - **函数**: `recv(int sockfd, void *buf, size_t len, int flags)` - `sockfd`: 套接字描述符(`client_socket`)。 - `buf`: 接收缓冲区指针(`buffer`)。 - `len`: 缓冲区大小(`BUFFER_SIZE`)。 - `flags`: 通常为0,表示阻塞接收。 - **返回值**: 接收到的字节数(>0),0表示连接关闭,<0表示错误。 #### 8. 发送消息 ```c send(client_socket, message, strlen(message), 0); ``` - **函数**: `send(int sockfd, const void *buf, size_t len, int flags)` - `sockfd`: 套接字描述符(`client_socket`)。 - `buf`: 发送缓冲区指针(`message`)。 - `len`: 发送数据的长度(`strlen(message)`)。 - `flags`: 通常为0。 ### 客户端关键代码详解(与服务器类似部分不再重复) #### 1. 连接服务器 ```c connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)); ``` - **函数**: `connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)` - `sockfd`: 套接字描述符(`sock`)。 - `addr`: 服务器地址结构指针(`serv_addr`)。 - `addrlen`: 服务器地址结构的大小。 ### 变量详解 #### 服务器端 - `int server_fd`: 服务器监听套接字描述符。 - `struct sockaddr_in server_addr, client_addr`: 服务器和客户端的地址结构。 - `socklen_t addr_len`: 客户端地址结构长度。 - `pthread_t recv_thread`: 接收线程的标识符。 - `int client_socket`: 与客户端通信的套接字描述符。 - `char buffer[BUFFER_SIZE]`: 接收消息的缓冲区。 - `char message[BUFFER_SIZE]`: 发送消息的缓冲区。 #### 客户端 - `int sock`: 连接服务器的套接字描述符。 - `struct sockaddr_in serv_addr`: 服务器地址结构。 - `pthread_t recv_thread`: 接收线程标识符。 - `char buffer[BUFFER_SIZE]`: 接收消息的缓冲区。 - `char message[BUFFER_SIZE]`: 发送消息的缓冲区。 ### 完整代码(已注释每一行) 由于用户要求详细解释每一行,我们将对之前的完整代码添加详细注释。但请注意,由于代码较长,注释会非常详细,可能会显得冗长。我们选择服务器端代码为例进行注释,客户端类似。 #### 服务器端代码(带详细注释) ```c #include <stdio.h> // 标准输入输出函数,如printf, fgets #include <stdlib.h> // 标准库函数,如exit, malloc #include <string.h> // 字符串处理函数,如memset, strcspn #include <unistd.h> // POSIX系统调用,如close, read, write #include <arpa/inet.h> // 网络地址转换函数,如inet_ntoa, htons #include <pthread.h> // 线程支持库 #define PORT 8080 // 定义服务器监听的端口号 #define BUFFER_SIZE 1024 // 定义缓冲区大小 int client_socket = -1; // 全局变量,用于存储与客户端通信的套接字描述符,初始为-1表示无效 // 接收消息的线程函数 void *receive_handler(void *arg) { char buffer[BUFFER_SIZE]; // 定义接收缓冲区 while (1) { // 循环接收消息 memset(buffer, 0, BUFFER_SIZE); // 将缓冲区清零,避免之前的数据残留 // 从client_socket接收数据,存放到buffer中,最大长度为BUFFER_SIZE int bytes_received = recv(client_socket, buffer, BUFFER_SIZE, 0); if (bytes_received <= 0) { // 如果接收字节数<=0,表示连接出错或关闭 perror("Connection closed or error"); // 打印错误信息 close(client_socket); // 关闭客户端套接字 exit(EXIT_FAILURE); // 退出程序(整个进程) } // 打印接收到的客户端消息,前面加换行是为了与输入提示分开 printf("\n[Client]: %s\n", buffer); // 打印服务器输入提示,注意这里没有换行,等待用户输入 printf("[Server] Enter message: "); // 刷新标准输出,确保提示信息立即显示 fflush(stdout); } return NULL; // 线程结束(实际上由于无限循环,不会执行到这里) } int main() { int server_fd; // 服务器监听套接字描述符 struct sockaddr_in server_addr, client_addr; // 定义服务器和客户端的地址结构 socklen_t addr_len = sizeof(client_addr); // 客户端地址结构长度 pthread_t recv_thread; // 接收线程的标识符 // 创建服务器套接字,使用IPv4协议,流式套接字(TCP) if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("Socket creation failed"); // 如果创建失败,打印错误 exit(EXIT_FAILURE); // 退出程序 } // 初始化服务器地址结构为0 memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; // 使用IPv4 server_addr.sin_addr.s_addr = INADDR_ANY; // 绑定到所有本地接口 server_addr.sin_port = htons(PORT); // 设置端口号,并转换为网络字节序 // 将套接字绑定到服务器地址 if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { perror("Bind failed"); // 绑定失败 close(server_fd); // 关闭套接字 exit(EXIT_FAILURE); // 退出 } // 开始监听,最大等待队列为1(即同时只能有一个客户端连接) if (listen(server_fd, 1) < 0) { perror("Listen failed"); // 监听失败 close(server_fd); // 关闭套接字 exit(EXIT_FAILURE); // 退出 } printf("Server listening on port %d...\n", PORT); // 打印监听信息 // 接受客户端连接,client_addr将存储客户端地址,addr_len是地址结构长度 if ((client_socket = accept(server_fd, (struct sockaddr *)&client_addr, &addr_len)) < 0) { perror("Accept failed"); // 接受失败 close(server_fd); // 关闭监听套接字 exit(EXIT_FAILURE); // 退出 } // 打印客户端IP地址(使用inet_ntoa将二进制IP转换为点分十进制字符串) printf("Client connected! IP: %s\n", inet_ntoa(client_addr.sin_addr)); // 创建接收线程,线程函数为receive_handler,参数为NULL if (pthread_create(&recv_thread, NULL, receive_handler, NULL) != 0) { perror("Thread creation failed"); // 线程创建失败 close(client_socket); // 关闭客户端套接字 close(server_fd); // 关闭监听套接字 exit(EXIT_FAILURE); // 退出 } char message[BUFFER_SIZE]; // 发送消息缓冲区 while (1) { // 循环发送消息 printf("[Server] Enter message: "); // 提示输入 fgets(message, BUFFER_SIZE, stdin); // 从标准输入读取一行(包括换行符) // 移除换行符:查找第一个换行符位置,并将其替换为字符串结束符 message[strcspn(message, "\n")] = '\0'; // 发送消息到客户端套接字 if (send(client_socket, message, strlen(message), 0) < 0) { perror("Send failed"); // 发送失败 break; // 退出循环 } } // 关闭套接字(实际由于无限循环,这里不会执行到) close(client_socket); close(server_fd); return 0; } ``` ### 客户端代码(带详细注释) 由于篇幅原因,客户端的详细注释类似,主要区别在于连接服务器而不是监听。这里只给出关键部分注释: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <pthread.h> #define SERVER_IP "127.0.0.1" // 服务器IP地址(本地回环地址) #define PORT 8080 // 服务器端口 #define BUFFER_SIZE 1024 // 缓冲区大小 int sock = -1; // 连接服务器的套接字描述符 void *receive_handler(void *arg) { char buffer[BUFFER_SIZE]; // 接收缓冲区 while (1) { memset(buffer, 0, BUFFER_SIZE); // 清空缓冲区 int bytes_received = recv(sock, buffer, BUFFER_SIZE, 0); // 接收数据 if (bytes_received <= 0) { perror("Connection closed or error"); close(sock); exit(EXIT_FAILURE); } printf("\n[Server]: %s\n", buffer); printf("[Client] Enter message: "); fflush(stdout); } return NULL; } int main() { struct sockaddr_in serv_addr; // 服务器地址结构 pthread_t recv_thread; // 接收线程标识符 // 创建套接字 if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("Socket creation failed"); exit(EXIT_FAILURE); } memset(&serv_addr, 0, sizeof(serv_addr)); // 初始化服务器地址结构 serv_addr.sin_family = AF_INET; // IPv4 serv_addr.sin_port = htons(PORT); // 端口转换 // 将字符串形式的IP地址转换为二进制形式,存入serv_addr.sin_addr if (inet_pton(AF_INET, SERVER_IP, &serv_addr.sin_addr) <= 0) { perror("Invalid address/Address not supported"); close(sock); exit(EXIT_FAILURE); } // 连接服务器 if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) { perror("Connection failed"); close(sock); exit(EXIT_FAILURE); } printf("Connected to server at %s:%d\n", SERVER_IP, PORT); // 创建接收线程 if (pthread_create(&recv_thread, NULL, receive_handler, NULL) != 0) { perror("Thread creation failed"); close(sock); exit(EXIT_FAILURE); } char message[BUFFER_SIZE]; while (1) { printf("[Client] Enter message: "); fgets(message, BUFFER_SIZE, stdin); message[strcspn(message, "\n")] = '\0'; // 去掉换行符 if (send(sock, message, strlen(message), 0) < 0) { perror("Send failed"); break; } } close(sock); return 0; } ``` ### 注意事项 1. **线程安全**: 在多线程环境下,多个线程同时访问标准输出(`printf`)可能导致输出混乱。但在本示例中,由于接收线程和主线程的输出内容不同(一个输出接收到的消息,一个输出输入提示),且我们使用了`fflush`,所以基本可用。但在高并发情况下,需要更严格的同步机制。 2. **错误处理**: 代码中多处使用了`perror`和`exit`,实际应用中可能需要更细致的错误恢复或重试机制。 3. **资源释放**: 在错误退出时,我们关闭了已打开的套接字,但正常退出时(按Ctrl+C中断)资源也会被操作系统回收。但在更复杂的程序中,应设计优雅退出的方式。 4. **多客户端支持**: 当前服务器只能同时处理一个客户端连接。要支持多客户端,需要使用多进程、多线程(每个客户端一个线程)或I/O多路复用(select/poll/epoll)。 ### §§相关问题§§: 1. 在Socket编程中,`INADDR_ANY`的作用是什么?为什么服务器通常使用这个地址? 2. `recv`函数返回0时表示什么情况?如何处理这种情况? 3. 在多线程程序中,为什么需要调用`fflush(stdout)`?如果不调用会有什么问题? 4. 如何修改上述代码,使得服务器能够同时处理多个客户端连接?</think>### 详细解释:Socket通信代码中的变量和参数意义 以下是服务器端和客户端代码的逐行详细解释,包括所有变量和参数的含义: #### 服务器端代码详解 ```c #include <stdio.h> // 标准输入输出函数(printf, fgets, fflush) #include <stdlib.h> // 标准库函数(exit, malloc, free) #include <string.h> // 字符串处理函数(memset, strlen, strcspn) #include <unistd.h> // POSIX系统调用(close, read, write) #include <arpa/inet.h> // 网络地址处理函数(inet_ntoa, htons) #include <pthread.h> // 线程支持库(pthread_create) #define PORT 8080 // 服务器监听的端口号 #define BUFFER_SIZE 1024 // 消息缓冲区大小(字节) int client_socket = -1; // 客户端套接字描述符,初始化为无效值(-1) // 接收消息的线程函数 void *receive_handler(void *arg) { // arg: 线程参数(此处未使用) char buffer[BUFFER_SIZE]; // 消息接收缓冲区 while (1) { // 无限循环,持续接收消息 // 清空缓冲区(防止旧数据残留) memset(buffer, 0, BUFFER_SIZE); /* recv() 函数参数详解: * client_socket: 客户端套接字描述符 * buffer: 接收数据缓冲区指针 * BUFFER_SIZE: 缓冲区大小 * 0: 标志位(0表示默认阻塞模式) * 返回值: >0表示接收字节数,=0表示连接关闭,<0表示错误 */ int bytes_received = recv(client_socket, buffer, BUFFER_SIZE, 0); // 检查接收状态 if (bytes_received <= 0) { perror("Connection closed or error"); // 打印错误信息 close(client_socket); // 关闭客户端套接字 exit(EXIT_FAILURE); // 退出程序(错误状态) } // 打印接收到的消息 printf("\n[Client]: %s\n", buffer); // 显示客户端消息 printf("[Server] Enter message: "); // 提示用户输入 fflush(stdout); // 强制刷新输出缓冲区(确保提示立即显示) } return NULL; // 线程退出(实际不会执行到这里) } int main() { int server_fd; // 服务器套接字描述符 // 服务器和客户端地址结构(sockaddr_in用于IPv4) struct sockaddr_in server_addr, client_addr; socklen_t addr_len = sizeof(client_addr); // 客户端地址结构长度 pthread_t recv_thread; // 接收线程标识符 /* 1. 创建服务器套接字 * socket() 参数: * AF_INET: IPv4地址族 * SOCK_STREAM: 流式套接字(TCP) * 0: 协议(0表示默认协议) * 返回值: 成功返回文件描述符,失败返回-1 */ if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("Socket creation failed"); // 打印错误信息 exit(EXIT_FAILURE); // 退出程序 } /* 2. 配置服务器地址 */ memset(&server_addr, 0, sizeof(server_addr)); // 清空地址结构 server_addr.sin_family = AF_INET; // IPv4地址族 server_addr.sin_addr.s_addr = INADDR_ANY; // 绑定到所有网络接口 server_addr.sin_port = htons(PORT); // 端口号(主机序转网络序) /* 3. 绑定套接字到地址 * bind() 参数: * server_fd: 服务器套接字描述符 * (struct sockaddr *)&server_addr: 地址结构指针(类型转换) * sizeof(server_addr): 地址结构大小 */ if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { perror("Bind failed"); close(server_fd); // 关闭套接字 exit(EXIT_FAILURE); } /* 4. 监听连接 * listen() 参数: * server_fd: 服务器套接字描述符 * 1: 等待连接队列的最大长度 */ if (listen(server_fd, 1) < 0) { perror("Listen failed"); close(server_fd); exit(EXIT_FAILURE); } printf("Server listening on port %d...\n", PORT); /* 5. 接受客户端连接 * accept() 参数: * server_fd: 服务器套接字描述符 * (struct sockaddr *)&client_addr: 客户端地址结构指针 * &addr_len: 地址结构长度指针(输入/输出参数) * 返回值: 新的套接字描述符(用于与客户端通信) */ if ((client_socket = accept(server_fd, (struct sockaddr *)&client_addr, &addr_len)) < 0) { perror("Accept failed"); close(server_fd); exit(EXIT_FAILURE); } // 打印客户端IP地址(inet_ntoa将二进制IP转换为点分十进制) printf("Client connected! IP: %s\n", inet_ntoa(client_addr.sin_addr)); /* 创建接收线程 * pthread_create() 参数: * &recv_thread: 线程标识符指针 * NULL: 线程属性(默认) * receive_handler: 线程函数 * NULL: 传递给线程函数的参数 */ if (pthread_create(&recv_thread, NULL, receive_handler, NULL) != 0) { perror("Thread creation failed"); close(client_socket); close(server_fd); exit(EXIT_FAILURE); } char message[BUFFER_SIZE]; // 发送消息缓冲区 /* 6. 主线程处理消息发送 */ while (1) { printf("[Server] Enter message: "); // 输入提示 // 从标准输入读取消息(包含换行符) fgets(message, BUFFER_SIZE, stdin); /* 移除换行符 * strcspn(message, "\n"): 查找第一个换行符的位置 * 将该位置字符设为'\0'(字符串结束符) */ message[strcspn(message, "\n")] = '\0'; /* 发送消息到客户端 * send() 参数: * client_socket: 客户端套接字描述符 * message: 发送数据缓冲区指针 * strlen(message): 数据长度 * 0: 标志位 */ if (send(client_socket, message, strlen(message), 0) < 0) { perror("Send failed"); break; // 退出循环 } } // 清理资源(实际不会执行到这里) close(client_socket); close(server_fd); return 0; } ``` #### 客户端代码详解 ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <pthread.h> #define SERVER_IP "127.0.0.1" // 服务器IP地址(本地回环) #define PORT 8080 // 服务器端口 #define BUFFER_SIZE 1024 // 消息缓冲区大小 int sock = -1; // 连接服务器的套接字描述符 // 接收消息的线程函数 void *receive_handler(void *arg) { char buffer[BUFFER_SIZE]; // 接收缓冲区 while (1) { memset(buffer, 0, BUFFER_SIZE); // 清空缓冲区 // 接收服务器消息 int bytes_received = recv(sock, buffer, BUFFER_SIZE, 0); if (bytes_received <= 0) { perror("Connection closed or error"); close(sock); exit(EXIT_FAILURE); } printf("\n[Server]: %s\n", buffer); // 显示服务器消息 printf("[Client] Enter message: "); // 输入提示 fflush(stdout); // 刷新输出缓冲区 } return NULL; } int main() { struct sockaddr_in serv_addr; // 服务器地址结构 pthread_t recv_thread; // 接收线程标识符 // 1. 创建套接字(与服务器端相同) if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("Socket creation failed"); exit(EXIT_FAILURE); } // 2. 配置服务器地址 memset(&serv_addr, 0, sizeof(serv_addr)); // 清空地址结构 serv_addr.sin_family = AF_INET; // IPv4地址族 serv_addr.sin_port = htons(PORT); // 端口号(主机序转网络序) /* 将IP地址从文本转换为二进制格式 * inet_pton() 参数: * AF_INET: IPv4地址族 * SERVER_IP: 点分十进制IP字符串 * &serv_addr.sin_addr: 存储转换结果的地址 * 返回值: 1-成功,0-无效地址,-1-错误 */ if (inet_pton(AF_INET, SERVER_IP, &serv_addr.sin_addr) <= 0) { perror("Invalid address/Address not supported"); close(sock); exit(EXIT_FAILURE); } /* 3. 连接服务器 * connect() 参数: * sock: 客户端套接字描述符 * (struct sockaddr *)&serv_addr: 服务器地址结构指针 * sizeof(serv_addr): 地址结构大小 */ if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) { perror("Connection failed"); close(sock); exit(EXIT_FAILURE); } printf("Connected to server at %s:%d\n", SERVER_IP, PORT); // 创建接收线程(与服务器端相同) if (pthread_create(&recv_thread, NULL, receive_handler, NULL) != 0) { perror("Thread creation failed"); close(sock); exit(EXIT_FAILURE); } char message[BUFFER_SIZE]; // 发送消息缓冲区 // 4. 主线程处理消息发送 while (1) { printf("[Client] Enter message: "); fgets(message, BUFFER_SIZE, stdin); message[strcspn(message, "\n")] = '\0'; // 移除换行符 // 发送消息到服务器 if (send(sock, message, strlen(message), 0) < 0) { perror("Send failed"); break; } } close(sock); // 关闭套接字 return 0; } ``` ### 关键参数详解表 #### 套接字函数参数 | 函数名 | 参数 | 类型 | 说明 | |--------|------|------|------| | `socket()` | domain | int | 地址族:`AF_INET`(IPv4), `AF_INET6`(IPv6) | | | type | int | 套接字类型:`SOCK_STREAM`(TCP), `SOCK_DGRAM`(UDP) | | | protocol | int | 协议:0表示默认协议 | | `bind()` | sockfd | int | 套接字描述符 | | | addr | const struct sockaddr * | 地址结构指针 | | | addrlen | socklen_t | 地址结构长度 | | `listen()` | sockfd | int | 套接字描述符 | | | backlog | int | 等待连接队列的最大长度 | | `accept()` | sockfd | int | 监听套接字描述符 | | | addr | struct sockaddr * | 客户端地址信息(输出参数) | | | addrlen | socklen_t * | 地址长度指针(输入/输出) | | `connect()` | sockfd | int | 客户端套接字描述符 | | | servaddr | const struct sockaddr * | 服务器地址结构 | | | addrlen | socklen_t | 地址结构长度 | | `send()` | sockfd | int | 已连接套接字描述符 | | | buf | const void * | 发送数据缓冲区 | | | len | size_t | 数据长度 | | | flags | int | 发送标志(通常为0) | | `recv()` | sockfd | int | 套接字描述符 | | | buf | void * | 接收数据缓冲区 | | | len | size_t | 缓冲区大小 | | | flags | int | 接收标志(通常为0) | #### 地址结构关键字段 | 字段 | 类型 | 说明 | |------|------|------| | `sin_family` | sa_family_t | 地址族(`AF_INET`) | | `sin_port` | in_port_t | 端口号(网络字节序) | | `sin_addr.s_addr` | in_addr_t | IP地址(网络字节序) | | `sin_zero` | char[8] | 填充字段(未使用) | #### 字节序转换函数 | 函数 | 原型 | 说明 | |------|------|------| | `htons()` | `uint16_t htons(uint16_t hostshort)` | 主机序→网络序(16位) | | `htonl()` | `uint32_t htonl(uint32_t hostlong)` | 主机序→网络序(32位) | | `ntohs()` | `uint16_t ntohs(uint16_t netshort)` | 网络序→主机序(16位) | | `ntohl()` | `uint32_t ntohl(uint32_t netlong)` | 网络序→主机序(32位) | #### IP地址转换函数 | 函数 | 原型 | 说明 | |------|------|------| | `inet_pton()` | `int inet_pton(int af, const char *src, void *dst)` | 点分十进制→二进制 | | `inet_ntop()` | `const char *inet_ntop(int af, const void *src, char *dst, socklen_t size)` | 二进制→点分十进制 | ### 关键设计决策解释 1. **多线程架构**: - 主线程处理用户输入和消息发送 - 独立线程处理消息接收(避免`recv()`阻塞用户输入) - 使用`pthread`库实现跨平台线程支持 2. **缓冲区管理**: - 固定大小缓冲区(1024字节)平衡性能和内存使用 - `memset()`清空缓冲区防止旧数据残留 - `strcspn()`移除换行符保证消息格式正确 3. **网络字节序处理**: - `htons(PORT)`转换端口号(主机序→网络序) - `inet_pton()`转换IP地址(文本→二进制) - `inet_ntoa()`转换IP地址(二进制→文本) 4. **错误处理机制**: - 所有关键系统调用检查返回值 - `perror()`输出可读的错误信息 - 错误时关闭套接字并退出程序 5. **资源清理**: - `close()`关闭套接字描述符 - 程序退出前释放所有系统资源 ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值