📝摘要:本文梳理传输层UDP、TCP协议头部、核心特性、关键机制、Socket编程接口,包含Wireshark抓包实操、TCP三次握手/四次挥手、滑动窗口、流量控制等核心原理,附带TCP服务端客户端代码示例。
🔖标签:
#Linux网络编程#TCP#UDP#Socket套接字#Wireshark抓包
目录
1. UDP报文头部结构
UDP属于用户数据报协议,是无连接、不可靠的传输层协议,头部固定8字节,不存在选项字段。
| 源端口号(2字节) | 目标端口号(2字节) |
|---|---|
| 数据包长度(2字节) | 校验和Checksum(2字节) |
字段说明:
- 源端口号(2B):发送方应用程序端口;允许为0,表示无返回端口。
- 目的端口号(2B):接收主机上目标应用程序端口,必须指定。
- 数据包长度(2B):UDP头部+UDP数据的总字节长度,最小值为8(仅有头部,无应用数据)。
- 校验和Checksum(2B):校验UDP头部+数据,还包含伪头部,用于检测传输过程中数据损坏;UDP校验和为可选。
💡特点:UDP无连接,没有握手挥手流程;发送即交付,不保证到达;适合音视频、广播等对实时性优先,可容忍少量丢包场景。
2. Wireshark网络抓包实操
Wireshark是跨平台网络抓包分析工具,可以捕获网卡上的数据包,用于协议分析、网络故障定位。
Ubuntu安装
sudo apt-get install wireshark
标准抓包步骤
-
终端启动工具:
sudo wireshark
使用sudo获取网卡抓包权限;普通用户可能出现无法捕获网卡数据包。
-
选择抓包网卡:选择
any,可以捕获本机所有网卡(包含本地回环lo、物理网卡eth0/wlan0)。 -
设置过滤表达式:过滤只抓取UDP 50000端口数据包,过滤表达式:
udp.port == 50000
拓展:
tcp.port == 80抓取tcp80端口;ip.addr == 192.168.1.100过滤指定IP。
- 运行对应网络程序,产生网络通信流量。
- Wireshark面板即可看到捕获完整报文,可逐层展开查看链路层、IP层、传输层头部详情。
✨小提示:过滤器分显示过滤器(上面的udp.port)和捕获过滤器,二者语法不一样,不要混淆。
3. TCP协议详解
TCP,传输控制协议,流式套接字,面向连接的可靠传输协议。
✅TCP核心特点
- 面向有连接:通信前必须建立连接,通信结束正常断开连接;对应三次握手建立,四次挥手断开。
- 面向字节流:应用层下发的数据看作一串无边界字节流,TCP不维护应用消息边界;多次send的数据内核缓冲区会合并,一次recv也可能读取多次发送的数据。
- 可靠传输:依靠三次握手、四次挥手、应答确认、超时重传、滑动窗口等整套机制保证数据准确、有序到达对端。
- 实现复杂,开销大:相比UDP协议头部更大,状态机复杂;实时性弱于UDP。
典型业务场景:HTTPS、MQTT、FTP文件传输,对数据完整度要求高的业务。
TCP通信流程
- 客户端流程
socket()→connect()→send()→recv()→close() - 服务端流程
socket()→bind()→listen()→accept()→recv()→send()→close()
📌概念区分:
- listen监听套接字:专门用来接收客户端连接,不传输业务数据。
- accept返回通信套接字:专门用于和对应客户端收发数据。
三次握手、四次挥手
- 三次握手:客户端主动发起,完成TCP连接建立,确认双方发送、接收能力均正常,协商初始序列号。
- 四次挥手:TCP断开连接,TCP是全双工,每个方向必须单独关闭,保证两端已经把缓冲区剩余数据全部收发完毕再释放连接。
⚠️原始文档笔误修正:TCP四次挥手不是四次握手;挥手处理双向关闭,一个方向关闭之后另一个方向还可以继续传输数据。
4. TCP核心编程API详解
connect
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:客户端向服务端发起TCP连接请求,触发三次握手。
| 参数 | 说明 |
|---|---|
| sockfd | 客户端socket创建的套接字文件描述符 |
| addr | 指向服务端地址结构体(IP+端口) |
| addrlen | 地址结构体的字节大小 |
返回值:成功返回0;失败返回-1,设置errno。
listen
int listen(int sockfd, int backlog);
功能:将socket设置为监听状态,内核维护连接队列,处理客户端三次握手。
| 参数 | 说明 |
|---|---|
| sockfd | 服务端socket创建套接字 |
| backlog | 全连接队列最大长度,代表已经完成三次握手等待accept取走的连接上限 |
返回值:成功返回0;失败返回-1。
💡注意:backlog不是最大客户端总数量,是内核全连接队列的上限。
accept
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能:从内核全连接队列取出一个已经完成三次握手的客户端连接,返回全新通信套接字。
| 参数 | 说明 |
|---|---|
| sockfd | 服务端监听套接字fd |
| addr | 输出参数,保存接入客户端的地址信息,可以传NULL不获取客户端地址 |
| addrlen | 输入输出参数,传入地址结构体大小,返回实际拷贝的地址长度 |
返回值:成功返回通信套接字fd ;失败返回‑1。
⚠️重点:监听socket继续保留监听;业务收发全部使用accept返回新fd。
recv
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
功能:从TCP套接字接收网络数据。
| 参数 | 说明 |
|---|---|
| sockfd | accept得到通信套接字 |
| buf | 接收缓冲区,存放收到数据 |
| len | 期望接收最大字节数 |
| flags | 标志位,填0代表普通读取;还有MSG_PEEK等选项 |
返回值:
- 大于0:实际读到的字节数;
- 等于0:对端已经正常关闭TCP连接;
- -1:发生错误。
注意:recv返回0代表对方close,不是报错,业务代码必须处理该分支。
5. TCP报文头部标志位
TCP头部包含6个1bit控制标志位,控制连接建立、传输、断开、异常重置。
| 标志 | 含义 |
|---|---|
| SYN | 请求建立连接标志;三次握手使用 |
| ACK | 确认应答报文标志;携带确认号,绝大多数TCP报文都会置位ACK |
| PSH | 推送标志;通知应用层立刻读取缓冲区数据,不要缓存 |
| FIN | 请求断开连接标志;四次挥手使用 |
| URG | 紧急数据标志,urgent pointer有效,优先处理紧急数据 |
| RST | 重置连接标志,强制异常断开连接,丢弃缓冲区数据 |
6. TCP核心可靠性机制
- 应答确认机制
TCP对每一个发送字节进行序列号编号;报文头部序列号为本段数据第一个字节编号;接收方回复ACK确认号 = 收到最后一字节编号 + 1,告诉发送方下一次期望收到的字节序号。 - 超时重传机制
发送报文之后启动超时计时器,如果在超时时间内没有收到对应ACK应答,就重新发送该段报文,保障报文丢失场景数据可靠交付。 - 滑动窗口机制
发送方维护滑动窗口,划分4块区域:已发送并且收到应答、已发送未收到应答、允许发送尚未发送、不允许发送;通过窗口大小控制发送的数据量,提升吞吐量。 - 延迟应答机制
接收方不会每收到数据包就立刻回复ACK;可以等待一小段时间,合并应答,减少网络中ACK报文数量,降低开销。 - 流量控制机制
依靠接收方返回ACK报文中携带的接收窗口大小,动态调整发送方发送速率;防止发送方发送太快,接收方缓冲区溢出,应用层来不及处理。 - 捎带应答机制
ACK确认报文不需要单独发送;当接收方本身有应用数据要发给对方时,可以把ACK确认信息捎带在业务数据报文头部一起发送,减少报文数量。
💡区分:流量控制 解决收发双方速率匹配;拥塞控制解决整个网络链路拥堵,原始文档没有提及,二者不可混淆。
7. TCP简单C语言编程示例
TCP服务端示例 server.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#define PORT 8888
#define BUF_LEN 1024
int main(void)
{
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if(listen_fd < 0){
perror("socket");
return -1;
}
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(listen_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0){
perror("bind");
close(listen_fd);
return -1;
}
if(listen(listen_fd, 5) < 0){
perror("listen");
close(listen_fd);
return -1;
}
printf("服务端监听端口:%d\n",PORT);
struct sockaddr_in cli_addr;
socklen_t cli_len = sizeof(cli_addr);
int conn_fd = accept(listen_fd, (struct sockaddr *)&cli_addr, &cli_len);
if(conn_fd < 0){
perror("accept");
close(listen_fd);
return -1;
}
printf("客户端连接:%s:%d\n", inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port));
char buf[BUF_LEN];
ssize_t n;
while((n = recv(conn_fd, buf, BUF_LEN-1,0)) > 0)
{
buf[n] = '\0';
printf("收到客户端:%s\n", buf);
send(conn_fd, buf, n, 0);
}
if(n == 0){
printf("客户端断开连接\n");
}
close(conn_fd);
close(listen_fd);
return 0;
}
TCP客户端示例 client.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#define PORT 8888
#define BUF_LEN 1024
int main(void)
{
int fd = socket(AF_INET, SOCK_STREAM,0);
if(fd < 0){
perror("socket");
return -1;
}
struct sockaddr_in serv_addr;
memset(&serv_addr,0,sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
inet_pton(AF_INET,"127.0.0.1",&serv_addr.sin_addr);
if(connect(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) <0 ){
perror("connect");
close(fd);
return -1;
}
char buf[BUF_LEN];
fgets(buf,BUF_LEN,stdin);
send(fd, buf, strlen(buf),0);
ssize_t n = recv(fd, buf, BUF_LEN-1, 0);
if(n>0){
buf[n] = '\0';
printf("服务端回复:%s\n",buf);
}
close(fd);
return 0;
}
编译运行
gcc server.c -o server
gcc client.c -o client
#终端1
./server
#终端2
./client
8. 英文缩写全称对照表
| 缩写 | 英文全称 | 中文释义 |
|---|---|---|
| UDP | User Datagram Protocol | 用户数据报协议 |
| TCP | Transmission Control Protocol | 传输控制协议 |
| SYN | Synchronize | 同步(建立连接标志) |
| ACK | Acknowledgment | 确认应答 |
| PSH | Push | 推送标志位 |
| FIN | Finish | 结束,断开连接标志 |
| URG | Urgent | 紧急数据标志 |
| RST | Reset | 重置连接标志 |
| HTTPS | Hyper Text Transfer Protocol Secure | 安全超文本传输协议 |
| MQTT | Message Queuing Telemetry Transport | 消息队列遥测传输协议 |
| FTP | File Transfer Protocol | 文件传输协议 |
| fd | file descriptor | 文件描述符 |
补充说明:
- TCP面向字节流,没有消息边界,业务层需要自行设计协议分割报文;
- recv返回0是对端正常close,不是错误,很多初学者会踩坑;
- Wireshark
any网卡可以捕获本地回环127.0.0.1通信报文。
如果本篇对你学习网络编程有帮助,欢迎点赞收藏,