這個系列是準備講基於Linux Socket進行文件傳輸。簡單的文件傳輸就是客戶端能夠上傳文件,能夠從服務器端下載文件。就這麼兩個功能若是再加上身份驗證,就成了FTP服務器了,若是對用戶的操做再加上一些功能(如分享),就能夠做爲一個最簡單的網盤了。想一想是否是有點小激動啊。html
我這一小節就不講那麼高級的東西,就先了解文件怎麼傳輸,咱們之前的聊天程序傳輸數據都是一次發送就完成本次的發送,由於一個sendBuf是足夠的。可是對於二進制文件來講,文件的大小就不必定了,有可能很大,因此咱們的Buf是不知道要多少的。因此要傳輸大文件,就要分屢次傳輸,而後在目的地進行合併。這樣就能夠實現大文件傳輸了。傳輸的方法有兩種,一種是串行一種是並行。我接下來要實現的代碼是使用串行傳輸的,由於比較簡單,而並行傳輸,比較複雜,要傳輸幾個控制信號,保證數據合併重整後不會出錯。並行能夠使用多進(線)程。優勢是能夠同時傳輸多個文件,串行是隻能一次傳輸一個文件。咱們看一下網盤,大可能是能夠下載多個文件(多任務)是吧?應該用的就是並行了。若是隻是從原理上講串行的傳輸速度確定是比並行的快,由於並行還要控制信號要傳,並且還要從新合併整合成一個文件。既然這樣那爲何全部的網盤都是使用多任務下載呢?(多個任務同時下載,每一個任務同時還有多個端口在接收數據)。是由於咱們所處的網絡環境問題。咱們的網絡環境並非那麼的穩定,若是隻是用一個端口進行串行傳輸,那麼若是網絡忽然中斷或者什麼緣由,致使服務器與客戶端暫時鏈接斷開。那麼咱們以前傳的數據就要重傳了。這就是爲何今天下載個任務到80%,而後明天還能夠接着下載了。若是是一次性串行傳輸就不行了。你就會問,那在串行傳輸再加個控制信號不就好了?那這樣的話,與並行區別就不大了。c++
廢話說了一大堆,到了真正要寫代碼的時候我仍是使用串行來寫,由於比較容易實現。(求原諒!)服務器
實現客戶端向服務器發送一個指定的二進制文件網絡
client.cpp (爲何用cpp了,由於上次用c,而後代碼越寫越多,結果不少變量都寫在開頭,找起來不是很方便,因此用cpp了,哎,用c++就是爲了用這個,會不會被罵?)socket
1 #include <netinet/in.h> // sockaddr_in 2 #include <sys/types.h> //socket 3 #include <sys/socket.h> //socket 4 #include <netdb.h> //gethostbyname 5 #include <unistd.h> //close 6 #include <stdio.h> 7 #include <stdlib.h> 8 #include <string.h> 9 #include <time.h> 10 #include <arpa/inet.h> //inet_addr 11 12 #define SERVVER_PORT 12138 13 #define LISTEN_QUEUE 20 14 #define BUFFER_SIZE 1024 15 16 struct Addr 17 { 18 char host[64]; 19 int port; 20 }; 21 22 int file_push(struct Addr addr,char *filenames) 23 { 24 struct sockaddr_in servAddr; 25 struct hostent * host; 26 int sockfd; 27 FILE *fp; 28 29 host=gethostbyname(addr.host); 30 servAddr.sin_family=AF_INET; 31 servAddr.sin_addr=*((struct in_addr *)host->h_addr); 32 //servAddr.sin_addr.s_addr=inet_addr("127.0.0.1"); 33 servAddr.sin_port=htons(addr.port); 34 if(host==NULL) 35 { 36 perror("獲取IP地址失敗"); 37 exit(-1); 38 } 39 if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1) 40 { 41 perror("socket建立失敗"); 42 exit(-1); 43 } 44 45 if(connect(sockfd,(struct sockaddr *)&servAddr,sizeof(struct sockaddr_in))== -1) 46 { 47 perror("connect 失敗"); 48 exit(-1); 49 } 50 51 //打開文件 52 if((fp=fopen(filenames,"rb"))==NULL) 53 { 54 perror("文件打開失敗"); 55 exit(-1); 56 } 57 char buffer[BUFFER_SIZE]; 58 bzero(buffer,BUFFER_SIZE); 59 printf("正在傳輸文件"); 60 int len=0; 61 //不斷的讀取文件直到文件結束 62 while((len=fread(buffer,1,BUFFER_SIZE,fp))>0) 63 { 64 if(send(sockfd,buffer,len,0)<0) 65 { 66 perror("發送數據失敗"); 67 exit(-1); 68 } 69 bzero(buffer,BUFFER_SIZE); 70 printf(".");//1K打印一個點//若是要實現百分比,就要計算文件大小,而後再處理便可 71 } 72 73 fclose(fp);//關閉文件流 74 close(sockfd);//關閉socket鏈接 75 76 return 0; 77 } 78 79 int main(int argc,char *argv[]) 80 { 81 char orderbuf[BUFFER_SIZE]; 82 char orderch[BUFFER_SIZE]; 83 struct Addr addr; 84 85 strcpy(addr.host,argv[1]); 86 addr.port=atoi(argv[2]); 87 while(1) 88 { 89 printf("\n請輸入文件名:"); 90 fgets(orderbuf,BUFFER_SIZE,stdin); 91 orderbuf[strlen(orderbuf)-1]=0;//去掉獲取到的回車符 92 //printf("%s\n",orderbuf); 93 file_push(addr,orderbuf); 94 } 95 96 return 0; 97 }
server.cppspa
1 #include <netinet/in.h> 2 #include <sys/types.h> 3 #include <sys/socket.h> 4 #include <stdlib.h> 5 #include <time.h> 6 #include <string.h> 7 #include <unistd.h> 8 #include <stdio.h> 9 #include <arpa/inet.h> //inet_ntoa 10 11 #define SERVER_PORT 12138 12 #define LISTEN_QUEUE 20 13 #define BUFFER_SIZE 1024 14 15 void print_time(char *ch);//打印時間 16 17 int main(int argc,char *argv[]) 18 { 19 struct sockaddr_in server_addr; 20 bzero(&server_addr,sizeof(server_addr)); 21 server_addr.sin_family=AF_INET; 22 server_addr.sin_addr.s_addr=htons(INADDR_ANY); 23 server_addr.sin_port=htons(SERVER_PORT); 24 25 //建立套接字 26 int sockfd=socket(AF_INET,SOCK_STREAM,0); 27 if(sockfd<0) 28 { 29 perror("建立套接字失敗"); 30 exit(-1); 31 } 32 33 if(bind(sockfd,(struct sockaddr *)&server_addr,sizeof(server_addr))==-1) 34 { 35 perror("bind 失敗"); 36 exit(-1); 37 } 38 39 if(listen(sockfd,LISTEN_QUEUE)) 40 { 41 perror("listen 失敗"); 42 exit(-1); 43 } 44 45 while(1) 46 { 47 pid_t pid; 48 struct sockaddr_in client_addr; 49 socklen_t length=sizeof(client_addr); 50 int clientfd=accept(sockfd,(struct sockaddr *)&client_addr,&length); 51 if(clientfd==-1) 52 { 53 perror("accept 失敗"); 54 continue; 55 } 56 else 57 { 58 printf("客戶端%s:%d鏈接成功\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port)); 59 pid=fork(); 60 if(pid<0) 61 { 62 perror("建立進程失敗"); 63 } 64 else if(pid==0)/*child*/ 65 { 66 char buffer[BUFFER_SIZE]; 67 int data_len; 68 FILE * fp=NULL; 69 bzero(buffer,BUFFER_SIZE); 70 if((fp=fopen("data","wb"))==NULL) 71 { 72 perror("文件打開失敗"); 73 exit(-1); 74 } 75 //循環接收數據 76 int size=0;//表示有多少個塊 77 while(data_len=recv(clientfd,buffer,BUFFER_SIZE,0))//data_len爲0時結束,是由於當客戶端沒有再發送數據過來時是接收0的,也表示該文件傳輸完畢了。 78 { 79 if(data_len<0) 80 { 81 perror("接收數據錯誤"); 82 exit(-1); 83 } 84 size++; 85 if(size==1) 86 { 87 printf("正在接收來自%s:%d的文件\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port)); 88 } 89 else 90 { 91 printf("."); 92 } 93 //向文件中寫入 94 int write_len=fwrite(buffer,sizeof(char),data_len,fp);//向文件中寫入,默認文件打開時會有一個文件指針進行寫入。若是是並行傳輸就要修改這個文件指針了,仍是有點麻煩的,若是是串行的,咱們都不用管了,多方便。 95 if(write_len>data_len) 96 { 97 perror("寫入數據錯誤"); 98 exit(-1); 99 } 100 bzero(buffer,BUFFER_SIZE); 101 } 102 if(size>0) 103 { 104 printf("\n%s:%d的文件傳送完畢\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port)); 105 } 106 else 107 { 108 printf("\n%s:%d的文件傳送失敗\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));//若是傳過來的文件大小爲0,也是會出現這個錯誤 109 } 110 fclose(fp); 111 //rename("data","asdf");//這裏能夠修改文件的名字。保存到服務器的話能夠隨便改個名字,防止文件名重複 112 exit(0); 113 } 114 else /*pather*/ 115 { 116 ; 117 } 118 } 119 close(clientfd); 120 } 121 122 return 0; 123 } 124 125 void print_time(char *ch) 126 { 127 time_t now; 128 struct tm * stm; 129 time(&now); 130 stm=localtime(&now); 131 sprintf(ch,"%02d:%02d:%02d\n",stm->tm_hour,stm->tm_min,stm->tm_sec); 132 return ; 133 }
兩部分的代碼都不難理解,關鍵的代碼就是那個while循環了。這個就不給截圖了,運行是沒有問題。指針
這篇杜鑫先生的回答很不錯,能夠看一下: http://www.zhihu.com/question/21591490code
本文地址: http://www.cnblogs.com/wunaozai/p/3886588.htmlserver