iT邦幫忙

1

UDP呼叫recvfrom()卡住

小弟是剛學會寫socket program的菜鳥,學校作業要求寫出分別以tcp及udp傳送檔案的程式,且每傳送5%時紀錄已傳送%數及當下時刻。

小弟不才,只想到笨笨的方法:伺服端每次從檔案讀(fread)一個byte後send/sendto到客戶端,客戶端recv/recvfrom後寫入(fwrite)到新的檔案裡,當客戶端成功寫入所有資料後,傳送訊息告知伺服端後雙方程式才停止。

tcp程式執行後可以正常運作,檔案無損毀;但udp程式在執行後,會卡在伺服端傳送完100%得畫面,服務端則無反應。經過測試後發現,當伺服端完成傳送後,客戶端會卡在recvfrom函式出不來,但伺服端還沒關閉socket,此時若強制停止程式,將會得到接近但小於原始檔案大小的檔案(損毀)。

請問各位大大有甚麼方法可以解決這個問題嗎?
以下程式碼有點亂,還請耐心觀賞:
server.c:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <time.h>

#define PORTNO 6000
#define SIZE 1 //每次傳送1byte
#define MAX 64 
#define PIECE_NUM 20 //傳送檔案大小拆20分
#define EACH_PERCENT 5 //每份佔5%

void error(const char *msg)
{
    perror(msg);
    exit(1);
}

