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#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"); //以二进制方法翻开文件 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#include #include #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() 就会前往,前面的代码持续履行。