如何修改C语言UDP停等协议收发代码实现文件传输功能
UDP停等协议文件传输最小改动方案
改动说明
- 新增文件读写逻辑,替换原有控制台输入输出逻辑
- 启用预定义的FIN帧标记(值为2),用于通知传输结束
- 新增客户端套接字超时配置,实现丢包重传的停等核心逻辑
- 修复原有帧序号递增的逻辑错误,保证传输可靠性
修改后client.c代码
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> typedef struct packet{ char data[1024]; }Packet; typedef struct frame{ int frame_kind; //ACK:0, SEQ:1 FIN:2 int sq_no; int ack; Packet packet; }Frame; int main(int argc, char **argv){ // 修改:参数新增要传输的文件路径 if (argc != 3){ printf("Usage: %s <port> <file_to_send>\n", argv[0]); exit(0); } int port = atoi(argv[1]); int sockfd; struct sockaddr_in serverAddr; char buffer[1024]; socklen_t addr_size; // 新增:打开要传输的文件 FILE *fp = fopen(argv[2], "rb"); if(fp == NULL){ perror("open file failed"); exit(1); } int frame_id = 0; Frame frame_send; Frame frame_recv; int ack_recv = 1; int eof_flag = 0; sockfd = socket(AF_INET, SOCK_DGRAM, 0); // 新增:设置套接字接收超时1秒,实现重传 struct timeval tv = {1, 0}; setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); memset(&serverAddr, '\0', sizeof(serverAddr)); serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(port); serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); while(1){ if(ack_recv == 1){ if(eof_flag == 1){ // 文件已读完,发送完FIN帧后退出 break; } frame_send.sq_no = frame_id; frame_send.ack = 0; // 修改:从文件读数据替换控制台输入 int read_len = fread(buffer, 1, 1024, fp); if(read_len == 0){ // 文件读完,发送FIN帧 frame_send.frame_kind = 2; memset(frame_send.packet.data, 0, 1024); eof_flag = 1; }else{ frame_send.frame_kind = 1; memcpy(frame_send.packet.data, buffer, read_len); } sendto(sockfd, &frame_send, sizeof(Frame), 0, (struct sockaddr*)&serverAddr, sizeof(serverAddr)); printf("[+]Frame %d Send\n", frame_id); } int addr_size = sizeof(serverAddr); int f_recv_size = recvfrom(sockfd, &frame_recv, sizeof(frame_recv), 0 ,(struct sockaddr*)&serverAddr, &addr_size); if( f_recv_size > 0 && frame_recv.sq_no == 0 && frame_recv.ack == frame_id+1){ printf("[+]Ack %d Received\n", frame_recv.ack); ack_recv = 1; // 修改:只有收到正确ACK才递增帧序号 frame_id++; }else{ printf("[-]Ack Not Received, Retransmit\n"); ack_recv = 0; } } fclose(fp); close(sockfd); return 0; }
修改后server.c代码
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/socket.h> #include <unistd.h> #include <arpa/inet.h> typedef struct packet{ char data[1024]; }Packet; typedef struct frame{ int frame_kind; //ACK:0, SEQ:1 FIN:2 int sq_no; int ack; Packet packet; }Frame; int main(int argc, char** argv){ if (argc != 2){ printf("Usage: %s <port>\n", argv[0]); exit(0); } int port = atoi(argv[1]); int sockfd; struct sockaddr_in serverAddr, newAddr; char buffer[1024]; socklen_t addr_size; // 新增:打开接收文件 FILE *fp = fopen("recv_file", "wb"); if(fp == NULL){ perror("open recv file failed"); exit(1); } int frame_id=0; Frame frame_recv; Frame frame_send; sockfd = socket(AF_INET, SOCK_DGRAM, 0); memset(&serverAddr, '\0', sizeof(serverAddr)); serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(port); serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); bind(sockfd, (struct sockaddr*)&serverAddr, sizeof(serverAddr)); addr_size = sizeof(newAddr); while(1){ int f_recv_size = recvfrom(sockfd, &frame_recv, sizeof(Frame), 0, (struct sockaddr*)&newAddr, &addr_size); if (f_recv_size > 0 && frame_recv.sq_no == frame_id){ if(frame_recv.frame_kind == 1){ // 普通数据帧,写入文件 printf("[+]Data Frame %d Received\n", frame_recv.sq_no); fwrite(frame_recv.packet.data, 1, 1024, fp); }else if(frame_recv.frame_kind == 2){ // FIN帧,传输结束 printf("[+]FIN Frame Received, Transfer Complete\n"); frame_send.sq_no = 0; frame_send.frame_kind = 0; frame_send.ack = frame_recv.sq_no + 1; sendto(sockfd, &frame_send, sizeof(frame_send), 0, (struct sockaddr*)&newAddr, addr_size); break; } frame_send.sq_no = 0; frame_send.frame_kind = 0; frame_send.ack = frame_recv.sq_no + 1; sendto(sockfd, &frame_send, sizeof(frame_send), 0, (struct sockaddr*)&newAddr, addr_size); printf("[+]Ack %d Send\n", frame_send.ack); // 修改:只有收到正确帧才递增序号 frame_id++; }else{ printf("[-]Invalid Frame Discarded\n"); } } fclose(fp); close(sockfd); return 0; }
使用方法
- 先启动服务端:
./server <端口号> - 再启动客户端:
./client <端口号> <要传输的文件路径> - 传输完成后两端自动退出,接收的文件保存在服务端运行目录下的
recv_file
内容的提问来源于stack exchange,提问作者I am learning
相关产品推荐
相关产品推荐

