
下面我给你逐行详细拆解 这 5 个 socket 核心 API,讲清它们的作用、参数含义、返回值、使用场景和关键细节,帮你彻底搞懂 TCP 网络编程的核心流程。
1.
socket()------ 创建套接字(客户端 + 服务器都要用)作用
创建一个套接字文件描述符 (相当于网络通信的 "句柄"),是网络编程的第一步 。它定义了通信的地址族、套接字类型、协议类型。
函数原型
cppint socket(int domain, int type, int protocol);参数详解
参数 含义 常用取值 domain地址族(通信域),决定用哪种网络协议 AF_INET:IPv4 网络AF_INET6:IPv6 网络AF_UNIX:本地进程间通信(Unix 域)type套接字类型,决定通信的特性 SOCK_STREAM:TCP(面向连接、可靠、字节流)SOCK_DGRAM:UDP(无连接、不可靠、数据报)SOCK_RAW:原始套接字(直接操作 IP 层)protocol具体协议,一般填 0让系统自动匹配0:自动匹配(TCP 对应IPPROTO_TCP,UDP 对应IPPROTO_UDP)IPPROTO_TCP:显式指定 TCPIPPROTO_UDP:显式指定 UDP返回值
- 成功:返回非负整数 (套接字文件描述符
sockfd)- 失败:返回
-1,可通过perror()打印错误信息关键要点
- 客户端和服务器都必须先调用
socket(),拿到sockfd才能进行后续操作。SOCK_STREAM对应 TCP,SOCK_DGRAM对应 UDP,这是最常用的两种类型。示例
cpp// 创建 IPv4 的 TCP 套接字 int sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd == -1) { perror("socket failed"); exit(1); }2.
bind()------ 绑定地址(服务器端专用)作用
将套接字
sockfd与一个具体的 IP 地址 + 端口号 绑定,让客户端能通过这个地址找到服务器。客户端一般不需要bind()(系统会自动分配临时端口)。函数原型
int bind(int socket, const struct sockaddr *address, socklen_t address_len);参数详解
参数 含义 关键说明 socket要绑定的套接字 sockfd即 socket()返回的文件描述符address指向地址结构体的指针 通用结构体 struct sockaddr,实际用struct sockaddr_in(IPv4),需强制转换address_len地址结构体的长度 填 sizeof(struct sockaddr_in)关键细节(必看)
地址结构体
struct sockaddr_in(IPv4 专用)
cppstruct sockaddr_in { sa_family_t sin_family; // 地址族,必须填 AF_INET in_port_t sin_port; // 端口号,需用 htons() 转网络字节序 struct in_addr sin_addr; // IP 地址 char sin_zero[8]; // 填充位,全填 0 };
sin_port:端口号,必须用htons()转换(主机字节序 → 网络字节序,大端序)。sin_addr.s_addr:IP 地址,常用INADDR_ANY(监听本机所有网卡的 IP,如 127.0.0.1、局域网 IP 等)。网络字节序转换
- 端口 / IP 必须转网络字节序:
htons()(端口)、htonl()(IP)- 接收时转主机字节序:
ntohs()(端口)、ntohl()(IP)返回值
- 成功:返回
0- 失败:返回
-1(常见错误:端口被占用、权限不足)示例
cppstruct sockaddr_in serv_addr; memset(&serv_addr, 0, sizeof(serv_addr)); // 清空结构体 serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(8080); // 绑定 8080 端口,转网络字节序 serv_addr.sin_addr.s_addr = INADDR_ANY; // 监听所有网卡 // 绑定套接字 if (bind(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) { perror("bind failed"); exit(1); }3.
listen()------ 开启监听(TCP 服务器专用)作用
将
bind()后的套接字从 "主动套接字" 转为 "被动监听套接字" ,让服务器开始等待客户端的连接请求。UDP 不需要listen()(无连接,无需监听)。函数原型
cppint listen(int socket, int backlog);参数详解
参数 含义 说明 socket监听的套接字 sockfd即 bind()后的套接字backlog等待连接的队列长度 指全连接队列 的最大长度(已完成三次握手、等待 accept()的连接数),不同系统有上限(如 Linux 一般最大 128)返回值
- 成功:返回
0- 失败:返回
-1关键要点
- 仅 TCP 服务器需要调用,调用后套接字进入监听状态。
backlog不是 "最大连接数",而是 "等待被接受的连接数",实际最大连接数受系统资源限制。示例
cpp// 开启监听,队列长度 5 if (listen(sockfd, 5) == -1) { perror("listen failed"); exit(1); }4.
accept()------ 接受连接(TCP 服务器专用)作用
从监听队列中取出一个客户端连接 ,并创建一个新的套接字 (用于和该客户端单独通信)。原监听套接字继续保持监听状态,可接受其他客户端。UDP 不需要
accept()(无连接,无需建立连接)。函数原型
cppint accept(int socket, struct sockaddr* address, socklen_t* address_len);参数详解
参数 含义 关键说明 socket监听套接字 sockfd即 listen()后的套接字address输出参数 用于存储客户端的地址信息 (IP + 端口),不需要可填 NULLaddress_len输入输出参数 传入时填 address结构体的长度,返回时填实际长度,不需要可填NULL返回值
- 成功:返回新的连接套接字
connfd(专门用于和该客户端通信)- 失败:返回
-1关键要点
accept()是阻塞函数:若无客户端连接,会一直阻塞,直到有连接到来。- 新的
connfd是独立的套接字 ,服务器通过它和客户端收发数据,原sockfd继续监听。- 多客户端场景:服务器需循环调用
accept(),或用多线程 / IO 多路复用(select/poll/epoll)处理。示例
cppstruct sockaddr_in cli_addr; socklen_t cli_len = sizeof(cli_addr); // 接受客户端连接,阻塞等待 int connfd = accept(sockfd, (struct sockaddr*)&cli_addr, &cli_len); if (connfd == -1) { perror("accept failed"); exit(1); } // 打印客户端 IP 和端口 printf("client connected: %s:%d\n", inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port));5.
connect()------ 建立连接(TCP 客户端专用)作用
客户端主动向服务器发起连接请求 ,完成 TCP 三次握手,建立可靠连接。UDP 不需要
connect()(无连接,可直接sendto()/recvfrom()通信)。函数原型
cppint connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);参数详解
参数 含义 说明 sockfd客户端的套接字 sockfd即客户端 socket()创建的套接字addr服务器的地址结构体 需填服务器的 IP + 端口,同样用 struct sockaddr_in,强制转换为struct sockaddr*addrlen地址结构体的长度 填 sizeof(struct sockaddr_in)返回值
- 成功:返回
0(连接建立完成)- 失败:返回
-1(常见错误:服务器未启动、IP / 端口错误、网络不通)关键要点
- 仅 TCP 客户端需要调用,调用后客户端和服务器的连接就建立了。
- 客户端无需
bind(),系统会自动分配临时端口。connect()也是阻塞函数,直到连接成功或失败。示例
cppstruct sockaddr_in serv_addr; memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(8080); // 服务器端口 // 服务器 IP,这里用 127.0.0.1(本地回环) if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) { perror("inet_pton failed"); exit(1); } // 连接服务器 if (connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) { perror("connect failed"); exit(1); }
总结:TCP 客户端 / 服务器的完整流程
服务器端(TCP)
socket()→ 创建监听套接字bind()→ 绑定 IP + 端口listen()→ 开启监听accept()→ 接受客户端连接,得到通信套接字read()/write()(或recv()/send())→ 和客户端通信close()→ 关闭套接字客户端(TCP)
socket()→ 创建客户端套接字connect()→ 连接服务器read()/write()(或recv()/send())→ 和服务器通信close()→ 关闭套接字UDP 简化流程(无连接)
- 服务器:
socket()→bind()→recvfrom()/sendto()- 客户端:
socket()→sendto()/recvfrom()(无需connect())