udp傳輸大檔案的一個例子
阿新 • • 發佈:2019-01-30
伺服器server.c
/************************************************************************* > File Name: server.c > Author: ljh ************************************************************************/ /* Linux網路程式設計之基於UDP實現可靠的檔案傳輸示例 這篇文章主要介紹了Linux網路程式設計之基於UDP實現可靠的檔案傳輸示例,是很實用的技巧,需要的朋友可以參考下 瞭解網路傳輸協議的人都知道,採用TCP實現檔案傳輸很簡單。相對於TCP,由於UDP是面向無連線、不可靠的傳輸協議, 所以我們需要考慮丟包和後發先至(包的順序)的問題, 所以我們想要實現UDP傳輸檔案,則需要解決這兩個問題。方法就是給資料包編號,按照包的順序接收並存儲,接收端接收到資料包後傳送確認資訊給傳送端, 傳送端接收確認資料以後再繼續傳送下一個包,如果接收端收到的資料包的編號不是期望的編號,則要求傳送端重新發送。 下面展示的是基於linux下C語言實現的一個示例程式,該程式定義一個包的結構體,其中包含資料和包頭,包頭裡包含有包的編號和資料大小, 經過測試後,該程式可以成功傳輸一個視訊檔案。 udp傳輸大檔案,解決了udp傳輸中拆包,組包問題, 1.後包先到 2.資料包確認 3.每包crc32校驗(可選,網友說udp解決了順序組包,應答確認就可以了,協議棧專門有udp校驗) 可以說這個udp可靠傳輸 */ #include<sys/types.h> #include<sys/socket.h> #include<unistd.h> #include<netinet/in.h> #include<arpa/inet.h> #include<stdio.h> #include<stdlib.h> #include<errno.h> #include<netdb.h> #include<stdarg.h> #include<string.h> #include"clientarm/log.h" #define SERVER_PORT 8000 #define BUFFER_SIZE 500 //傳送檔案udp緩衝區大小 #define FILE_NAME_MAX_SIZE 512 /* 包頭 */ typedef struct { long int id; int buf_size; unsigned int crc32val; //每一個buffer的crc32值 int errorflag; }PackInfo; /* 接收包 */ struct SendPack { PackInfo head; char buf[BUFFER_SIZE]; } data; //----------------------crc32---------------- static unsigned int crc_table[256]; static void init_crc_table(void); static unsigned int crc32(unsigned int crc, unsigned char * buffer, unsigned int size); /* 第一次傳入的值需要固定,如果傳送端使用該值計算crc校驗碼, 那麼接收端也同樣需要使用該值進行計算 */ unsigned int crc = 0xffffffff; /* * 初始化crc表,生成32位大小的crc表 * 也可以直接定義出crc表,直接查表, * 但總共有256個,看著眼花,用生成的比較方便. */ static void init_crc_table(void) { unsigned int c; unsigned int i, j; for (i = 0; i < 256; i++) { c = (unsigned int)i; for (j = 0; j < 8; j++) { if (c & 1) c = 0xedb88320L ^ (c >> 1); else c = c >> 1; } crc_table[i] = c; } } /* 計算buffer的crc校驗碼 */ static unsigned int crc32(unsigned int crc,unsigned char *buffer, unsigned int size) { unsigned int i; for (i = 0; i < size; i++) { crc = crc_table[(crc ^ buffer[i]) & 0xff] ^ (crc >> 8); } return crc ; } //主函式入口 int main() { /* 傳送id */ long int send_id = 0; /* 接收id */ int receive_id = 0; /* 建立UDP套介面 */ struct sockaddr_in server_addr; bzero(&server_addr, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(INADDR_ANY); server_addr.sin_port = htons(SERVER_PORT); /* 建立socket */ int server_socket_fd = socket(AF_INET, SOCK_DGRAM, 0); if(server_socket_fd == -1) { printe("Create Socket Failed:"); exit(1); } /* 繫結套介面 */ if(-1 == (bind(server_socket_fd,(struct sockaddr*)&server_addr,sizeof(server_addr)))) { printe("Server Bind Failed:"); exit(1); } //crc32 init_crc_table(); /* 資料傳輸 */ while(1) { /* 定義一個地址,用於捕獲客戶端地址 */ struct sockaddr_in client_addr; socklen_t client_addr_length = sizeof(client_addr); /* 接收資料 */ char buffer[BUFFER_SIZE]; bzero(buffer, BUFFER_SIZE); if(recvfrom(server_socket_fd, buffer, BUFFER_SIZE,0,(struct sockaddr*)&client_addr, &client_addr_length) == -1) { printe("Receive Data Failed:"); exit(1); } /* 從buffer中拷貝出file_name */ char file_name[FILE_NAME_MAX_SIZE+1]; bzero(file_name,FILE_NAME_MAX_SIZE+1); strncpy(file_name, buffer, strlen(buffer)>FILE_NAME_MAX_SIZE?FILE_NAME_MAX_SIZE:strlen(buffer)); printi("%s\n", file_name); /* 開啟檔案 */ FILE *fp = fopen(file_name, "r"); if(NULL == fp) { printi("File:%s Not Found.\n", file_name); } else { int len = 0; /* 每讀取一段資料,便將其發給客戶端 */ while(1) { PackInfo pack_info; bzero((char *)&data,sizeof(data)); //ljh socket傳送緩衝區清零 printi("receive_id=%d\n",receive_id); printi("send_id=%ld\n",send_id); if(receive_id == send_id) { ++send_id; if((len = fread(data.buf, sizeof(char), BUFFER_SIZE, fp)) > 0) { data.head.id = send_id; /* 傳送id放進包頭,用於標記順序 */ data.head.buf_size = len; /* 記錄資料長度 */ data.head.crc32val = crc32(crc,data.buf,sizeof(data)); printi("len =%d\n",len); printi("data.head.crc32val=0x%x\n",data.head.crc32val); //printf("data.buf=%s\n",data.buf); resend: if(sendto(server_socket_fd, (char*)&data, sizeof(data), 0, (struct sockaddr*)&client_addr, client_addr_length) < 0) { printi("Send File Failed:"); break; } /* 接收確認訊息 */ recvfrom(server_socket_fd, (char*)&pack_info, sizeof(pack_info), 0, (struct sockaddr*)&client_addr, &client_addr_length); receive_id = pack_info.id; //如果確認包提示資料錯誤 if(pack_info.errorflag==1) { pack_info.errorflag = 0; goto resend; } //usleep(50000); } else { break; } } else { /* 如果接收的id和傳送的id不相同,重新發送 */ if(sendto(server_socket_fd, (char*)&data, sizeof(data), 0, (struct sockaddr*)&client_addr, client_addr_length) < 0) { printi("Send File Failed:"); break; } printi("repeat send\n"); /* 接收確認訊息 */ recvfrom(server_socket_fd, (char*)&pack_info, sizeof(pack_info), 0, (struct sockaddr*)&client_addr, &client_addr_length); receive_id = pack_info.id; //usleep(50000); } } //傳送結束包 0位元組目的告訴客戶端傳送完畢 if(sendto(server_socket_fd, (char*)&data, 0, 0, (struct sockaddr*)&client_addr, client_addr_length) < 0) { printi("Send 0 char Failed:"); break; } printi("sever send file end 0 char\n"); /* 關閉檔案 */ fclose(fp); printi("File:%s Transfer Successful!\n", file_name); //清零id,準備傳送下一個檔案 /* 傳送id */ send_id = 0; /* 接收id */ receive_id = 0; } } close(server_socket_fd); return 0; }
客戶端client.c
/************************************************************************* > File Name: client.c > Author: ljh ************************************************************************/ #include<sys/types.h> #include<sys/socket.h> #include<unistd.h> #include<netinet/in.h> #include<arpa/inet.h> #include<stdio.h> #include<stdlib.h> #include<errno.h> #include<netdb.h> #include<stdarg.h> #include<string.h> #include"log.h" #define SERVER_PORT 8000 #define BUFFER_SIZE 500 #define FILE_NAME_MAX_SIZE 512 /* 包頭 */ typedef struct { long int id; int buf_size; unsigned int crc32val; //每一個buffer的crc32值 int errorflag; }PackInfo; /* 接收包 */ struct RecvPack { PackInfo head; char buf[BUFFER_SIZE]; } data; //----------------------crc32---------------- static unsigned int crc_table[256]; static void init_crc_table(void); static unsigned int crc32(unsigned int crc, unsigned char * buffer, unsigned int size); /* 第一次傳入的值需要固定,如果傳送端使用該值計算crc校驗碼, 那麼接收端也同樣需要使用該值進行計算 */ unsigned int crc = 0xffffffff; /* * 初始化crc表,生成32位大小的crc表 * 也可以直接定義出crc表,直接查表, * 但總共有256個,看著眼花,用生成的比較方便. */ static void init_crc_table(void) { unsigned int c; unsigned int i, j; for (i = 0; i < 256; i++) { c = (unsigned int)i; for (j = 0; j < 8; j++) { if (c & 1) c = 0xedb88320L ^ (c >> 1); else c = c >> 1; } crc_table[i] = c; } } /* 計算buffer的crc校驗碼 */ static unsigned int crc32(unsigned int crc,unsigned char *buffer, unsigned int size) { unsigned int i; for (i = 0; i < size; i++) { crc = crc_table[(crc ^ buffer[i]) & 0xff] ^ (crc >> 8); } return crc ; } //主函式入口 int main() { long int id = 1; unsigned int crc32tmp; /* 服務端地址 */ struct sockaddr_in server_addr; bzero(&server_addr, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = inet_addr("192.168.26.33"); server_addr.sin_port = htons(SERVER_PORT); socklen_t server_addr_length = sizeof(server_addr); /* 建立socket */ int client_socket_fd = socket(AF_INET, SOCK_DGRAM, 0); if(client_socket_fd < 0) { printe("Create Socket Failed:"); exit(1); } //crc32 init_crc_table(); /* 輸入檔名到緩衝區 */ char file_name[FILE_NAME_MAX_SIZE+1]; bzero(file_name, FILE_NAME_MAX_SIZE+1); printi("Please Input File Name On Server: "); scanf("%s", file_name); char buffer[BUFFER_SIZE]; bzero(buffer, BUFFER_SIZE); strncpy(buffer, file_name, strlen(file_name)>BUFFER_SIZE?BUFFER_SIZE:strlen(file_name)); /* 傳送檔名 */ if(sendto(client_socket_fd, buffer, BUFFER_SIZE,0,(struct sockaddr*)&server_addr,server_addr_length) < 0) { printe("Send File Name Failed:"); exit(1); } /* 開啟檔案,準備寫入 */ FILE *fp = fopen(file_name, "w"); if(NULL == fp) { printe("File:\t%s Can Not Open To Write\n", file_name); exit(1); } /* 從伺服器接收資料,並寫入檔案 */ int len = 0; while(1) { PackInfo pack_info; //定義確認包變數 if((len = recvfrom(client_socket_fd, (char*)&data, sizeof(data), 0, (struct sockaddr*)&server_addr,&server_addr_length)) > 0) { printi("len =%d\n",len); crc32tmp = crc32(crc,data.buf,sizeof(data)); //crc32tmp=5; printi("-------------------------\n"); printi("data.head.id=%ld\n",data.head.id); printi("id=%ld\n",id); if(data.head.id == id) { printi("crc32tmp=0x%x\n",crc32tmp); printi("data.head.crc32val=0x%x\n",data.head.crc32val); //printf("data.buf=%s\n",data.buf); //校驗資料正確 if(data.head.crc32val == crc32tmp) { printi("rec data success\n"); pack_info.id = data.head.id; pack_info.buf_size = data.head.buf_size; //檔案中有效位元組的個數,作為寫入檔案fwrite的位元組數 ++id; //接收正確,準備接收下一包資料 /* 傳送資料包確認資訊 */ if(sendto(client_socket_fd, (char*)&pack_info, sizeof(pack_info), 0, (struct sockaddr*)&server_addr, server_addr_length) < 0) { printi("Send confirm information failed!"); } /* 寫入檔案 */ if(fwrite(data.buf, sizeof(char), data.head.buf_size, fp) < data.head.buf_size) { printi("File:\t%s Write Failed\n", file_name); break; } } else { pack_info.id = data.head.id; //錯誤包,讓伺服器重發一次 pack_info.buf_size = data.head.buf_size; pack_info.errorflag = 1; printi("rec data error,need to send again\n"); /* 重發資料包確認資訊 */ if(sendto(client_socket_fd, (char*)&pack_info, sizeof(pack_info), 0, (struct sockaddr*)&server_addr, server_addr_length) < 0) { printi("Send confirm information failed!"); } } }//if(data.head.id == id) else if(data.head.id < id) /* 如果是重發的包 */ { pack_info.id = data.head.id; pack_info.buf_size = data.head.buf_size; pack_info.errorflag = 0; //錯誤包標誌清零 printi("data.head.id < id\n"); /* 重發資料包確認資訊 */ if(sendto(client_socket_fd, (char*)&pack_info, sizeof(pack_info), 0, (struct sockaddr*)&server_addr, server_addr_length) < 0) { printi("Send confirm information failed!"); } }//else if(data.head.id < id) /* 如果是重發的包 */ } else //接收完畢退出 { break; } } printi("Receive File:\t%s From Server IP Successful!\n", file_name); fclose(fp); close(client_socket_fd); return 0; }