生活随笔
收集整理的這篇文章主要介紹了
socket编程实现文件传输功能
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
這節我們來完成?socket?文件傳輸程序,這是一個非常實用的例子。要實現的功能為:client 從 server 下載一個文件并保存到本地。
編寫這個程序需要注意兩個問題:
1) 文件大小不確定,有可能比緩沖區大很多,調用一次 write()/send() 函數不能完成文件內容的發送。接收數據時也會遇到同樣的情況。
要解決這個問題,可以使用 while 循環,例如:
//Server 代碼int nCount;while( (nCount = fread(buffer, 1, BUF_SIZE, fp)) > 0 ){send(sock, buffer, nCount, 0);}?//Client 代碼int nCount;while( (nCount = recv(clntSock, buffer, BUF_SIZE, 0)) > 0 ){fwrite(buffer, nCount, 1, fp);}
對于 Server 端的代碼,當讀取到文件末尾,fread() 會返回 0,結束循環。
對于 Client 端代碼,有一個關鍵的問題,就是文件傳輸完畢后讓 recv() 返回 0,結束 while 循環。
注意:讀取完緩沖區中的數據 recv() 并不會返回 0,而是被阻塞,直到緩沖區中再次有數據。
2) Client 端如何判斷文件接收完畢,也就是上面提到的問題——何時結束 while 循環。
最簡單的結束 while 循環的方法當然是文件接收完畢后讓 recv() 函數返回 0,那么,如何讓 recv() 返回 0 呢?recv() 返回 0 的唯一時機就是收到FIN包時。
FIN 包表示數據傳輸完畢,計算機收到 FIN 包后就知道對方不會再向自己傳輸數據,當調用 read()/recv() 函數時,如果緩沖區中沒有數據,就會返回 0,表示讀到了”socket文件的末尾“。
這里我們調用 shutdown() 來發送FIN包:server 端直接調用 close()/closesocket() 會使輸出緩沖區中的數據失效,文件內容很有可能沒有傳輸完畢連接就斷開了,而調用 shutdown() 會等待輸出緩沖區中的數據傳輸完畢。
本節以Windows為例演示文件傳輸功能,Linux與此類似,不再贅述。請看下面完整的代碼。
服務器端 server.cpp:
#include <stdio.h>#include <stdlib.h>#include <winsock2.h>#pragma comment (lib, "ws2_32.lib") //加載 ws2_32.dll?#define BUF_SIZE 1024?int main(){//先檢查文件是否存在char *filename = "D:\\send.avi"; //文件名FILE *fp = fopen(filename, "rb"); //以二進制方式打開文件if(fp == NULL){printf("Cannot open file, press any key to exit!\n");system("pause");exit(0);}?WSADATA wsaData;WSAStartup( MAKEWORD(2, 2), &wsaData);SOCKET servSock = socket(AF_INET, SOCK_STREAM, 0);?sockaddr_in sockAddr;memset(&sockAddr, 0, sizeof(sockAddr));sockAddr.sin_family = PF_INET;sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");sockAddr.sin_port = htons(1234);bind(servSock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));listen(servSock, 20);?SOCKADDR clntAddr;int nSize = sizeof(SOCKADDR);SOCKET clntSock = accept(servSock, (SOCKADDR*)&clntAddr, &nSize);?//循環發送數據,直到文件結尾char buffer[BUF_SIZE] = {0}; //緩沖區int nCount;while( (nCount = fread(buffer, 1, BUF_SIZE, fp)) > 0 ){send(clntSock, buffer, nCount, 0);}?shutdown(clntSock, SD_SEND); //文件讀取完畢,斷開輸出流,向客戶端發送FIN包recv(clntSock, buffer, BUF_SIZE, 0); //阻塞,等待客戶端接收完畢?fclose(fp);closesocket(clntSock);closesocket(servSock);WSACleanup();?system("pause");return 0;}
客戶端代碼:
#include <stdio.h>#include <stdlib.h>#include <WinSock2.h>#pragma comment(lib, "ws2_32.lib")?#define BUF_SIZE 1024?int main(){//先輸入文件名,看文件是否能創建成功char filename[100] = {0}; //文件名printf("Input filename to save: ");gets(filename);FILE *fp = fopen(filename, "wb"); //以二進制方式打開(創建)文件if(fp == NULL){printf("Cannot open file, press any key to exit!\n");system("pause");exit(0);}?WSADATA wsaData;WSAStartup(MAKEWORD(2, 2), &wsaData);SOCKET sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);?sockaddr_in sockAddr;memset(&sockAddr, 0, sizeof(sockAddr));sockAddr.sin_family = PF_INET;sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");sockAddr.sin_port = htons(1234);connect(sock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));?//循環接收數據,直到文件傳輸完畢char buffer[BUF_SIZE] = {0}; //文件緩沖區int nCount;while( (nCount = recv(sock, buffer, BUF_SIZE, 0)) > 0 ){fwrite(buffer, nCount, 1, fp);}puts("File transfer success!");?//文件接收完畢后直接關閉套接字,無需調用shutdown()fclose(fp);closesocket(sock);WSACleanup();system("pause");return 0;}
在D盤中準備好send.avi文件,先運行 server,再運行 client:
Input filename to save: D:\\recv.avi↙
//稍等片刻后
File transfer success!
打開D盤就可以看到 recv.avi,大小和 send.avi 相同,可以正常播放。
注意 server.cpp 第42行代碼,recv() 并沒有接收到 client 端的數據,當 client 端調用 closesocket() 后,server 端會收到FIN包,recv() 就會返回,后面的代碼繼續執行。
總結
以上是生活随笔為你收集整理的socket编程实现文件传输功能的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。