UDP/UDS数据报服务器while(1)循环无法退出导致传输耗时无法统计
UDP/UDS数据报服务器循环无法退出的解决方法
要测试TCP、管道、mmap等各类套接字传输100MB文件的耗时,客户端在发送数据前后计时,服务器在接收数据前和写入文件完成后计时,但UDP/UDS数据报服务器的while(1)循环始终无法退出,导致无法统计传输耗时。以下是UDP IPv6服务器的循环部分代码:
clock_t start = 0, end = 0; FILE *fp; char *filename = "new_data.txt"; fp = fopen(filename, "w"); /* now wait until we get a datagram */ printf("waiting for a datagram...\n"); clilen = sizeof(client_addr); start = clock(); while (1) { ssize_t bytes = recvfrom(sock, buffer, 1024, 0, (struct sockaddr *) &client_addr, &clilen); if (bytes < 0) { perror("recvfrom failed"); exit(4); } fprintf(fp, "%s", buffer); bzero(buffer, 1024); } close(sock); //fclose(fp); end = clock(); double cpu_time_used = ((double) (end - start)) / CLOCKS_PER_SEC; printf("Receive in %f seconds\n", cpu_time_used);
核心问题
UDP是无连接的报文协议,服务器无法像TCP那样通过连接关闭判断传输结束,因此需要额外的机制来触发循环退出。
可行解决方案
1. 约定结束标记
客户端在发送完所有文件数据后,发送一个预定义的结束标记(比如字符串"EOF"、空数据包),服务器收到该标记后退出循环。
修改后的服务器循环示例:
while (1) { ssize_t bytes = recvfrom(sock, buffer, 1024, 0, (struct sockaddr *) &client_addr, &clilen); if (bytes < 0) { perror("recvfrom failed"); exit(4); } // 判断是否收到结束标记 if (bytes == 3 && strncmp(buffer, "EOF", 3) == 0) { break; } // 用接收的字节数指定输出长度,避免buffer无'\0'导致的乱码/越界 fprintf(fp, "%.*s", (int)bytes, buffer); bzero(buffer, 1024); } // 必须关闭文件,确保数据写入磁盘 fclose(fp);
2. 累计接收字节数
由于已知传输文件大小为100MB,服务器可以累计接收的总字节数,当达到100MB时退出循环。
修改后的服务器循环示例:
#define TOTAL_SIZE 100 * 1024 * 1024 // 100MB size_t total_received = 0; while (1) { ssize_t bytes = recvfrom(sock, buffer, 1024, 0, (struct sockaddr *) &client_addr, &clilen); if (bytes < 0) { perror("recvfrom failed"); exit(4); } fprintf(fp, "%.*s", (int)bytes, buffer); total_received += bytes; // 达到总大小则退出 if (total_received >= TOTAL_SIZE) { break; } bzero(buffer, 1024); } fclose(fp);
3. 设置套接字超时
通过setsockopt给套接字设置接收超时,当超过一定时间没有收到数据时,判定传输结束并退出循环。这种方法适合作为辅助机制,避免因异常情况导致服务器无限阻塞。
示例:
struct timeval timeout; timeout.tv_sec = 5; // 超时5秒 timeout.tv_usec = 0; setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout)); while (1) { ssize_t bytes = recvfrom(sock, buffer, 1024, 0, (struct sockaddr *) &client_addr, &clilen); if (bytes < 0) { // 超时错误,判定传输结束 if (errno == EAGAIN || errno == EWOULDBLOCK) { break; } perror("recvfrom failed"); exit(4); } fprintf(fp, "%.*s", (int)bytes, buffer); bzero(buffer, 1024); } fclose(fp);
额外注意事项
- 原代码中
fprintf(fp, "%s", buffer)存在风险:如果buffer中没有字符串结束符'\0',会导致输出乱码或内存越界,建议改用%.*s格式,通过接收的字节数限制输出长度。 - 必须取消注释
fclose(fp),否则文件数据可能停留在缓冲区未写入磁盘,导致统计的写入完成时间不准确。
内容的提问来源于stack exchange,提问作者ATB
相关产品推荐
相关产品推荐