int main(int argc,char *argv[])
{   
    int sock,get_request,cli_finish;
    int count,piece,how_many_piece,percent;
    char buffer[SIZE],f_name[MAX];
    FILE *fp;
    size_t f_size;
    time_t timep;
    socklen_t cli_len;
    struct sockaddr_in serv_addr,cli_addr;
    struct tm *p;
    
    if((sock = socket(AF_INET,SOCK_DGRAM,0)) < 0){ //連接socket
        fprintf(stderr,"fail opening socket\n");
        exit(1);
    }
    memset(&serv_addr,0,sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = INADDR_ANY;
    serv_addr.sin_port = htons(PORTNO);
    
    //bind到socket
    if(bind(sock,(struct sockaddr *) &serv_addr,sizeof(serv_addr)) < 0) 
        error("error on binding");
       
    strcpy(f_name,argv[1]); 
    fp = fopen(f_name,"rb"); 
    fseek(fp,0,SEEK_END);
    f_size = ftell(fp);   //get file size
    piece = f_size / PIECE_NUM; //每份大小
    fseek(fp,0,SEEK_SET);
    
    cli_len = sizeof(cli_addr);
    while(1){
        if(recvfrom(sock,&get_request,sizeof(get_request),0,(struct sockaddr *)&cli_addr,&cli_len) > 0)
            break;
    }
    
    if(sendto(sock,&f_name,sizeof(f_name),0,(struct sockaddr *)&cli_addr,cli_len) < 0)
        error("error on sending file name");
    if(sendto(sock,&f_size,sizeof(f_size),0,(struct sockaddr *)&cli_addr,cli_len) < 0)
        error("error on sending file size");
    
    count = 0;
    how_many_piece = 0;
    percent = 0;
    while(!feof(fp)){ //重複執行,直到檔案結尾
        count++;
        fread(buffer,SIZE,1,fp); 
        if(sendto(sock,buffer,SIZE,0,(struct sockaddr *)&cli_addr,cli_len) < 0)
            error("error on sending file");
        memset(buffer,0,sizeof(buffer));
        
        if(count == piece * (how_many_piece + 1)){ //每5%印出%數及時間
            percent += EACH_PERCENT;
            time(&timep);  
            p = localtime(&timep);
            printf("%d%s  ",percent,"%");
            printf("%d/%d/%d  ",(1900+p->tm_year),(1+p->tm_mon),(p->tm_mday));
            printf("%d:%d:%d\n",(p->tm_hour),(p->tm_min),(p->tm_sec));
            how_many_piece++;
        }
    }
    while(1){ //確認client接收完畢
        if(recvfrom(sock,&cli_finish,sizeof(cli_finish),0,(struct sockaddr *)&cli_addr,&cli_len) > 0)
            break;
    }
    printf("done!\n");
    
    return 0;
}

client.c:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#define PORTNO 6000
#define SIZE 1 //每次傳送1byte
#define MAX 64
#define PIECE_NUM 20 //傳送檔案大小拆20分
#define EACH_PERCENT 5 //每份佔5%

void error(const char *msg)
{
    perror(msg);
    exit(1);
}

int main(int argc,char *argv[])
{   
    int sock,request,finish;
    char buffer[SIZE],f_name[MAX];
    FILE *fp;
    size_t f_size;
    socklen_t serv_len;
    struct sockaddr_in serv_addr,my_addr;

    if((sock = socket(AF_INET,SOCK_DGRAM,0)) < 0){
        fprintf(stderr,"fail opening socket\n");
        exit(1);
    }

    memset(&my_addr,0,sizeof(my_addr));
    my_addr.sin_family = AF_INET;
    my_addr.sin_addr.s_addr = INADDR_ANY;
    my_addr.sin_port = htons(0);

    if(bind(sock,(struct sockaddr *) &my_addr,sizeof(my_addr)) < 0)
        error("error on binding");

    memset(&serv_addr,0,sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    serv_addr.sin_port = htons(PORTNO);

    serv_len = sizeof(serv_addr);
    if(sendto(sock,&request,sizeof(request),0,(struct sockaddr *) &serv_addr,serv_len) < 0)
        error("error on sending request");
    
    if(recvfrom(sock,&f_name,sizeof(f_name),0,(struct sockaddr *) &serv_addr,&serv_len) < 0)
        error("error on receiving file name");
    if(recvfrom(sock,&f_size,sizeof(f_size),0,(struct sockaddr *) &serv_addr,&serv_len) < 0)
        error("error on receiving file size");
    char output[MAX] = "output_"; 
    strcat(output,f_name);
    fp = fopen(output,"wb");
    
    while(recvfrom(sock,buffer,SIZE,0,(struct sockaddr *) &serv_addr,&serv_len) > 0){
        fwrite(buffer,SIZE,1,fp);
        memset(buffer,0,sizeof(buffer));
    }
    
    if(sendto(sock,&finish,sizeof(finish),0,(struct sockaddr *) &serv_addr,serv_len) < 0) 
        error("error on sending finish message");
        
    return 0;
}

執行:
伺服端:$gcc server.c -o server $./server test.jpg
客戶端:$gcc client.c -o client $./client

看更多先前的討論...收起先前的討論...
weiclin iT邦高手 4 級 ‧ 2019-03-20 22:04:40 檢舉
因為你的 client 沒有檢查收到多少資料了啊, 所以迴圈一直沒結束
levi0421 iT邦新手 5 級 ‧ 2019-03-21 00:18:30 檢舉
剛剛改了client的code,限制迴圈run完檔案大小的次數後break,可是還是卡住了
weiclin iT邦高手 4 級 ‧ 2019-03-21 00:30:58 檢舉
嗯, 你可以在 client 收到資料的時候看總共收到多少 bytes 印出來, 我猜你有封包遺失了
levi0421 iT邦新手 5 級 ‧ 2019-03-21 00:51:31 檢舉
原檔案有42464 Bytes,但client只收到36484 Bytes...剛剛爬文發現可能是sendto速度太快了,recvfrom來不及收到就遺失掉了
weiclin iT邦高手 4 級 ‧ 2019-03-21 02:06:40 檢舉
是啊, 用 udp 傳檔案要可靠的話, 你得實做一些類似 tcp 的功能, 最少也得有 ack 封包與重送機制, 我不曉得你程度到哪, 最好還能做出 sliding window
levi0421 iT邦新手 5 級 ‧ 2019-03-21 11:51:14 檢舉
好的,謝謝你的回答~下次的作業正好要求做出reliable udp,我得去研究一下了
weiclin iT邦高手 4 級 ‧ 2019-03-21 12:37:16 檢舉
既然是下次的作業, 那你這次可以只修改成這樣: server 每送一個封包就要等 client 回應一次收到了, 這樣一來一往速度會變慢, 在區網內應該就不太會丟封包

1 個回答

0
echochio
iT邦研究生 5 級 ‧ 2019-03-23 23:01:57

我相信您的程式沒問題 .... 因為我沒看 ... 哈
但是架構有問題 ....
因為 udp 傳送有個很大的問題 , client 知道 server 開始送資料了 , 但是不知道結束了 ...
所以我猜你的 傳送的資訊內沒有結束的封包內容告知 client 已結束的 可以停止程式了 ...
資料段結束加一堆結束通知 , client 收到後分析只要是一連串結束封包就可停止程式了

我要發表回答

立即登入回答