news 2026/4/27 19:14:35

Linux系统编程——网络:TCP 协议与通信实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux系统编程——网络:TCP 协议与通信实战

目录

一、TCP 的 “三大通信模型”

1.CS 模型(Client-Server)

2.BS 模型(Browser-Server)

3.P2P 模型(Peer-to-Peer)

二、TCP 的核心特征

三、TCP 的核心交互

1.三次握手(建立连接)

2.四次挥手(断开连接)

四、TCP 的 “黏包” 问题

五、Linux 下 TCP 通信的 “流程 + 函数”

1.TCP 函数调用顺序

2.TCP 相关函数

2.1 创建套接字:socket()

2.2 绑定 IP 和端口:bind()

2.3 监听端口:listen()

2.4 接受客户端连接:accept()

2.5 接收数据:recv()

2.6 发送数据:send()

2.7 客户端自动连接服务器:connect()

六、TCP 通信代码示例

1.服务端代码(server.c)

2.客户端代码(client.c)

3.编译运行

4.运行结果


一、TCP 的 “三大通信模型”

1.CS 模型(Client-Server)

  • 客户端是专用程序(比如手机里的微信 App),服务器是后台服务;
  • 协议可以自定义(比如微信自己的通信协议);
  • 资源大多存在客户端本地,功能相对复杂。

2.BS 模型(Browser-Server)

  • 客户端是通用浏览器(Chrome、Edge),服务器是 Web 服务;
  • 协议固定用 HTTP/HTTPS;
  • 资源由服务器发给浏览器,功能相对简单(毕竟依赖浏览器能力)。

3.P2P 模型(Peer-to-Peer)

  • 没有明确的 “客户端 / 服务器” 之分,每个节点既是下载者(客户端)也是上传者(服务器);
  • 典型场景:网络下载工具。

二、TCP 的核心特征

  1. 有连接:通信前必须通过 “三次握手” 建立连接,断开要 “四次挥手”;
  2. 可靠传输:丢包会超时重传,接收方会发 ACK 确认,保证数据不丢不缺;
  3. 流式数据:数据是 “连续无边界” 的字节流(这也是 “黏包” 问题的根源);
  4. 全双工:双方可以同时收发数据;
  5. 拥塞控制:会根据网络状况调整发送速度,避免堵死。

三、TCP 的核心交互

TCP 的交互流程如下:

重点在于三次握手和四次挥手过程。

1.三次握手(建立连接)

TCP 是 “面向连接” 的协议,通信前必须通过三次握手初始化连接、同步序列号,确保双方收发能力正常。

三次握手流程:

三次握手流程
  • 第一次握手:客户端给服务器发 SYN 报文,请求建立连接,同时告诉服务器 “我的初始序列号是 x”;此时客户端进入 SYN_SENT 状态。
  • 第二次握手:服务器收到 SYN 后,回复 SYN+ACK 报文 —— 既确认收到客户端的请求(ACK=x+1),也告诉客户端 “我的初始序列号是 y”;此时服务器进入 SYN_RCVD 状态。
  • 第三次握手:客户端收到 SYN+ACK 后,发 ACK 报文确认(ACK=y+1);此时客户端进入ESTABLISHED(已连接)状态,服务器收到后也进入该状态,连接正式建立。

为什么需要三次?

核心是确认双方的收发能力都正常:客户端知道自己能发、服务器能收;服务器知道自己能收能发;客户端最终确认服务器能发、自己能收。少一次都无法完成双向确认。

2.四次挥手(断开连接)

TCP 是全双工通信,断开连接时双方都要独立关闭自己的发送通道,因此需要四次交互。

四次挥手流程:

四次挥手流程
  • 第一次挥手:客户端发 FIN 报文,请求关闭自己的发送通道;客户端进入FIN_WAIT_1状态。
  • 第二次挥手:服务器收到 FIN 后,发 ACK 报文确认;服务器进入 CLOSE_WAIT 状态,客户端进入 FIN_WAIT_2 状态(此时客户端只能收、不能发,服务器还能发数据)。
  • 第三次挥手:服务器发完剩余数据后,发 FIN 报文请求关闭自己的发送通道;服务器进入 LAST_ACK 状态。
  • 第四次挥手:客户端收到 FIN 后,发 ACK 报文确认,同时等待 2MSL(最大报文生存时间);客户端进入 TIME_WAIT,服务器收到 ACK 后进入 CLOSED,客户端等待超时后也进入 CLOSED。

为什么需要四次?

因为 TCP 是全双工 —— 客户端说 “我不发了”,服务器可能还没发完数据,得等服务器发完,再告诉客户端 “我也不发了”,才能彻底断开。

四、TCP 的 “黏包” 问题

因为 TCP 是 “流式数据”,发送方分 10 次发的内容,接收方可能一次全收了,导致数据混在一起 —— 这就是黏包

