计算机网络知识点总结(三)Socket基本函数详解,C++ Socket入门实战
2026/7/5 14:22:51 网站建设 项目流程

上一篇文章简单介绍了什么是Socket以及如何用Socket实现客户端和服务端的基本通信。这一篇我打算详细拆解Socket的核心函数,配合代码示例让大家能真正动手写出一个可用的程序。

Socket数据结构

在正式讲函数之前,先看一下Socket编程中最核心的数据结构——sockaddrsockaddr_in

sockaddr是通用的地址结构,而sockaddr_in是IPv4专用的结构,实际编程中用得更多。两者可以通过强制类型转换互相转换。

Linux系统下的头文件

写Socket程序时,Linux下需要包含以下头文件:

#include<sys/socket.h>// 核心Socket函数和数据结构#include<netinet/in.h>// sockaddr_in结构和IP地址转换函数#include<arpa/inet.h>// inet_addr、inet_ntoa等地址转换函数#include<unistd.h>// close()函数#include<string.h>// memset()等字符串操作#include<errno.h>// 错误处理

Windows系统下的头文件

Windows下的头文件略有不同:

#include<winsock2.h>// Windows Socket 2核心头文件#include<ws2tcpip.h>// IPv6支持和新的地址转换函数#pragmacomment(lib,"ws2_32.lib")// 链接Winsock库

需要注意的是,Windows下使用Socket前必须先调用WSAStartup()初始化,结束时调用WSACleanup()清理。

服务端与客户端通信过程

先看一下完整的通信流程图,对整个过程有个全局概念:

服务端的流程是:创建socket → 绑定地址 → 监听 → 接受连接 → 读写数据 → 关闭连接。客户端则简单一些:创建socket → 连接服务器 → 读写数据 → 关闭连接。

基本函数

套接字的创建

socket()函数用于创建一个套接字描述符:

intsocket(intdomain,inttype,intprotocol);

参数说明

参数含义常用值
domain协议族(地址族)AF_INET(IPv4)、AF_INET6(IPv6)、AF_LOCAL(本地通信)
type套接字类型SOCK_STREAM(TCP,面向连接)、SOCK_DGRAM(UDP,无连接)、SOCK_RAW(原始套接字)
protocol协议类型IPPROTO_TCPIPPROTO_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_family=AF_INET;// IPv4serv_addr.sin_addr.s_addr=INADDR_ANY;// 绑定所有可用网卡serv_addr.sin_port=htons(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协议通过三次握手建立可靠连接:

  1. 第一次握手:客户端发送SYN包(SYN=j),进入SYN_SEND状态
  2. 第二次握手:服务器收到SYN包,确认客户端的SYN(ACK=j+1),同时发送自己的SYN包(SYN=k),进入SYN_RECV状态
  3. 第三次握手:客户端收到SYN+ACK包,发送确认包ACK(ACK=k+1),双方进入ESTABLISHED状态

三次握手完成后,连接建立,可以开始传输数据。

TCP四次挥手

断开连接需要四次挥手:

由于TCP支持半关闭(half-close),一端可以在结束发送后继续接收数据。完整关闭需要四次握手:

  1. 主动关闭方发送FIN包,进入FIN_WAIT_1状态
  2. 被动关闭方收到FIN,发送ACK确认,进入CLOSE_WAIT状态
  3. 被动关闭方完成数据发送后,发送FIN包,进入LAST_ACK状态
  4. 主动关闭方收到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()关闭socket:

intclose(intsockfd);

Windows下用closesocket()

intclosesocket(SOCKET s);

C代码实战

下面给出一个完整的TCP客户端和服务端示例,在Linux环境下编译运行。服务端接收客户端发送的消息并原样返回(echo服务)。

服务端代码 server.c

#include<stdio.h>#include<stdlib.h>#include<string.h>#include<unistd.h>#include<sys/socket.h>#include<netinet/in.h>#definePORT8888#defineBUFFER_SIZE1024intmain(){intserver_fd,new_socket;structsockaddr_inaddress;intopt=1;intaddrlen=sizeof(address);charbuffer[BUFFER_SIZE]={0};// 创建socket文件描述符if((server_fd=socket(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_family=AF_INET;address.sin_addr.s_addr=INADDR_ANY;address.sin_port=htons(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_socket=accept(server_fd,(structsockaddr*)&address,(socklen_t*)&addrlen))<0){perror("accept");exit(EXIT_FAILURE);}printf("Client connected\n");// 接收客户端消息并原样返回intvalread;while((valread=read(new_socket,buffer,BUFFER_SIZE))>0){printf("Received: %s",buffer);send(new_socket,buffer,strlen(buffer),0);memset(buffer,0,BUFFER_SIZE);}if(valread==0){printf("Client disconnected\n");}else{perror("read failed");}close(new_socket);close(server_fd);return0;}

客户端代码 client.c

#include<stdio.h>#include<stdlib.h>#include<string.h>#include<unistd.h>#include<sys/socket.h>#include<netinet/in.h>#include<arpa/inet.h>#definePORT8888#defineBUFFER_SIZE1024intmain(intargc,charconst*argv[]){intsock=0;structsockaddr_inserv_addr;charbuffer[BUFFER_SIZE]={0};charmessage[BUFFER_SIZE];// 创建socketif((sock=socket(AF_INET,SOCK_STREAM,0))<0){perror("socket creation failed");exit(EXIT_FAILURE);}serv_addr.sin_family=AF_INET;serv_addr.sin_port=htons(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);// 接收服务器响应intvalread=read(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编程的核心流程。实际项目中还需要考虑错误处理、并发处理、超时机制等问题。如果想支持多客户端并发,可以参考我后续的文章。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询