深入浅出 TCP 通信:从基础到并发服务器实现

TCP(Transmission Control Protocol,传输控制协议)是互联网协议族中面向连接、可靠的字节流传输协议。本文将从 TCP 通信的核心流程、API 接口、与 UDP 的差异,到粘包问题解决,最终深入讲解 TCP 并发服务器的实现思路与 Linux IO 模型,帮助开发者全面掌握 TCP 编程核心知识点。

一、TCP 通信核心流程

TCP 通信基于三次握手建立连接四次挥手关闭连接,分为客户端(发端)和服务端(收端)两个角色,核心操作流程如下:

1.1 TCP 客户端(发端)流程

c

运行

复制代码
socket()   // 创建套接字
connect()  // 发起三次握手,连接服务端
send()     // 发送数据
recv()     // 接收数据
close()    // 关闭套接字,发起四次挥手

1.2 TCP 服务端(收端)流程

c

运行

复制代码
socket()   // 创建套接字
bind()     // 绑定IP和端口
listen()   // 监听连接请求
accept()   // 接受连接(阻塞等待)
recv()     // 接收客户端数据
send()     // 向客户端发送数据
close()    // 关闭套接字

二、TCP 核心函数接口详解

2.1 基础套接字操作

1. socket () - 创建套接字

c

运行

复制代码
int socket(int domain, int type, int protocol);
// 示例:创建TCP套接字
int tcpsockfd = socket(AF_INET, SOCK_STREAM, 0);
  • domain :地址族,AF_INET表示 IPv4
  • type :套接字类型,SOCK_STREAM表示 TCP(流式),SOCK_DGRAM表示 UDP
  • protocol:协议类型,0 表示默认
2. listen () - 监听连接请求

c

运行

复制代码
int listen(int sockfd, int backlog);
  • 功能:监听三次握手请求,将未处理的连接放入队列
  • backlog:未处理连接的最大排队数(如 5、10)
  • 返回值:成功 0,失败 - 1
3. accept () - 接受连接

c

运行

复制代码
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
  • 功能:处理连接队列中的第一个请求,建立客户端与服务端的连接
  • addr:输出参数,存储客户端的 IP 和端口
  • 返回值:成功返回新的套接字描述符(用于与该客户端通信),失败 - 1
4. connect () - 发起连接

c

运行

复制代码
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • 功能:向服务端发送三次握手请求
  • addr:服务端的 IP 和端口
  • 返回值:成功 0,失败 - 1

2.2 数据收发操作

1. send () - 发送数据

c

运行

复制代码
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
  • flags:默认为 0(阻塞发送)
  • 返回值:成功返回发送的字节数,失败 - 1
2. recv () - 接收数据

c

运行

复制代码
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
  • 返回值:成功返回接收字节数,失败 - 1,对方关闭连接返回 0

三、TCP 与 UDP 核心差异

表格

特性 TCP UDP
资源开销 大(包头至少 20 字节) 小(包头仅 8 字节)
连接特性 面向连接(三次握手) 无连接
可靠性 可靠(确认、重传、有序) 不可靠(无确认机制)
传输方式 流式传输 数据报传输
机制复杂度 复杂(流量 / 拥塞控制、断线检测) 简单(无额外控制机制)

四、TCP 粘包问题及解决

4.1 粘包原因

TCP 是流式传输,无数据边界,发送方多次发送的小数据包可能被内核合并为一个数据包发送,接收方一次读取到多个数据包的内容,即 "粘包"。

4.2 解决方法

核心思路:设定数据边界,让接收方能够拆分连续数据,常见方案:

  1. 固定长度:约定每个数据包长度(如每次发送 1024 字节),接收方按固定长度读取;
  2. 分隔符 :在数据包末尾添加特殊分隔符(如\n|),接收方按分隔符拆分;
  3. 头部标识长度:数据包头部添加长度字段(如 4 字节表示数据长度),接收方先读长度再读数据。

示例(分隔符方案):

c

运行

复制代码
// 发送方:数据末尾加换行符
char buf[] = "hello world\n";
send(sockfd, buf, strlen(buf), 0);

// 接收方:按换行符拆分数据
char recv_buf[1024] = {0};
int n = recv(sockfd, recv_buf, sizeof(recv_buf)-1, 0);
char *p = strtok(recv_buf, "\n"); // 按换行符拆分
while(p != NULL) {
    printf("收到数据:%s\n", p);
    p = strtok(NULL, "\n");
}

