Linux下C语言UDP模拟TCP:服务器边发数据边带超时等ACK方案咨询
用UDP模拟TCP:服务器并发发送数据块与ACK处理方案
问题描述
我想用UDP模拟TCP协议,希望服务器在等待客户端ACK确认报文的同时,能够发送客户端请求文件的数据块。目前的初步实现是发送一个数据块后调用recv()等待对应ACK,这种停等模式必须收到ACK1才能发送数据块2,效率太低。我想知道:
- 是否应该直接循环发送所有数据块,再通过带超时的循环等待所有ACK?
- 有没有方案能让
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套接字的可读事件:
- 维护一个未确认数据块队列,记录每个数据块的序号、发送时间、内容。
- 每次循环:
- 检查未确认队列,对超时的数据块(用
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
相关产品推荐
相关产品推荐

