当前位置: 首页 > news >正文

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数据包的处理程序:

重要步骤:

  1. 数据包有效性检查

    • 通过dccp_invalid_packet(skb)函数检查sk_buff(网络数据缓冲区)承载的 DCCP 数据包是否合法(如报头格式、校验和等)。若无效,执行goto discard_it;直接丢弃数据包,不进行后续处理。
  2. 提取报头信息

    • sk_buff中解析出 DCCP 报头(struct dccp_hdr *dh)和 IP 报头(struct iphdr *iph),为后续处理(如连接匹配、数据归属判断)提供基础信息。
  3. 向高层传递数据

    • 验证通过后,DCCP 层会进一步处理数据包(如匹配连接、更新拥塞控制状态等),最终将有效数据从sk_buff中提取,按协议栈向上层(如应用层套接字)传递,完成数据接收流程。

4.发送DCCP数据包

        当从DCCP用户空间套接字发送数据的时候,在内核中,最终由方法dccp_sendmsg()处理:

重要步骤:

  1. 输入参数校验

    • 检查发送数据长度(if (len > dp->dccps_mss_cache)),若超过预设的最大分段大小缓存值,直接返回错误(return -EMSGSIZE),避免无效发送。
  2. sk_buff创建与封装

    • 分配sk_buff缓冲区,用于封装用户空间发送的数据。填充 DCCP 报头字段(如序列号、控制信息等),并整合用户数据到sk_buff中,完成数据包的底层封装。
  3. 协议层处理

    • 处理 DCCP 协议相关逻辑,如拥塞控制(更新发送窗口、记录发送状态等),确保数据包按 DCCP 协议规则发送。
  4. 向网络层传递

    • 封装完成后,通过网络层接口(如 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,可能会面临与某些网络设备、防火墙规则以及其他游戏相关技术不兼容的问题。这将增加游戏的部署成本和技术风险,需要投入更多的资源来进行测试和优化。

 

相关文章:

  • 靶场(十二)---小白心得靶场思路---Cockpit
  • 基于SpringBoot+Vue开发的在线音乐视频播放平台
  • 深入理解嵌入式开发中的三个重要工具:零长度数组、container_of 和 typeof
  • docker4-容器命令及其案例
  • 【MySQL】MySQL是如何处理请求的?
  • 详解布隆过滤器及其模拟实现
  • HuskyLens:让AI视觉开发更简单
  • 定时器相关
  • 【css酷炫效果】纯CSS实现3D翻转卡片动画
  • 【Pandas】pandas Series dt
  • VBA技术资料MF279:点击任意工作表任意单元格显示其地址
  • Qt Graphics View
  • Vue3 + TS组件封装指南
  • 大模型面试高频考点-显存占用
  • QoS 技术详解:原理、应用与配置实践
  • Java中,`Thread`类的`sleep`方法使用整理
  • 日语学习-日语知识点小记-构建基础-JLPT-N4N5阶段(23):たら ても
  • 如果etc里的文件缺失,或者etc被删除了导致无法正常启动该怎么做?
  • python-leetcode 54.全排列
  • 详细解释javascript的GO对象和AO对象
  • 经济日报:人工智能开启太空经济新格局
  • 上海市重大工程一季度开局良好,多项生态类项目按计划实施
  • 中央军委决定调整组建3所军队院校
  • 玉渊谭天丨卢拉谈美国降低对华关税:中国的行动捍卫了主权
  • 女孩患异食癖爱吃头发,一年后腹痛入院体内惊现“头发巨石”
  • 光明日报:家长孩子共同“息屏”,也要保证高质量陪伴