You need to enable JavaScript to run this app. 如何修改C语言UDP停等协议收发代码实现文件传输功能
优惠活动
大模型
产品
解决方案
定价
更多

如何修改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;
}

使用方法

  1. 先启动服务端:./server <端口号>
  2. 再启动客户端:./client <端口号> <要传输的文件路径>
  3. 传输完成后两端自动退出,接收的文件保存在服务端运行目录下的recv_file

内容的提问来源于stack exchange,提问作者I am learning

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.16 12:45:08