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