Linux内核传输层DCCP分析
一、数据包拥塞控制协议DCCP
DCCP 是一种不可靠的拥塞控制传输协议,它借鉴 UDP 和 TCP,并添加新功能,与 UDP 一样,它是面向消息且不可靠的,与 TCP 一样,它是面向连接的,且将使用三次握手机制来建立连接。每个 DCCP 数据包开头都是一个 DCCP 报头。DCCP 报头最短 12 字节。DCCP 使用 12-2020 字节的变长报头,具体长度取决于使用的是否是短序列号以及包含哪些 TLV 数据包选项。
- 协议定位:DCCP(Datagram Congestion Control Protocol,数据报拥塞控制协议)是传输层协议,核心解决不可靠传输场景下的拥塞控制问题,适用于对实时性要求高但允许部分数据丢失的业务(如流媒体、在线游戏)。
- 特性融合:
- 类似 UDP:面向消息,发送方发送的消息边界在接收方会保留;数据传输不可靠,不保证数据包按序到达或不丢失。
- 类似 TCP:面向连接,通过三次握手机制建立连接,确保通信双方状态同步,提供一定的连接管理能力。
- 报头特点:
- DCCP 数据包以 DCCP 报头开头,报头长度可变(12-2020 字节)。最短 12 字节,若使用短序列号或包含类型 - 长度 - 值(TLV)选项(如扩展功能字段),报头会变长,灵活性高,可适配不同场景需求。
1.头部信息dccp_hdr
2.DCCP套接字初始化操作
在用户空间中,使用系统调用socket()来创建DCCP套接字,其中的域参数(SOCK_DCCP)指明要创建的是DCCP套接字。
3.接收来自网络层的DCCP数据包
方法dccp_v4_rcv()是负责接收来着网络层的DCCP数据包的处理程序:
重要步骤:
数据包有效性检查
- 通过
dccp_invalid_packet(skb)
函数检查sk_buff
(网络数据缓冲区)承载的 DCCP 数据包是否合法(如报头格式、校验和等)。若无效,执行goto discard_it;
直接丢弃数据包,不进行后续处理。提取报头信息
- 从
sk_buff
中解析出 DCCP 报头(struct dccp_hdr *dh
)和 IP 报头(struct iphdr *iph
),为后续处理(如连接匹配、数据归属判断)提供基础信息。向高层传递数据
- 验证通过后,DCCP 层会进一步处理数据包(如匹配连接、更新拥塞控制状态等),最终将有效数据从
sk_buff
中提取,按协议栈向上层(如应用层套接字)传递,完成数据接收流程。
4.发送DCCP数据包
当从DCCP用户空间套接字发送数据的时候,在内核中,最终由方法dccp_sendmsg()处理:
重要步骤:
输入参数校验
- 检查发送数据长度(
if (len > dp->dccps_mss_cache)
),若超过预设的最大分段大小缓存值,直接返回错误(return -EMSGSIZE
),避免无效发送。
sk_buff
创建与封装
- 分配
sk_buff
缓冲区,用于封装用户空间发送的数据。填充 DCCP 报头字段(如序列号、控制信息等),并整合用户数据到sk_buff
中,完成数据包的底层封装。协议层处理
- 处理 DCCP 协议相关逻辑,如拥塞控制(更新发送窗口、记录发送状态等),确保数据包按 DCCP 协议规则发送。
向网络层传递
- 封装完成后,通过网络层接口(如 IP 层发送函数)将
sk_buff
传递给网络层。网络层进一步处理(如添加 IP 报头、路由选择等),最终将数据包发送到物理链路层,完成数据发送流程。
5.用户空间使用DCCP
dccp_server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <linux/dccp.h>
#define PORT 8888
#define BUFFER_SIZE 1024
int main() {
int sockfd, new_sockfd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_len;
char buffer[BUFFER_SIZE];
// 创建DCCP套接字
sockfd = socket(AF_INET, SOCK_DCCP, IPPROTO_DCCP);
if (sockfd < 0) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
// 初始化服务器地址结构
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(PORT);
// 绑定套接字到指定地址和端口
if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("bind failed");
close(sockfd);
exit(EXIT_FAILURE);
}
// 监听连接
if (listen(sockfd, 5) < 0) {
perror("listen failed");
close(sockfd);
exit(EXIT_FAILURE);
}
printf("Server listening on port %d...\n", PORT);
client_len = sizeof(client_addr);
// 接受客户端连接
new_sockfd = accept(sockfd, (struct sockaddr *)&client_addr, &client_len);
if (new_sockfd < 0) {
perror("accept failed");
close(sockfd);
exit(EXIT_FAILURE);
}
// 接收客户端消息
memset(buffer, 0, BUFFER_SIZE);
ssize_t bytes_received = recv(new_sockfd, buffer, BUFFER_SIZE - 1, 0);
if (bytes_received < 0) {
perror("recv failed");
close(new_sockfd);
close(sockfd);
exit(EXIT_FAILURE);
}
printf("Received from client: %s\n", buffer);
// 发送响应给客户端
const char *response = "Message received!";
ssize_t bytes_sent = send(new_sockfd, response, strlen(response), 0);
if (bytes_sent < 0) {
perror("send failed");
}
// 关闭套接字
close(new_sockfd);
close(sockfd);
return 0;
}
dccp_client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <linux/dccp.h>
#define SERVER_IP "127.0.0.1"
#define PORT 8888
#define BUFFER_SIZE 1024
int main() {
int sockfd;
struct sockaddr_in server_addr;
char buffer[BUFFER_SIZE];
// 创建DCCP套接字
sockfd = socket(AF_INET, SOCK_DCCP, IPPROTO_DCCP);
if (sockfd < 0) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
// 初始化服务器地址结构
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0) {
perror("invalid address");
close(sockfd);
exit(EXIT_FAILURE);
}
// 连接到服务器
if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("connect failed");
close(sockfd);
exit(EXIT_FAILURE);
}
// 发送消息到服务器
const char *message = "Hello, server!";
ssize_t bytes_sent = send(sockfd, message, strlen(message), 0);
if (bytes_sent < 0) {
perror("send failed");
close(sockfd);
exit(EXIT_FAILURE);
}
// 接收服务器响应
memset(buffer, 0, BUFFER_SIZE);
ssize_t bytes_received = recv(sockfd, buffer, BUFFER_SIZE - 1, 0);
if (bytes_received < 0) {
perror("recv failed");
close(sockfd);
exit(EXIT_FAILURE);
}
printf("Received from server: %s\n", buffer);
// 关闭套接字
close(sockfd);
return 0;
}
编译运行:
6.DCCP和UDP的区别
比较维度 | DCCP(数据报拥塞控制协议) | UDP(用户数据报协议) |
---|---|---|
连接特性 | 面向连接,使用三次握手机制建立连接,具备一定连接管理能力,数据传输前要建立连接,结束后释放连接 | 无连接,发送数据前无需与接收方建立连接,随时能向目标地址发送数据 |
可靠性 | 提供一定拥塞控制机制保障数据传输稳定性和有序性,在允许部分数据丢失场景下比 UDP 可靠一些 | 数据传输不可靠,不保证数据包按序到达、不丢失或不重复,接收数据可能存在乱序、丢失等情况,可靠性依赖应用层处理 |
拥塞控制 | 核心功能是解决不可靠传输场景下的拥塞控制问题,拥塞控制算法可根据网络状况动态调整数据发送速率,避免网络拥塞 | 本身无内置拥塞控制机制,发送方按自身速率发送数据,不管网络状况,可能导致网络拥塞时数据大量丢失或延迟增加 |
报头特点 | 报头长度可变,最短 12 字节,使用短序列号或包含类型 - 长度 - 值(TLV)选项等会使报头变长,灵活性高,能适配不同场景需求 | 报头固定为 8 字节,包含源端口、目的端口、长度和校验和等字段,结构简单,开销小 |
适用场景 | 适用于对实时性要求高且允许部分数据丢失的业务,如流媒体、在线游戏等,更侧重于需要一定拥塞控制且对可靠性有一定要求的场景,像高清视频流传输 | 适用于对实时性要求极高、对数据完整性要求相对较低的场景,如实时语音通话、在线游戏中的实时操作指令传输等,允许应用层按需灵活处理数据传输 |
应用层控制 | 提供连接管理和拥塞控制功能,在一定程度上限制应用层对数据传输的完全控制,开发者需适配其机制 | 几乎将所有控制权力交给应用层,开发者可根据需求在应用层灵活实现各种自定义传输策略 |
性能开销 | 因有连接建立、拥塞控制等机制,在数据传输过程中会产生更多开销,包括处理时间和额外带宽占用 | 协议简单,无复杂控制机制,性能开销小,传输效率高,能快速发送数据 |
虽然DCCP比UDP功能更多,但是依旧无法代替UDP,原因如下:
可靠性与实时性的平衡
- UDP 的优势:UDP 本身具有简单高效的特点,在游戏中,尤其是实时在线游戏,对实时性要求极高。UDP 允许部分数据包丢失,游戏开发者可以根据游戏的具体情况,如动作类游戏中一些非关键的位置信息等,即使丢失部分数据包也不会对游戏体验造成太大影响,同时能保证游戏的流畅运行。
- DCCP 的不足:DCCP 虽然也适用于允许部分数据丢失的业务,但它毕竟引入了拥塞控制等机制,相对 UDP 来说增加了一定的复杂性和处理时间。在处理实时性要求极高的游戏数据时,可能会因为这些额外的处理而产生一定的延迟,影响游戏的实时性体验。
网络适应性
- UDP 的优势:UDP 在网络环境复杂多变的情况下,具有很强的适应性。游戏玩家可能处于各种不同的网络环境中,UDP 可以通过应用层的一些简单策略,如丢包重传(针对重要数据包)、帧率调整等,来快速适应网络变化。例如在网络不稳定时,游戏可以降低画面质量或减少非关键数据的传输,保证游戏的基本运行。
- DCCP 的不足:DCCP 的拥塞控制机制虽然旨在优化网络传输,但在某些复杂网络场景下,可能会过于保守或反应不够迅速。比如在网络瞬间拥塞又快速恢复的情况下,DCCP 的拥塞窗口调整等机制可能无法像 UDP 那样快速让游戏恢复到正常的数据传输速率,导致游戏出现卡顿等现象。
应用层控制的灵活性
- UDP 的优势:UDP 提供了非常简单的传输机制,几乎把所有的控制权力都交给了应用层。游戏开发者可以根据游戏的具体逻辑和需求,在应用层灵活地实现各种自定义的传输策略。例如,在多人在线射击游戏中,可以根据玩家的操作频率和重要性,动态调整不同类型数据包的发送优先级和频率。
- DCCP 的不足:DCCP 由于其自身的协议特性,虽然提供了一定的连接管理和拥塞控制功能,但这也在一定程度上限制了应用层对数据传输的完全控制。开发者可能需要花费更多的精力去适配 DCCP 的机制,而不能像使用 UDP 那样可以随心所欲地根据游戏的特殊需求来定制传输方案。
兼容性和部署成本
- UDP 的优势:UDP 已经在游戏领域得到了广泛的应用和支持,几乎所有的游戏平台和网络环境都对 UDP 有很好的兼容性。游戏开发者在使用 UDP 时,无需担心与各种设备、网络基础设施以及其他游戏组件之间的兼容性问题。
- DCCP 的不足:DCCP 相对来说应用并不广泛,在游戏领域的生态系统还不够成熟。如果在游戏中使用 DCCP,可能会面临与某些网络设备、防火墙规则以及其他游戏相关技术不兼容的问题。这将增加游戏的部署成本和技术风险,需要投入更多的资源来进行测试和优化。