五、TCP 并发服务器实现

5.1 并发服务器的核心问题

单线程服务端中,accept()(等待新连接)和recv()(等待数据)都是阻塞 IO,无法同时处理多个客户端的连接和数据收发,导致服务端只能串行处理客户端请求。

5.2 Linux IO 模型基础

解决并发问题的核心是理解 Linux 的 4 种 IO 模型:

表格

IO 模型 特点
阻塞 IO 数据未就绪时,进程阻塞等待,不占用 CPU 资源(默认的accept/recv均为阻塞 IO)
非阻塞 IO 数据未就绪时立即返回,需轮询检查,CPU 开销大(通过fcntl设置)
异步 IO 内核监测到 IO 事件后主动向应用层发信号,无需轮询
多路复用 IO 一个接口监听多个文件描述符,任一就绪则返回(select/poll/epoll
关键函数:fcntl(设置非阻塞 IO)

c

运行

复制代码
int fcntl(int fd, int cmd, ...);
// 将套接字设置为非阻塞
int flag = fcntl(fd, F_GETFL); // 获取当前属性
fcntl(fd, F_SETFL, flag | O_NONBLOCK); // 添加非阻塞属性
关键函数:select(多路复用 IO)

c

运行

复制代码
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
// 辅助宏
FD_ZERO(fd_set *set);    // 清空集合
FD_SET(int fd, fd_set *set);  // 将fd加入集合
FD_CLR(int fd, fd_set *set);  // 从集合删除fd
FD_ISSET(int fd, fd_set *set); // 判断fd是否在就绪集合中
  • nfds:监听的最大文件描述符 + 1
  • timeout:超时时间(NULL 表示永久阻塞)
  • 返回值:就绪的文件描述符个数,超时返回 0,失败返回 - 1

5.3 TCP 并发服务器实现方案

方案 1:线程 / 进程模型(简单易实现)
  • 思路:服务端主线程调用accept()接受连接,每建立一个连接就创建一个子线程 / 子进程,专门处理该客户端的recv/send
  • 优点:实现简单,新手易上手;
  • 缺点:资源消耗大,连接数过多时线程 / 进程切换开销高。
方案 2:多路复用模型(高性能)
  • 思路:用select/epoll监听所有文件描述符(包括监听套接字和已连接套接字),根据就绪事件处理连接或数据收发。
  • 示例(select 实现简单并发):

运行

六、抓包调试技巧(Wireshark)

开发中可通过 Wireshark 抓取 TCP/UDP 包调试:

  1. 启动 Wireshark:sudo wireshark
  2. 过滤规则:
    • 过滤 TCP 包:tcp
    • 过滤指定端口:tcp.port == 50000udp.port == 50000
    • 过滤指定 IP:ip.addr == 192.168.0.165

总结

  1. TCP 通信是面向连接的可靠传输,核心流程为 "客户端 connect 连接,服务端 listen/accept 接收连接,双方 send/recv 收发数据";
  2. TCP 粘包源于流式传输无边界,解决核心是 "约定数据边界(固定长度 / 分隔符 / 长度头)";
  3. TCP 并发服务器可通过 "线程 / 进程模型(简单)" 或 "多路复用 IO 模型(高性能)" 实现,多路复用是高并发场景的首选方案。
相关推荐
认真学GIS2 小时前
日尺度地下水水位!全国11897个地下水动态监测站点2005-2021年日尺度地下水水位(地下水埋深)(EXCEL格式)数据
服务器·前端·excel
鲨辣椒100862 小时前
TCP连接有多函数接口阻塞问题???——TCP并发服务器的实现
服务器·网络协议·tcp/ip
风中凌乱2 小时前
linux服务器安装部署mayfly-go
linux·服务器·golang
永远不会出bug2 小时前
调整nginx代理 并获取到访问你网站的用户本机 IP
运维·tcp/ip·nginx
2401_858936882 小时前
深入理解 TCP 并发服务器:从 IO 模型到多路复用实现
服务器·tcp/ip·php
LCG元2 小时前
以太网通信实战:STM32F407+LAN8720A+LwIP,TCP/IP协议栈应用
stm32·嵌入式硬件·tcp/ip
gaize12132 小时前
个人博客 / 官网云服务器|简单好用不贵
运维·服务器
野犬寒鸦2 小时前
SAP后端实习开发面试:操作系统与网络核心考点及Linux与Redis
java·服务器·网络·后端·面试
偷影子的机2 小时前
LVS实验
网络