這節(jié)我們來完成 socket 文件傳輸程序呢岗,這是一個(gè)非常實(shí)用的例子。要實(shí)現(xiàn)的功能為:client 從 server 下載一個(gè)文件并保存到本地梧躺。
編寫這個(gè)程序需要注意兩個(gè)問題:
- 文件大小不確定惑申,有可能比緩沖區(qū)大很多,調(diào)用一次 write()/send() 函數(shù)不能完成文件內(nèi)容的發(fā)送藕筋。接收數(shù)據(jù)時(shí)也會(huì)遇到同樣的情況纵散。
要解決這個(gè)問題,可以使用 while 循環(huán)隐圾,例如:
//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);
}
對(duì)于 Server 端的代碼伍掀,當(dāng)讀取到文件末尾,fread() 會(huì)返回 0暇藏,結(jié)束循環(huán)蜜笤。
對(duì)于 Client 端代碼,有一個(gè)關(guān)鍵的問題盐碱,就是文件傳輸完畢后讓 recv() 返回 0把兔,結(jié)束 while 循環(huán)。
注意:讀取完緩沖區(qū)中的數(shù)據(jù) recv() 并不會(huì)返回 0瓮顽,而是被阻塞县好,直到緩沖區(qū)中再次有數(shù)據(jù)。
- Client 端如何判斷文件接收完畢暖混,也就是上面提到的問題——何時(shí)結(jié)束 while 循環(huán)缕贡。
最簡(jiǎn)單的結(jié)束 while 循環(huán)的方法當(dāng)然是文件接收完畢后讓 recv() 函數(shù)返回 0,那么拣播,如何讓 recv() 返回 0 呢晾咪?recv() 返回 0 的唯一時(shí)機(jī)就是收到FIN包時(shí)。
FIN 包表示數(shù)據(jù)傳輸完畢贮配,計(jì)算機(jī)收到 FIN 包后就知道對(duì)方不會(huì)再向自己傳輸數(shù)據(jù)轮洋,當(dāng)調(diào)用 read()/recv() 函數(shù)時(shí)惨好,如果緩沖區(qū)中沒有數(shù)據(jù),就會(huì)返回 0,表示讀到了”socket文件的末尾“兽间。
這里我們調(diào)用 shutdown() 來發(fā)送FIN包:server 端直接調(diào)用 close()/closesocket() 會(huì)使輸出緩沖區(qū)中的數(shù)據(jù)失效噩峦,文件內(nèi)容很有可能沒有傳輸完畢連接就斷開了笑窜,而調(diào)用 shutdown() 會(huì)等待輸出緩沖區(qū)中的數(shù)據(jù)傳輸完畢程储。
服務(wù)器端 server.cpp:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define BUF_SIZE 1024
int main()
{
char *ab = "abc.mp3";
FILE *fp = fopen(ab , "rb");
if (fp == NULL)
{
printf("Cannot open file, press any key to exit!\n");
system("pause");
exit(0);
}
int servSock = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in sockAddr;
memset(&sockAddr, 0, sizeof(sockaddr_in));
sockAddr.sin_family = PF_INET;
sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
sockAddr.sin_port = htons(1234);
bind(servSock, (struct sockaddr *)&sockAddr, sizeof(sockAddr));
listen(servSock, 20);
struct sockaddr_in clntAddr;
socklen_t nSize = sizeof(clntAddr);
int clntSock = accept(servSock, (struct sockaddr *)&clntAddr, &nSize);
//循環(huán)發(fā)送
char buffer[BUF_SIZE] = {0};
int nCount = 0;
while( (nCount = fread(buffer, 1, BUF_SIZE, fp)) > 0 )
{
write(clntSock, buffer, BUF_SIZE);
}
//文件讀取王弼,斷開輸出流辽剧,向客戶端發(fā)送FIN包
shutdown(clntSock, SHUT_RD);
//阻塞送淆,等待客戶端接收完畢
read(clntSock, buffer, BUF_SIZE);
fclose(fp);
close(clntSock);
close(servSock);
system("pause");
return 0;
}
客戶端代碼:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define BUF_SIZE 1024
int main(void)
{
char ab[100] = {0};
printf("Input filename to save: ");
scanf("%s", ab);
FILE *fp = fopen(ab, "wb");
if (fp == NULL)
{
printf("Cannot open file, press any key to exit!\n");
system("pause");
exit(0);
}
int sock = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP);
struct 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, (struct sockaddr *)&sockAddr, sizeof(sockAddr));
char buffer[BUF_SIZE] = {0};
int nCount = 0;
while( (nCount = read(sock, buffer, BUF_SIZE)) > 0 )
{
fwrite(buffer, nCount, 1, fp);
}
puts("File transfer success!");
fclose(fp);
close(sock);
system("pause");
return 0;
}
在D盤中準(zhǔn)備好send.avi文件,先運(yùn)行 server怕轿,再運(yùn)行 client:
Input filename to save: D:\recv.avi↙
//稍等片刻后
File transfer success!
打開D盤就可以看到 recv.avi偷崩,大小和 send.avi 相同辟拷,可以正常播放。
注意 server.cpp 第42行代碼阐斜,recv() 并沒有接收到 client 端的數(shù)據(jù)衫冻,當(dāng) client 端調(diào)用 closesocket() 后,server 端會(huì)收到FIN包谒出,recv() 就會(huì)返回隅俘,后面的代碼繼續(xù)執(zhí)行。