目录
(2)接收连接与处理请求:accept→read/write
在网络编程的学习旅程中,TCP 协议是绕不开的核心内容。它作为一种面向连接的可靠传输协议,支撑着互联网中绝大多数的应用通信。本文将结合一套完整的 C++ 实现代码,从基本原理到具体实践,带你掌握 TCP 编程的全流程 —— 从 socket 创建到多进程并发处理,最终实现一个可交互的 "回声" 程序。
一、TCP 通信的核心逻辑
TCP(Transmission Control Protocol)的通信模型遵循固定的 "连接 - 传输 - 断开" 流程,核心特点是面向连接和可靠传输:
- 角色划分:通信双方分为服务器(被动等待连接)和客户端(主动发起连接)
- 连接建立:通过 "三次握手" 建立可靠连接,确保双方都做好通信准备
- 数据传输:基于字节流的方式传输数据,通过确认机制保证数据不丢失、不重复
- 连接关闭:通过 "四次挥手" 优雅关闭连接,确保双方数据都已传输完成
本次实现的 "回声程序" 是 TCP 编程的经典入门案例:客户端发送任意字符串,服务器接收后添加 "server echo#" 前缀返回,直观展示完整通信流程。
二、TCP 服务器编程步骤
服务器的核心功能是 "监听连接→接收请求→处理交互",完整步骤如下:
步骤 1:创建监听 Socket
Socket(套接字)是网络通信的 "门户",本质是操作系统提供的网络通信接口(文件描述符)。
// 创建TCP监听Socket
int listensockfd = socket(AF_INET, SOCK_STREAM, 0);
if (listensockfd < 0) {
// 错误处理:创建失败(如协议不支持)
perror("socket error");
exit(1);
}
参数解析
AF_INET:使用 IPv4 地址族(互联网最常用)SOCK_STREAM:指定为流式套接字(TCP 协议的特征)0:自动选择对应的数据传输协议(此处为 TCP)
步骤 2:绑定地址与端口(bind)
创建 Socket 后,需要将其与本机的具体 IP 和端口绑定,明确 "监听哪个地址的请求"。
// 准备地址结构(网络字节序)
struct sockaddr_in local_addr;
memset(&local_addr, 0, sizeof(local_addr));
local_addr.sin_family = AF_INET; // IPv4
local_addr.sin_port = htons(8081); // 端口(主机字节序→网络字节序)
local_addr.sin_addr.s_addr = INADDR_ANY; // 绑定所有本地IP(多网卡场景适用)
// 绑定操作
int ret = bind(listensockfd, (struct sockaddr*)&local_addr, sizeof(local_addr));
if (ret < 0) {
perror("bind error");
exit(2);
}
关键细节:
- 网络字节序:TCP 规定网络中数据必须使用大端字节序,
htons()(host to network short)用于端口转换 INADDR_ANY:表示绑定本机所有可用 IP(无需手动指定具体 IP,灵活适配多网卡环境)
步骤 3:设置监听状态(listen)
绑定完成后,需将 Socket 转为 "监听状态",准备接收客户端的连接请求。
// 开始监听(BACKLOG=8:未完成连接队列的最大长度)
int ret = listen(listensockfd, 8);
if (ret < 0) {
perror("listen error");
exit(3);
}
BACKLOG 参数:限制正在进行三次握手(未完成连接)的最大数量,超过此值的新连接会被暂时拒绝。
步骤 4:接收客户端连接(accept)
监听状态的 Socket 可以通过accept()函数阻塞等待并接收客户端连接。
struct sockaddr_in client_addr; // 存储客户端地址
socklen_t client_addr_len = sizeof(client_addr);
// 阻塞等待新连接,返回与该客户端通信的Socket
int clientsockfd = accept(listensockfd, (struct sockaddr*)&client_addr, &client_addr_len);
if (clientsockfd < 0) {
perror("accept error");
continue; // 忽略错误,继续等待下一个连接
}
核心特性:
accept()是阻塞函数,若无新连接则一直等待- 成功返回新的 Socket 描述符(专门用于与当前客户端通信)
- 原监听 Socket(
listensockfd)继续用于接收其他连接
步骤 5:与客户端交互(read/write)
连接建立后,通过read()和write()实现数据收发。
char buffer[4096];
while (true) {
// 读取客户端数据
ssize_t n = read(clientsockfd, buffer, sizeof(buffer) - 1);
if (n > 0) { // 读取成功
buffer[n] = '\0'; // 手动添加字符串结束符
// 处理数据(示例:添加前缀后回送)
std::string response = "server: " + std::string(buffer);
write(clientsockfd, response.c_str(), response.size());
}
else if (n == 0) { // 客户端主动关闭连接
std::cout << "client closed" << std::endl;
break;
}
else { // 读取错误(如网络异常)
perror("read error");
break;
}
}
注意事项:
read()返回值需严格判断:正数为实际读取字节数,0 表示对方关闭,负数表示错误- 避免假设 " 一次
read()能获取完整数据 "(TCP 是流式协议,数据可能分多次到达)
步骤 6:关闭连接(close)
交互结束后,关闭 Socket 释放资源:
close(clientsockfd); // 关闭与客户端的连接Socket
// 服务器退出时关闭监听Socket
// close(listensockfd);


3814

被折叠的 条评论
为什么被折叠?



