1. 项目概述:单进程TCP通信的核心价值
在Linux网络编程中,TCP通信是最基础也最经典的实现方式。这个项目演示了如何用最简单的单进程模型完成服务端与客户端的全双工通信。虽然现代高并发场景多采用多线程/多进程方案,但理解单线程模型是进阶学习的必经之路——就像学会走路才能跑步。
我十年前刚接触网络编程时,曾被各种IO模型绕得头晕。后来发现,先掌握这种基础模式再逐步扩展,才是最高效的学习路径。通过这个项目,你能清晰看到:
- TCP三次握手在代码层的具体表现
- 阻塞式IO的运作特点
- 字节流传输的本质特征
- 单进程模型的性能边界
提示:建议配合Wireshark抓包工具同步观察通信过程,可以直观看到TCP报文交互细节
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备与工具链选择
2.1 基础开发环境配置
推荐使用Ubuntu 20.04 LTS或CentOS 7+作为开发环境,这两个发行版的网络工具链最稳定。关键组件包括:
bash复制# 安装编译工具和调试器
sudo apt install gcc make gdb net-tools
# 验证TCP内核参数
cat /proc/sys/net/ipv4/tcp_fin_timeout # 默认60秒
2.2 网络调试必备工具
-
netstat:查看端口占用情况
bash复制
netstat -tulnp | grep 你的端口号 -
telnet:快速测试服务可用性
bash复制
telnet 127.0.0.1 8888 -
tcpdump:命令行抓包分析
bash复制sudo tcpdump -i lo -nn 'port 8888' -w dump.pcap
3. 服务端实现深度解析
3.1 套接字创建与绑定
核心代码段:
c复制int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(8888);
bind(listen_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
关键点说明:
INADDR_ANY表示绑定所有可用网卡htons处理端口字节序转换- 绑定失败常见原因:
- 端口被占用(使用netstat检查)
- 权限不足(1024以下端口需要root)
3.2 监听与接受连接
c复制listen(listen_fd, 5); // 第二个参数是backlog队列长度
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int conn_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_len);
注意:backlog参数不是最大连接数,而是已完成三次握手但未被accept的队列长度。实际值会被内核限制,最大不超过/proc/sys/net/core/somaxconn
4. 客户端实现关键点
4.1 连接建立过程
c复制int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr);
serv_addr.sin_port = htons(8888);
connect(sock_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
连接失败常见原因排查:
- 服务端未启动
- 防火墙拦截(sudo ufw status查看)
- 路由不可达(traceroute诊断)
4.2 数据收发注意事项
TCP是字节流协议,需要处理粘包问题。推荐两种方案:
-
固定长度协议
c复制// 发送方 uint32_t len = htonl(strlen(msg)); write(fd, &len, 4); write(fd, msg, strlen(msg)); // 接收方 uint32_t len; read(fd, &len, 4); len = ntohl(len); char buf[len+1]; read(fd, buf, len); buf[len] = '\0'; -
分隔符协议(如\n结尾)
5. 完整通信流程演示
5.1 服务端示例代码
c复制#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define PORT 8888
#define BUFFER_SIZE 1024
int main() {
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(PORT);
bind(listen_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
listen(listen_fd, 5);
printf("[Server] Listening on port %d...\n", PORT);
while(1) {
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int conn_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_len);
char buffer[BUFFER_SIZE];
ssize_t n = read(conn_fd, buffer, BUFFER_SIZE-1);
buffer[n] = '\0';
printf("Received: %s\n", buffer);
char *reply = "Message received!";
write(conn_fd, reply, strlen(reply));
close(conn_fd);
}
close(listen_fd);
return 0;
}
5.2 客户端示例代码
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>
#define SERVER_IP "127.0.0.1"
#define PORT 8888
#define BUFFER_SIZE 1024
int main() {
int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
inet_pton(AF_INET, SERVER_IP, &serv_addr.sin_addr);
connect(sock_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
char *message = "Hello from client!";
write(sock_fd, message, strlen(message));
char buffer[BUFFER_SIZE];
ssize_t n = read(sock_fd, buffer, BUFFER_SIZE-1);
buffer[n] = '\0';
printf("Server reply: %s\n", buffer);
close(sock_fd);
return 0;
}
6. 性能优化与问题排查
6.1 单进程模型瓶颈测试
使用time命令测试并发性能:
bash复制# 服务端
./server &
# 客户端连续测试
time for i in {1..100}; do ./client & done
典型问题现象:
- 连接延迟逐渐增大
- 部分请求超时
- 服务端CPU占用100%
6.2 常见错误码处理
| 错误码 | 含义 | 解决方案 |
|---|---|---|
| EADDRINUSE | 端口被占用 | 修改端口或kill占用进程 |
| ECONNREFUSED | 连接拒绝 | 检查服务端是否启动 |
| ETIMEDOUT | 连接超时 | 检查网络连通性 |
| EPIPE | 连接已关闭 | 添加信号处理 SIGPIPE |
6.3 资源泄漏检查
使用valgrind工具检测:
bash复制valgrind --leak-check=full ./server
重点关注:
- 未关闭的文件描述符
- 内存未释放
- 线程未join
7. 进阶学习路径建议
掌握基础模型后,可以逐步扩展:
- 多进程版本:fork()处理每个连接
- 多线程版本:pthread_create创建线程
- IO多路复用:select/poll/epoll
- 异步IO:libevent/libuv
我在实际项目中发现,理解TCP协议栈的这几点特别重要:
- 滑动窗口机制
- Nagle算法与TCP_NODELAY
- Keepalive参数设置
- 拥塞控制策略
建议下一步用Wireshark分析不同场景下的TCP报文交互,这是理解网络协议最直观的方式。
