[socket編程-一個(gè)文件傳輸?shù)睦覿

這節(jié)我們來完成 socket 文件傳輸程序呢岗,這是一個(gè)非常實(shí)用的例子。要實(shí)現(xiàn)的功能為:client 從 server 下載一個(gè)文件并保存到本地梧躺。

編寫這個(gè)程序需要注意兩個(gè)問題:

  1. 文件大小不確定惑申,有可能比緩沖區(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ù)。

  1. 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í)行。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末笤喳,一起剝皮案震驚了整個(gè)濱河市为居,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌杀狡,老刑警劉巖蒙畴,帶你破解...
    沈念sama閱讀 221,888評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異呜象,居然都是意外死亡膳凝,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門恭陡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蹬音,“玉大人,你說我怎么就攤上這事休玩∷畎恚” “怎么了?”我有些...
    開封第一講書人閱讀 168,386評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵哥捕,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我嘉熊,道長(zhǎng)遥赚,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,726評(píng)論 1 297
  • 正文 為了忘掉前任阐肤,我火速辦了婚禮凫佛,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘孕惜。我一直安慰自己愧薛,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,729評(píng)論 6 397
  • 文/花漫 我一把揭開白布衫画。 她就那樣靜靜地躺著毫炉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪削罩。 梳的紋絲不亂的頭發(fā)上瞄勾,一...
    開封第一講書人閱讀 52,337評(píng)論 1 310
  • 那天费奸,我揣著相機(jī)與錄音,去河邊找鬼进陡。 笑死愿阐,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的趾疚。 我是一名探鬼主播缨历,決...
    沈念sama閱讀 40,902評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼糙麦!你這毒婦竟也來了辛孵?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,807評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤喳资,失蹤者是張志新(化名)和其女友劉穎觉吭,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體仆邓,經(jīng)...
    沈念sama閱讀 46,349評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鲜滩,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,439評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了节值。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片徙硅。...
    茶點(diǎn)故事閱讀 40,567評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖搞疗,靈堂內(nèi)的尸體忽然破棺而出嗓蘑,到底是詐尸還是另有隱情,我是刑警寧澤匿乃,帶...
    沈念sama閱讀 36,242評(píng)論 5 350
  • 正文 年R本政府宣布桩皿,位于F島的核電站,受9級(jí)特大地震影響幢炸,放射性物質(zhì)發(fā)生泄漏泄隔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,933評(píng)論 3 334
  • 文/蒙蒙 一宛徊、第九天 我趴在偏房一處隱蔽的房頂上張望佛嬉。 院中可真熱鬧,春花似錦闸天、人聲如沸暖呕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽湾揽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間钝腺,已是汗流浹背抛姑。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評(píng)論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留艳狐,地道東北人定硝。 一個(gè)月前我還...
    沈念sama閱讀 48,995評(píng)論 3 377
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像毫目,于是被迫代替她去往敵國和親蔬啡。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,585評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • 1镀虐、TCP狀態(tài)linux查看tcp的狀態(tài)命令:1)箱蟆、netstat -nat 查看TCP各個(gè)狀態(tài)的數(shù)量2)、lso...
    北辰青閱讀 9,437評(píng)論 0 11
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理刮便,服務(wù)發(fā)現(xiàn)空猜,斷路器,智...
    卡卡羅2017閱讀 134,702評(píng)論 18 139
  • socket通信原理 socket又被叫做套接字,它就像連接到兩端的插座孔一樣,通過建立管道恨旱,將兩個(gè)不同的進(jìn)程之間...
    jiodg45閱讀 1,144評(píng)論 0 1
  • ① 記得我們是『2012』相識(shí)時(shí)間感覺過的好快辈毯,可是我永遠(yuǎn)記得你是我是一生一世,最愛過得人搜贤,喜歡過谆沃,還有恨過,聽說...
    徐可寶閱讀 144評(píng)論 0 0
  • 家里要請(qǐng)一個(gè)阿姨來幫忙,好朋友介紹了自己熟悉的一個(gè)人掂名,我非常珍惜這份緣分据沈,我覺得在自己的家附近找一個(gè)知根知底的人來...
    長(zhǎng)青竹ing閱讀 171評(píng)論 0 0