解决办法有 3 种:

  1. 自定义结束标志:比如每个数据包末尾加 “\r\n”,接收方读到标志就分割;
  2. 固定数据包大小:用 struct 定义固定长度的结构体,接收方按固定长度读;
  3. 自定义协议头:比如设计一个协议格式:AA(起始符) + Num(数据长度) + Data(数据) + 校验 + BB(结束符),接收方先读头、再读对应长度的数据。

五、Linux 下 TCP 通信的 “流程 + 函数”

1.TCP 函数调用顺序

2.TCP 相关函数

2.1 创建套接字:socket()

int socket(int domain, int type, int protocol);
  • 功能:程序向内核提出创建一个基于内存的套接字描述符
  • 参数:
    • domain:地址族
      • PF_INET == AF_INET:适用于互联网程序
      • PF_UNIX == AF_UNIX:适用于单机进程间通信
    • type:套接字类型
      • SOCK_STREAM:流式套接字,对应 TCP 协议
      • SOCK_DGRAM:用户数据报套接字,对应 UDP 协议
      • SOCK_RAW:原始套接字,对应 IP 协议
    • protocol:协议类型,填 0 表示自动适应用层协议
  • 返回值:
    • 成功:返回申请到的套接字 ID
    • 失败:返回 -1

2.2 绑定 IP 和端口:bind()

int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);
  • 功能:仅服务器端调用,将参数 1 对应的套接字文件描述符,与参数 2 指定的接口地址进行绑定,用于从该接口接收数据。
  • 参数:
    • sockfd:目标套接字的 ID
    • my_addr:需要绑定的接口地址信息
    • addrlen:my_addr 对应的结构体长度
  • 返回值:
    • 成功:返回 0
    • 失败:返回 -1

2.3 监听端口:listen()

int listen(int sockfd, int backlog);
  • 功能:在参数 1 对应的套接字 ID 上,开启监听状态,等待客户端的连接请求。
  • 参数:
    • sockfd:目标套接字的 ID
    • backlog:允许处于三次握手过程中的连接排队数量
  • 返回值:
    • 成功:返回 0
    • 失败:返回 -1

2.4 接受客户端连接:accept()

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
  • 功能:从已监听的连接队列中,取出一个有效的客户端连接,并将其接入当前程序。
  • 参数:
    • sockfd:处于监听状态的套接字 ID
    • addr:用于存储客户端地址信息的结构体指针
      • 若填 NULL,表示不记录客户端信息
      • 若需记录,需先定义变量并传入其地址,函数会自动将客户端信息存入该变量
    • addrlen:addr 对应的结构体长度
      • 若addr为 NULL,该值也填 NULL
      • 若addr不为 NULL,需先将其赋值为 sizeof(struct sockaddr)
  • 返回值:
    • 成功:返回一个新的套接字 ID(后续与该客户端的通信,均基于此 ID)
    • 失败:返回 -1

2.5 接收数据:recv()

ssize_t recv(int sockfd, void *buf, size_t len, int flags);
  • 功能:从指定的套接字中,以 flags 指定的方式,读取长度为 len 的字节数据,并存入 buf 对应的内存中。
  • 参数:
    • sockfd:目标套接字的 ID
      • 服务器端:填 accept 返回的新套接字 ID
      • 客户端:填 socket 返回的套接字 ID
    • buf:用于存储数据的本地内存(通常为数组或动态分配内存)
    • len:期望读取的数据长度
    • flags:数据读取方式,填0表示阻塞式接收
  • 返回值:
    • 成功:返回实际接收到的数据长度(通常≤len)
    • 失败:返回 -1

2.6 发送数据:send()

int send(int sockfd, const void *msg, size_t len, int flags);
  • 功能:从 msg 对应的内存中,取出长度为 len 的数据,以 flags 指定的方式,写入到 spckfd 对应的套接字中。
  • 参数:
    • sockfd:目标套接字的 ID
      • 服务器端:填 accept 返回的新套接字 ID
      • 客户端:填 socket 返回的套接字 ID
    • msg:需要发送的消息内容
    • len:需要发送的消息长度
    • flags:消息发送方式
  • 返回值:
    • 成功:返回实际发送的字符长度
    • 失败:返回 -1

2.7 客户端自动连接服务器:connect()

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • 功能:仅客户端调用,向 addr 对应的远程目标主机,发起连接请求(触发 TCP 三次握手)。
  • 参数:
    • sockfd:本地 socket 创建的套接字 ID
    • addr:远程目标主机的地址信息
    • addrlen:addr 对应的结构体长度
  • 返回值:
    • 成功:返回 0
    • 失败:返回 -1

六、TCP 通信代码示例

1.服务端代码(server.c)

