深入浅出 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 模型(高性能)" 实现,多路复用是高并发场景的首选方案。
相关推荐
KOYUELEC光与电子努力加油3 小时前
JAE日本航空电子推出满足汽车市场小型防水最新需求的MX80系列连接器
服务器·科技·单片机·汽车
Zeku4 小时前
虚拟机网络设置
网络·stm32·freertos·linux驱动开发·linux应用开发
攻城狮在此4 小时前
华三交换机ACL配置(封禁内网高危端口)
网络·安全
123过去4 小时前
hashid使用教程
linux·网络·测试工具·安全
cdprinter4 小时前
信刻安全加密光盘,保障光盘保密安全
网络·安全·自动化
XZY0284 小时前
如何使用grpc
运维·服务器
rleS IONS4 小时前
SQL2000在win10上安装的方法
运维·服务器
xiaomo22494 小时前
javaee-网络原理2
网络
云边云科技_云网融合4 小时前
基于深度学习的设备监控技术:从被动报警到主动预警的革新
网络·人工智能·云计算
zly35005 小时前
centos7 sshd无法启动
linux·运维·服务器