You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Linux下C语言UDP模拟TCP:服务器边发数据边带超时等ACK方案咨询

用UDP模拟TCP:服务器并发发送数据块与ACK处理方案

问题描述

我想用UDP模拟TCP协议,希望服务器在等待客户端ACK确认报文的同时,能够发送客户端请求文件的数据块。目前的初步实现是发送一个数据块后调用recv()等待对应ACK,这种停等模式必须收到ACK1才能发送数据块2,效率太低。我想知道:

  1. 是否应该直接循环发送所有数据块,再通过带超时的循环等待所有ACK?
  2. 有没有方案能让recv()在后台以超时模式运行,收到ACK时停止超时,超时则中断流程并重发对应数据块?

补充:开发语言为C,操作系统为Linux。
服务器端头文件:
packet-format.h、arpa/inet.h、limits.h、netinet/in.h、stdbool.h、stddef.h、stdio.h、stdlib.h、string.h、sys/socket.h、unistd.h
客户端端头文件:
packet-format.h、limits.h、netdb.h、stdbool.h、stddef.h、stdio.h、stdlib.h、string.h、unistd.h


解决方案

不推荐全量发送后等待ACK的方案

直接发送所有数据块再等ACK的问题非常明显:

  • 一旦某个数据块丢失,所有后续ACK都会无法匹配,最终需要全量重传,带宽浪费极大。
  • 无法利用UDP的并发发送能力,本质上还是低效的批量停等,完全没发挥模拟TCP的优势。

推荐实现**滑动窗口(Sliding Window)**机制(对应你要的"边发边等"需求)

这是TCP核心的并发传输机制,在Linux C环境下可以通过以下两种方式实现:

方式1:用select()/poll()/epoll()实现IO多路复用

这是单线程下的最优方案,不需要额外线程开销,核心思路是同时监听发送缓冲区状态和UDP套接字的可读事件:

  1. 维护一个未确认数据块队列,记录每个数据块的序号、发送时间、内容。
  2. 每次循环:
    • 检查未确认队列,对超时的数据块(用gettimeofday()记录发送时间,和当前时间对比)执行重发。
    • 如果窗口还有剩余容量(未确认数小于窗口大小),发送下一个数据块并加入未确认队列。
    • 用select()监听套接字的可读事件,设置一个短超时(比如100ms):
      • 如果收到ACK,从队列中移除对应序号的数据块,滑动窗口。
      • 如果超时,回到第一步处理超时重传。

示例核心代码片段:

struct sockaddr_in client_addr;
socklen_t addr_len = sizeof(client_addr);
fd_set read_fds;
struct timeval timeout = {0, 100000}; // 100ms超时

while (has_more_data || !unacked_queue_empty()) {
    // 处理超时重传
    check_and_resend_unacked_blocks();

    // 发送新数据块(如果窗口有空间)
    if (has_more_data && unacked_count < WINDOW_SIZE) {
        send_next_block();
    }

    // 监听ACK
    FD_ZERO(&read_fds);
    FD_SET(sockfd, &read_fds);
    int ret = select(sockfd + 1, &read_fds, NULL, NULL, &timeout);
    if (ret > 0 && FD_ISSET(sockfd, &read_fds)) {
        char ack_buf[ACK_SIZE];
        recvfrom(sockfd, ack_buf, ACK_SIZE, 0, (struct sockaddr*)&client_addr, &addr_len);
        uint32_t ack_seq = parse_ack_sequence(ack_buf);
        remove_unacked_block(ack_seq);
    }
}

方式2:用多线程分离发送与接收逻辑

如果IO多路复用的逻辑对你来说太复杂,可以用两个线程:

  • 发送线程:负责按窗口大小循环发送数据块,维护未确认队列,定时检查超时重传。
  • 接收线程:单独阻塞调用recvfrom()(可以通过setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout))设置超时),收到ACK后更新未确认队列的状态(比如标记对应序号已确认)。

注意:多线程访问未确认队列时需要用互斥锁(pthread_mutex_t)保证线程安全,避免竞态条件。

关于"后台recv超时并中断重发"的细节

在Linux下,setsockopt()设置的SO_RCVTIMEO是针对整个套接字的,无法在收到ACK后临时停止超时,但可以通过以下方式实现类似效果:

  • 用IO多路复用的短超时代替套接字级别的超时,这样每次循环都能灵活控制是否等待ACK,或者处理重发。
  • 如果用多线程,接收线程在收到ACK后可以重置超时时间,或者通过条件变量(pthread_cond_t)通知发送线程无需重传对应数据块。

内容的提问来源于stack exchange,提问作者dredegui

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 05:40:41