這節(jié)我們來完成 socket 文件傳輸程序,這是一個(gè)十分適用的例子。要完成的功用為:client 從 server 下載一個(gè)文件并保管到當(dāng)?shù)亍?br />編寫這個(gè)程序需求留意兩個(gè)成績:
1) 文件巨細(xì)不肯定,有能夠比緩沖區(qū)大許多,挪用一次 write()/send() 函數(shù)不克不及完成文件內(nèi)容的發(fā)送。接納數(shù)據(jù)時(shí)也會(huì)碰到異樣的狀況。
要處理這個(gè)成績,可以運(yùn)用 while 輪回,例如:
創(chuàng)新互聯(lián)建站專注于上城企業(yè)網(wǎng)站建設(shè),自適應(yīng)網(wǎng)站建設(shè),商城開發(fā)。上城網(wǎng)站建設(shè)公司,為上城等地區(qū)提供建站服務(wù)。全流程按需網(wǎng)站開發(fā),專業(yè)設(shè)計(jì),全程項(xiàng)目跟蹤,創(chuàng)新互聯(lián)建站專業(yè)和態(tài)度為您提供的服務(wù)
//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); }
關(guān)于 Server 端的代碼,當(dāng)讀取到文件末尾,fread() 會(huì)前往 0,完畢輪回。
關(guān)于 Client 端代碼,有一個(gè)癥結(jié)的成績,就是文件傳輸終了后讓 recv() 前往 0,完畢 while 輪回。
留意:讀取完緩沖區(qū)中的數(shù)據(jù) recv() 并不會(huì)前往 0,而是被壅塞,直到緩沖區(qū)中再次無數(shù)據(jù)。
2) Client 端若何判別文件接納終了,也就是下面提到的成績——何時(shí)完畢 while 輪回。
最復(fù)雜的完畢 while 輪回的辦法當(dāng)然是文件接納終了后讓 recv() 函數(shù)前往 0,那么,若何讓 recv() 前往 0 呢?recv() 前往 0 的獨(dú)一機(jī)遇就是收到FIN包時(shí)。
FIN 包表現(xiàn)數(shù)據(jù)傳輸終了,盤算機(jī)收到 FIN 包后就曉得對(duì)方不會(huì)再向本人傳輸數(shù)據(jù),當(dāng)挪用 read()/recv() 函數(shù)時(shí),假如緩沖區(qū)中沒無數(shù)據(jù),就會(huì)前往 0,表現(xiàn)讀到了”socket文件的末尾“。
這里我們挪用 shutdown() 來發(fā)送FIN包:server 端直接挪用 close()/closesocket() 會(huì)使輸入緩沖區(qū)中的數(shù)據(jù)生效,文件內(nèi)容很有能夠沒有傳輸終了銜接就斷開了,而挪用 shutdown() 會(huì)等候輸入緩沖區(qū)中的數(shù)據(jù)傳輸終了。
本節(jié)以Windows為例演示文件傳輸功用,Linux與此相似,不再贅述。請(qǐng)看下面完好的代碼。
效勞器端 server.cpp:
#include#include #include #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"); //以二進(jìn)制方法翻開文件 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); //輪回發(fā)送數(shù)據(jù),直到文件開頭 char buffer[BUF_SIZE] = {0}; //緩沖區(qū) int nCount; while( (nCount = fread(buffer, 1, BUF_SIZE, fp)) > 0 ){ send(clntSock, buffer, nCount, 0); } shutdown(clntSock, SD_SEND); //文件讀取終了,斷開輸入流,向客戶端發(fā)送FIN包 recv(clntSock, buffer, BUF_SIZE, 0); //壅塞,等候客戶端接納終了 fclose(fp); closesocket(clntSock); closesocket(servSock); WSACleanup(); system("pause"); return 0; }
客戶端代碼:
#include#include #include #pragma comment(lib, "ws2_32.lib") #define BUF_SIZE 1024 int main(){ //先輸出文件名,看文件能否能創(chuàng)立勝利 char filename[100] = {0}; //文件名 printf("Input filename to save: "); gets(filename); FILE *fp = fopen(filename, "wb"); //以二進(jìn)制方法翻開(創(chuàng)立)文件 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)); //輪回接納數(shù)據(jù),直到文件傳輸終了 char buffer[BUF_SIZE] = {0}; //文件緩沖區(qū) 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盤中預(yù)備好send.avi文件,先運(yùn)轉(zhuǎn) server,再運(yùn)轉(zhuǎn) client:
Input filename to save: D:\\recv.avi↙
//稍等少焉后
File transfer success!
翻開D盤就可以看到 recv.avi,巨細(xì)和 send.avi 相反,可以正常播放。
留意 server.cpp 第42行代碼,recv() 并沒有接納到 client 端的數(shù)據(jù),當(dāng) client 端挪用 closesocket() 后,server 端會(huì)收到FIN包,recv() 就會(huì)前往,前面的代碼持續(xù)履行。