#include <netinet/in.h> #include <netinet/ip.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <strings.h> #include <sys/socket.h> #include <sys/types.h> #include <time.h> #include <unistd.h> typedef struct sockaddr *(SA); int main(int argc, char **argv) { // 监听套接字,用来三次握手 int listfd = socket(AF_INET, SOCK_STREAM, 0); if (-1 == listfd) { perror("socket"); return 1; } struct sockaddr_in ser, cli; bzero(&ser, sizeof(ser)); bzero(&cli, sizeof(cli)); ser.sin_family = AF_INET; ser.sin_port = htons(50000); ser.sin_addr.s_addr = INADDR_ANY; int ret = bind(listfd, (SA)&ser, sizeof(ser)); if (-1 == ret) { perror("bind"); return 1; } // 3代表建立连接(三次握手)的排队数 listen(listfd, 3); socklen_t len = sizeof(cli); // 通信套接字,表示客户端 int conn = accept(listfd, (SA)&cli, &len); if (-1 == conn) { perror("accept"); return 1; } while (1) { char buf[512] = {0}; int ret = recv(conn, buf, sizeof(buf), 0); if (ret <= 0) { break; } time_t tm; time(&tm); struct tm *info = localtime(&tm); sprintf(buf, "%s %d:%d:%d", buf, info->tm_hour, info->tm_min, info->tm_sec); send(conn, buf, strlen(buf), 0); } close(listfd); close(conn); return 0; }

2.客户端代码(client.c)

#include <netinet/in.h> #include <netinet/ip.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <strings.h> #include <sys/socket.h> #include <sys/types.h> #include <time.h> #include <unistd.h> typedef struct sockaddr *(SA); int main(int argc, char **argv) { int sockfd = socket(AF_INET, SOCK_STREAM, 0); if (-1 == sockfd) { perror("socket"); return 1; } struct sockaddr_in ser; bzero(&ser, sizeof(ser)); ser.sin_family = AF_INET; ser.sin_port = htons(50000); ser.sin_addr.s_addr = INADDR_ANY; int ret = connect(sockfd, (SA)&ser, sizeof(ser)); if (-1 == ret) { perror("connect"); return 1; } int i = 10; while (i--) { char buf[512] = "this is tcp test"; send(sockfd, buf, strlen(buf), 0); bzero(buf, sizeof(buf)); recv(sockfd, buf, sizeof(buf), 0); printf("ser:%s\n", buf); sleep(1); } close(sockfd); return 0; }

3.编译运行

# 编译服务端 gcc tcp_server.c -o server # 编译客户端 gcc tcp_client.c -o client # 启动服务端 ./server # 另开终端启动客户端 ./client

4.运行结果

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/21 17:56:29

10分钟快速上手:TsubakiTranslator终极配置指南

10分钟快速上手&#xff1a;TsubakiTranslator终极配置指南 【免费下载链接】TsubakiTranslator 一款Galgame文本翻译工具&#xff0c;支持Textractor/剪切板/OCR翻译 项目地址: https://gitcode.com/gh_mirrors/ts/TsubakiTranslator 想要畅玩日系Galgame却苦于语言障碍…

作者头像 李华
网站建设 2026/4/23 11:23:22

音乐解锁神器:ncmdumpGUI一键解密网易云音乐NCM文件

音乐解锁神器&#xff1a;ncmdumpGUI一键解密网易云音乐NCM文件 【免费下载链接】ncmdumpGUI C#版本网易云音乐ncm文件格式转换&#xff0c;Windows图形界面版本 项目地址: https://gitcode.com/gh_mirrors/nc/ncmdumpGUI 还在为网易云音乐的加密NCM文件无法在其他设备…

作者头像 李华
网站建设 2026/4/22 2:00:36

STM32低功耗模式实践:MDK环境下的优化策略

STM32低功耗实战&#xff1a;如何用MDK榨干每一微安在物联网设备遍地开花的今天&#xff0c;电池寿命成了衡量产品成败的关键指标。你有没有遇到过这样的情况——明明选的是低功耗MCU&#xff0c;系统却跑不了几个月&#xff1f;或者调试时一切正常&#xff0c;实测功耗却高得离…

作者头像 李华
网站建设 2026/4/20 18:58:06

Windows 10系统优化终极指南:使用Win10BloatRemover告别卡顿

Windows 10系统优化终极指南&#xff1a;使用Win10BloatRemover告别卡顿 【免费下载链接】Win10BloatRemover Configurable CLI tool to easily and aggressively debloat and tweak Windows 10 by removing preinstalled UWP apps, services and more. Originally based on th…

作者头像 李华
网站建设 2026/4/20 18:58:05

MCEdit 2.0如何让你的《我的世界》创作之旅更轻松?

MCEdit 2.0如何让你的《我的世界》创作之旅更轻松&#xff1f; 【免费下载链接】mcedit2 MCEdit 2.0 - World Editor for Minecraft. 项目地址: https://gitcode.com/gh_mirrors/mc/mcedit2 作为一名《我的世界》玩家&#xff0c;你是否曾为手动搭建复杂建筑而烦恼&…

作者头像 李华
网站建设 2026/4/25 3:06:27

GTNH汉化完全指南:从零开始打造中文游戏体验

&#x1f3af; 汉化项目价值宣言 【免费下载链接】Translation-of-GTNH GTNH整合包的汉化 项目地址: https://gitcode.com/gh_mirrors/tr/Translation-of-GTNH GTNH汉化项目是为全球最复杂的Minecraft科技魔法整合包提供的中文本地化解决方案。想象一下&#xff0c;当你…

作者头像 李华