小弟是剛學會寫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 收到後分析只要是一連串結束封包就可停止程式了