Linux网络编程|UDP与TCP传输层协议深度梳理

📝摘要:本文梳理传输层UDP、TCP协议头部、核心特性、关键机制、Socket编程接口,包含Wireshark抓包实操、TCP三次握手/四次挥手、滑动窗口、流量控制等核心原理,附带TCP服务端客户端代码示例。

🔖标签:#Linux网络编程 #TCP #UDP #Socket套接字 #Wireshark抓包


目录

  1. UDP报文头部结构
  2. Wireshark网络抓包实操
  3. TCP协议详解
  4. TCP核心编程API详解
  5. TCP报文头部标志位
  6. TCP核心可靠性机制
  7. TCP简单C语言编程示例
  8. 英文缩写全称对照表

1. UDP报文头部结构

UDP属于用户数据报协议,是无连接、不可靠的传输层协议,头部固定8字节,不存在选项字段。

源端口号(2字节) 目标端口号(2字节)
数据包长度(2字节) 校验和Checksum(2字节)

字段说明:

  1. 源端口号(2B):发送方应用程序端口;允许为0,表示无返回端口。
  2. 目的端口号(2B):接收主机上目标应用程序端口,必须指定。
  3. 数据包长度(2B):UDP头部+UDP数据的总字节长度,最小值为8(仅有头部,无应用数据)。
  4. 校验和Checksum(2B):校验UDP头部+数据,还包含伪头部,用于检测传输过程中数据损坏;UDP校验和为可选。

💡特点:UDP无连接,没有握手挥手流程;发送即交付,不保证到达;适合音视频、广播等对实时性优先,可容忍少量丢包场景。


2. Wireshark网络抓包实操

Wireshark是跨平台网络抓包分析工具,可以捕获网卡上的数据包,用于协议分析、网络故障定位。

Ubuntu安装

复制代码
sudo apt-get install wireshark

标准抓包步骤

  1. 终端启动工具:

    sudo wireshark

使用sudo获取网卡抓包权限;普通用户可能出现无法捕获网卡数据包。

  1. 选择抓包网卡:选择any,可以捕获本机所有网卡(包含本地回环lo、物理网卡eth0/wlan0)。

  2. 设置过滤表达式:过滤只抓取UDP 50000端口数据包,过滤表达式:

    udp.port == 50000

拓展:tcp.port == 80抓取tcp80端口;ip.addr == 192.168.1.100过滤指定IP。

  1. 运行对应网络程序,产生网络通信流量。
  2. Wireshark面板即可看到捕获完整报文,可逐层展开查看链路层、IP层、传输层头部详情。
    ✨小提示:过滤器分显示过滤器(上面的udp.port)和捕获过滤器,二者语法不一样,不要混淆。

3. TCP协议详解

TCP,传输控制协议,流式套接字,面向连接的可靠传输协议。

✅TCP核心特点

  1. 面向有连接:通信前必须建立连接,通信结束正常断开连接;对应三次握手建立,四次挥手断开。
  2. 面向字节流:应用层下发的数据看作一串无边界字节流,TCP不维护应用消息边界;多次send的数据内核缓冲区会合并,一次recv也可能读取多次发送的数据。
  3. 可靠传输:依靠三次握手、四次挥手、应答确认、超时重传、滑动窗口等整套机制保证数据准确、有序到达对端。
  4. 实现复杂,开销大:相比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等选项

返回值:

  1. 大于0:实际读到的字节数;
  2. 等于0:对端已经正常关闭TCP连接;
  3. -1:发生错误。

注意:recv返回0代表对方close,不是报错,业务代码必须处理该分支。


5. TCP报文头部标志位

TCP头部包含6个1bit控制标志位,控制连接建立、传输、断开、异常重置。

标志 含义
SYN 请求建立连接标志;三次握手使用
ACK 确认应答报文标志;携带确认号,绝大多数TCP报文都会置位ACK
PSH 推送标志;通知应用层立刻读取缓冲区数据,不要缓存
FIN 请求断开连接标志;四次挥手使用
URG 紧急数据标志,urgent pointer有效,优先处理紧急数据
RST 重置连接标志,强制异常断开连接,丢弃缓冲区数据

6. TCP核心可靠性机制

  1. 应答确认机制
    TCP对每一个发送字节进行序列号编号;报文头部序列号为本段数据第一个字节编号;接收方回复ACK确认号 = 收到最后一字节编号 + 1,告诉发送方下一次期望收到的字节序号。
  2. 超时重传机制
    发送报文之后启动超时计时器,如果在超时时间内没有收到对应ACK应答,就重新发送该段报文,保障报文丢失场景数据可靠交付。
  3. 滑动窗口机制
    发送方维护滑动窗口,划分4块区域:已发送并且收到应答、已发送未收到应答、允许发送尚未发送、不允许发送;通过窗口大小控制发送的数据量,提升吞吐量。
  4. 延迟应答机制
    接收方不会每收到数据包就立刻回复ACK;可以等待一小段时间,合并应答,减少网络中ACK报文数量,降低开销。
  5. 流量控制机制
    依靠接收方返回ACK报文中携带的接收窗口大小,动态调整发送方发送速率;防止发送方发送太快,接收方缓冲区溢出,应用层来不及处理。
  6. 捎带应答机制
    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 文件描述符

补充说明:

  1. TCP面向字节流,没有消息边界,业务层需要自行设计协议分割报文;
  2. recv返回0是对端正常close,不是错误,很多初学者会踩坑;
  3. Wireshark any网卡可以捕获本地回环127.0.0.1通信报文。

如果本篇对你学习网络编程有帮助,欢迎点赞收藏,

相关推荐
比兔代理2 小时前
动态 IP 代理深度讲解:IP 地址轮换机制与会话保持方案
网络·网络协议·tcp/ip
蕾米莉亚《'';2 小时前
科普向host和port(感谢千问
网络
Mortalbreeze2 小时前
深入理解 Linux IO 模型(一):阻塞 IO、非阻塞 IO 与信号驱动 IO
linux·运维·服务器
一直C2 小时前
Linux应用软件编程|TCP协议与Socket全套编程(三次握手、四次挥手、报文头部、核心机制)
linux·网络协议·tcp/ip·wireshark·vim·visual studio
玫幽倩2 小时前
2025MoeCTF(Pwn全)
网络·安全·pwn·ctf·新生赛·二进程·moectf
rcms152702692182 小时前
Novellus 27-033321-00 涡轮分子泵
网络
2301_780789663 小时前
CDN提供商常用的DDoS缓解技术与策略
linux·运维·服务器·人工智能·架构
薛定e的猫咪3 小时前
(IEEE Transactions 2025)自适应元强化学习动态柔性作业车间调度框架
网络·人工智能·算法
夏炳辉.3 小时前
Rocky Linux 8 从零安装 psql(PostgreSQL 客户端)
linux·运维·postgresql