计算机网络知识点总结(三)Socket基本函数详解,C++ Socket入门实战

📅 2026/7/5 14:22:54
计算机网络知识点总结(三)Socket基本函数详解,C++ Socket入门实战
上一篇文章简单介绍了什么是Socket以及如何用Socket实现客户端和服务端的基本通信。这一篇我打算详细拆解Socket的核心函数配合代码示例让大家能真正动手写出一个可用的程序。Socket数据结构在正式讲函数之前先看一下Socket编程中最核心的数据结构——sockaddr和sockaddr_insockaddr是通用的地址结构而sockaddr_in是IPv4专用的结构实际编程中用得更多。两者可以通过强制类型转换互相转换。Linux系统下的头文件写Socket程序时Linux下需要包含以下头文件#includesys/socket.h// 核心Socket函数和数据结构#includenetinet/in.h// sockaddr_in结构和IP地址转换函数#includearpa/inet.h// inet_addr、inet_ntoa等地址转换函数#includeunistd.h// close()函数#includestring.h// memset()等字符串操作#includeerrno.h// 错误处理Windows系统下的头文件Windows下的头文件略有不同#includewinsock2.h// Windows Socket 2核心头文件#includews2tcpip.h// IPv6支持和新的地址转换函数#pragmacomment(lib,ws2_32.lib)// 链接Winsock库需要注意的是Windows下使用Socket前必须先调用WSAStartup()初始化结束时调用WSACleanup()清理。服务端与客户端通信过程先看一下完整的通信流程图对整个过程有个全局概念服务端的流程是创建socket → 绑定地址 → 监听 → 接受连接 → 读写数据 → 关闭连接。客户端则简单一些创建socket → 连接服务器 → 读写数据 → 关闭连接。基本函数套接字的创建socket()函数用于创建一个套接字描述符intsocket(intdomain,inttype,intprotocol);参数说明参数含义常用值domain协议族地址族AF_INETIPv4、AF_INET6IPv6、AF_LOCAL本地通信type套接字类型SOCK_STREAMTCP面向连接、SOCK_DGRAMUDP无连接、SOCK_RAW原始套接字protocol协议类型IPPROTO_TCP、IPPROTO_UDP设为0时自动选择type对应的默认协议socket()成功时返回非负的文件描述符失败返回-1。向套接字分配网络地址bind()函数把一个地址绑定到socket上intbind(intsockfd,conststructsockaddr*addr,socklen_taddrlen);参数说明sockfdsocket()返回的文件描述符addr指向sockaddr结构的指针包含要绑定的IP地址和端口号addrlen地址结构的长度实际编程中通常用sockaddr_in结构来初始化然后强制转换为sockaddr*structsockaddr_inserv_addr;memset(serv_addr,0,sizeof(serv_addr));serv_addr.sin_familyAF_INET;// IPv4serv_addr.sin_addr.s_addrINADDR_ANY;// 绑定所有可用网卡serv_addr.sin_porthtons(8888);// 端口号需转换为网络字节序进入等待连接请求状态服务端调用listen()开始监听intlisten(intsockfd,intbacklog);sockfd要监听的socket描述符backlog等待队列的最大长度即未被accept的连接最大数量客户端调用connect()发起连接intconnect(intsockfd,conststructsockaddr*addr,socklen_taddrlen);sockfd客户端的socket描述符addr服务器的地址结构addrlen地址结构长度接受客户端连接服务端调用accept()接受连接请求intaccept(intsockfd,structsockaddr*addr,socklen_t*addrlen);sockfd监听socket描述符addr输出参数用于获取客户端的地址信息addrlen输入输出参数调用前设为sizeof(struct sockaddr)返回实际地址长度accept()成功时返回一个新的socket描述符用于与该客户端通信失败返回-1。注意accept()是阻塞函数如果没有连接请求会一直等待。TCP三次握手TCP协议通过三次握手建立可靠连接第一次握手客户端发送SYN包SYNj进入SYN_SEND状态第二次握手服务器收到SYN包确认客户端的SYNACKj1同时发送自己的SYN包SYNk进入SYN_RECV状态第三次握手客户端收到SYNACK包发送确认包ACKACKk1双方进入ESTABLISHED状态三次握手完成后连接建立可以开始传输数据。TCP四次挥手断开连接需要四次挥手由于TCP支持半关闭half-close一端可以在结束发送后继续接收数据。完整关闭需要四次握手主动关闭方发送FIN包进入FIN_WAIT_1状态被动关闭方收到FIN发送ACK确认进入CLOSE_WAIT状态被动关闭方完成数据发送后发送FIN包进入LAST_ACK状态主动关闭方收到FIN发送ACK确认进入TIME_WAIT状态等待一段时间后完全关闭发送数据Linux下用send()或write()发送数据ssize_tsend(intsockfd,constvoid*buf,size_tlen,intflags);ssize_twrite(intsockfd,constvoid*buf,size_tcount);Windows下用send()intsend(SOCKET s,constchar*buf,intlen,intflags);flags参数一般设为0表示常规发送。成功时返回实际发送的字节数失败返回-1。接收数据Linux下用recv()或read()接收数据ssize_trecv(intsockfd,void*buf,size_tlen,intflags);ssize_tread(intsockfd,void*buf,size_tcount);Windows下用recv()intrecv(SOCKET s,char*buf,intlen,intflags);buf是接收缓冲区len是缓冲区大小。成功时返回实际接收的字节数返回0表示对方关闭了连接失败返回-1。关闭连接Linux下用close()关闭socketintclose(intsockfd);Windows下用closesocket()intclosesocket(SOCKET s);C代码实战下面给出一个完整的TCP客户端和服务端示例在Linux环境下编译运行。服务端接收客户端发送的消息并原样返回echo服务。服务端代码 server.c#includestdio.h#includestdlib.h#includestring.h#includeunistd.h#includesys/socket.h#includenetinet/in.h#definePORT8888#defineBUFFER_SIZE1024intmain(){intserver_fd,new_socket;structsockaddr_inaddress;intopt1;intaddrlensizeof(address);charbuffer[BUFFER_SIZE]{0};// 创建socket文件描述符if((server_fdsocket(AF_INET,SOCK_STREAM,0))0){perror(socket failed);exit(EXIT_FAILURE);}// 设置socket选项允许端口复用if(setsockopt(server_fd,SOL_SOCKET,SO_REUSEADDR|SO_REUSEPORT,opt,sizeof(opt))){perror(setsockopt);exit(EXIT_FAILURE);}address.sin_familyAF_INET;address.sin_addr.s_addrINADDR_ANY;address.sin_porthtons(PORT);// 绑定端口if(bind(server_fd,(structsockaddr*)address,sizeof(address))0){perror(bind failed);exit(EXIT_FAILURE);}// 开始监听最大等待队列长度为3if(listen(server_fd,3)0){perror(listen);exit(EXIT_FAILURE);}printf(Server listening on port %d...\n,PORT);// 接受连接if((new_socketaccept(server_fd,(structsockaddr*)address,(socklen_t*)addrlen))0){perror(accept);exit(EXIT_FAILURE);}printf(Client connected\n);// 接收客户端消息并原样返回intvalread;while((valreadread(new_socket,buffer,BUFFER_SIZE))0){printf(Received: %s,buffer);send(new_socket,buffer,strlen(buffer),0);memset(buffer,0,BUFFER_SIZE);}if(valread0){printf(Client disconnected\n);}else{perror(read failed);}close(new_socket);close(server_fd);return0;}客户端代码 client.c#includestdio.h#includestdlib.h#includestring.h#includeunistd.h#includesys/socket.h#includenetinet/in.h#includearpa/inet.h#definePORT8888#defineBUFFER_SIZE1024intmain(intargc,charconst*argv[]){intsock0;structsockaddr_inserv_addr;charbuffer[BUFFER_SIZE]{0};charmessage[BUFFER_SIZE];// 创建socketif((socksocket(AF_INET,SOCK_STREAM,0))0){perror(socket creation failed);exit(EXIT_FAILURE);}serv_addr.sin_familyAF_INET;serv_addr.sin_porthtons(PORT);// 将IPv4地址从点分十进制转换为二进制if(inet_pton(AF_INET,127.0.0.1,serv_addr.sin_addr)0){perror(invalid address/ address not supported);exit(EXIT_FAILURE);}// 连接服务器if(connect(sock,(structsockaddr*)serv_addr,sizeof(serv_addr))0){perror(connection failed);exit(EXIT_FAILURE);}printf(Connected to server. Type exit to quit.\n);while(1){printf(Enter message: );fgets(message,BUFFER_SIZE,stdin);if(strncmp(message,exit,4)0){printf(Disconnecting...\n);break;}// 发送消息send(sock,message,strlen(message),0);// 接收服务器响应intvalreadread(sock,buffer,BUFFER_SIZE);printf(Server response: %s,buffer);memset(buffer,0,BUFFER_SIZE);}close(sock);return0;}编译与运行# 编译服务端gcc server.c-oserver# 编译客户端gcc client.c-oclient# 先运行服务端./server# 打开另一个终端运行客户端./client这个示例比较简单但涵盖了Socket编程的核心流程。实际项目中还需要考虑错误处理、并发处理、超时机制等问题。如果想支持多客户端并发可以参考我后续的文章。