c++ 網(wǎng)絡(luò)文件傳輸_第1頁
c++ 網(wǎng)絡(luò)文件傳輸_第2頁
c++ 網(wǎng)絡(luò)文件傳輸_第3頁
c++ 網(wǎng)絡(luò)文件傳輸_第4頁
c++ 網(wǎng)絡(luò)文件傳輸_第5頁
已閱讀5頁,還剩19頁未讀 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡介

1、建議讀者再看實(shí)驗(yàn)步驟之前,先閱讀一下文章末尾的【注意事項(xiàng)】一節(jié)。這將有助于更好的理解本章的實(shí)現(xiàn)。一單線程文件傳輸 (I):·服務(wù)器端(負(fù)責(zé)發(fā)送數(shù)據(jù))的實(shí)現(xiàn)1 建立一個(gè)基于對(duì)話框的工程Server,并在建立的過程中選擇支持windows socket。2 在對(duì)話框上添加“發(fā)送”按鈕。3 為“發(fā)送”按鈕添加事件BN_CLICKED的響應(yīng)函數(shù)OnSend()。void CServerDlg:OnSend() / TODO: Add your control notification handler code hereCFileDialog fd(TRUE); / CFileDialog是M

2、FC提供的一個(gè)用于選擇文件的對(duì)話框類CString filename;char fn40;CSocket listenSocket, socketSend;CFile file;long FileLength;char* data;if(IDOK=fd.DoModal() / 啟動(dòng)用于選擇文件的對(duì)話框/選擇了文件filename=fd.GetFileName(); / 獲取用戶選擇的文件的文件名if(!file.Open(filename.GetBuffer(0),CFile:modeRead| File:typeBinary)AfxMessageBox(" 打開文件錯(cuò)誤,取消發(fā)送!

3、");return;strcpy(fn,filename.GetBuffer(0);else return; /按了取消按鈕listenSocket.Create(7000,SOCK_STREAM); listenSocket.Listen(5); listenSocket.Accept(socketSend); FileLength = file.GetLength(); / 獲取文件的長度socketSend.Send(&FileLength, 4); / 把要發(fā)送的文件的長度傳送給對(duì)方socketSend.Send(fn,40); / 發(fā)送要傳送的文件的文件名data

4、= new charFileLength; /分配一塊和要傳輸?shù)奈募粯哟笮〉膬?nèi)存空間file.ReadHuge(data, FileLength); /把文件中所有的數(shù)據(jù)一次性讀入datasocketSend.Send(data, FileLength); /把data中的數(shù)據(jù)都發(fā)送出去file.Close();delete data;socketSend.Close();·客戶端(負(fù)責(zé)接收數(shù)據(jù))的實(shí)現(xiàn)1 建立一個(gè)基于對(duì)話框的工程,并在建立的過程中選擇支持windows socket。(為了能夠利用Server端的代碼,在程序編寫時(shí),可以復(fù)制Server的代碼到Client目錄,并

5、在Server的基礎(chǔ)上修改或添加代碼)2 在對(duì)話框上添加“接收”按鈕。3 為“發(fā)送”按鈕添加事件BN_CLICKED的響應(yīng)函數(shù)OnReceive ()。void CServerDlg:OnReceive() / TODO: Add your control notification handler code hereCSocket socketReceive;CFile file;long FileLength;char * data;char fn40;socketReceive.Create();socketReceive.Connect("127.0.0.1", 70

6、00); /這里為了測(cè)試的方便,我們使用127.0.0.1本地回路IPsocketReceive.Receive(&FileLength, 4); /獲取要接受的文件的長度socketReceive.Receive(fn, 40); /獲取要接受的文件的文件名data = new charFileLength; socketReceive.Receive(data, FileLength); /獲取要接受的文件內(nèi)容file.Open(fn,CFile:modeCreate|CFile:modeWrite | CFile:typeBinary); /在當(dāng)前目錄建立文件file.Write

7、Huge(data, FileLength); file.Close();delete data;socketReceive.Close();AfxMessageBox("接收文件成功");上面的程序以最簡單的方式實(shí)現(xiàn)了文件傳輸功能。但正是因?yàn)樗暮唵?,所以忽略了很多重要的東西。讀者讀到這里的時(shí)候可以考慮一下可能存在著些什么問題。在這里給出一些上面程序的不足:1 在本書的原理部分曾經(jīng)提到阻塞式和非阻塞式兩種套接字的工作方式。在上面的程序中使用的CSocket類提供了阻塞式的服務(wù),這令編寫程序非常方便。然而這卻不利于程序的友好性和可用性服務(wù)器端在沒有獲得客戶端連接的時(shí)候固執(zhí)的

8、偵聽,一點(diǎn)也不理會(huì)用戶的命令。對(duì)話框不再響應(yīng)消息,用戶只能用強(qiáng)制關(guān)閉來結(jié)束程序。2 希望一次性地動(dòng)態(tài)分配整個(gè)文件大小的堆存貯區(qū)作為讀入數(shù)據(jù)的內(nèi)存空間。這個(gè)空間一方面受到堆大小的限制,另一方面也受到CFile類成員函數(shù)ReadHuge()和WriteHuge()的讀寫能力限制,還有Receive()和Send()函數(shù)一次能夠發(fā)送的數(shù)據(jù)也以其參數(shù)的最大值為限,所以在遇到大中型文件的時(shí)候,系統(tǒng)將不能滿足程序提出的動(dòng)態(tài)分配大塊內(nèi)存的要求,這樣傳輸便不得不終止。3 在實(shí)際的網(wǎng)絡(luò)傳輸中,網(wǎng)絡(luò)情況是十分多變和復(fù)雜的。通過CSocket的成員函數(shù)們的返回值可以了解傳輸?shù)臓顟B(tài),然而在上面的程序中卻沒有使用這些異

9、常控制,帶來的直接后果就是當(dāng)遇到網(wǎng)絡(luò)擁塞或?qū)Ψ絺魉偷臄?shù)據(jù)暫時(shí)未到時(shí),程序就會(huì)認(rèn)為傳輸結(jié)束而出乎意料的終止。4 在上面的程序中,程序沒有傳送文件的屬性數(shù)據(jù),而只通過套接字傳送文件數(shù)據(jù)。在實(shí)際應(yīng)用中這種假設(shè)通常是不存在的,所以應(yīng)當(dāng)把文件屬性也傳給對(duì)方,以達(dá)到文件的完全復(fù)制。改進(jìn)方法:1 使用CAsyncSocket實(shí)現(xiàn)異步套接字,避免阻塞2 化整為零,把文件分若干次讀入并發(fā)送3 在程序中加入異??刂普Z句已應(yīng)付網(wǎng)絡(luò)多變的情況4 在傳送文件數(shù)據(jù)之前獲取文件屬性值傳給對(duì)方下面將給出基于上述改進(jìn)方案的前三點(diǎn)而重新編寫的程序,對(duì)于第四點(diǎn),有興趣的讀者可以自己實(shí)現(xiàn)。二單線程文件傳輸(II):·服務(wù)器

10、端(負(fù)責(zé)發(fā)送數(shù)據(jù))的實(shí)現(xiàn)1 建立一個(gè)基于對(duì)話框的工程Server,并在建立的過程中選擇支持windows socket。2 在對(duì)話框上添加“偵聽”,“發(fā)送”按鈕。3 用Class Wizard添加一個(gè)派生于CAsyncSocket的類CMySocket。4 添加CMySocket類的全局變量listenSocket用于偵聽。添加CMySocket類全局變量的位置,最宜在MySocket.cpp的頭部。如下:/ MySocket.cpp : implementation file/#include "stdafx.h"#include "Server.h"

11、#include "MySocket.h"#ifdef _DEBUG#define new DEBUG_NEW#undef THIS_FILEstatic char THIS_FILE = _FILE_;#endif/ CMySocketCMySocket listenSocket;CMySocket:CMySocket()CMySocket:CMySocket()本章中以后所有的CMySocket型全局變量都應(yīng)仿效本段程序添加。5 添加CMySocket類的全局變量sendSocket,用于傳輸數(shù)據(jù)6 為“發(fā)送”按鈕添加事件BN_CLICKED的響應(yīng)函數(shù)OnSend()d

12、efine ReadSize 500void CServerDlg :OnSend()/ TODO: Add your control notification handler code hereCFile file;char dataReadSize; / 用于存放讀入的文件數(shù)據(jù)塊long ByteSended=0, FileLength,count;CFileDialog fd(TRUE);CString filename;char fn40;if(IDOK=fd.DoModal() / 啟動(dòng)用于選擇文件的對(duì)話框/選擇了文件filename=fd.GetFileName(); /獲取選擇的

13、文件的文件名if(!file.Open(filename.GetBuffer(0),CFile:modeRead|CFile:typeBinary)AfxMessageBox("打開文件錯(cuò)誤,取消發(fā)送!");return;strcpy(fn,filename.GetBuffer(0);else return; /按了取消按鈕FileLength=file.GetLength();sendSocket.Send(&FileLength,sizeof(long);sendSocket.Send(fn,40);memset(data,0,sizeof(data);do/

14、從文件讀取數(shù)據(jù),每次最多讀入ReadSize個(gè)字節(jié)。count表示實(shí)際讀入的字節(jié)數(shù)count=file.ReadHuge(data, ReadSize);/ 發(fā)送數(shù)據(jù)while(SOCKET_ERROR=sendSocket.Send(data,count)/ 統(tǒng)計(jì)已發(fā)送的字節(jié)數(shù)ByteSended =ByteSended+count;while(ByteSended <FileLength); file.Close();7 為“偵聽”按鈕添加事件BN_CLICKED的響應(yīng)函數(shù)OnListen ()void CServerDlg:Listen()/ TODO: Add your cont

15、rol notification handler code herelistenSocket.Create(7000);listenSocket.Listen(); 8 為CMySocket類添加消息OnAccpet響應(yīng)函數(shù)OnAccept()void CMySocket:OnAccept(int nErrorCode) / TODO: Add your specialized code here and/or call the base classif(!listenSocket.Accept(sendSocket)AfxMessageBox("連接失敗");return

16、;AfxMessageBox("連接成功");CAsyncSocket:OnAccept(nErrorCode); ·客戶端(負(fù)責(zé)接收數(shù)據(jù))的實(shí)現(xiàn)1 建立一個(gè)基于對(duì)話框的工程,并在建立的過程中選擇支持windows socket。2 添加CMySocket類的全局變量receiveSocket,用于接收數(shù)據(jù)3 在對(duì)話框上添加“連接”和“接收”按鈕。4 為“接收”按鈕添加事件BN_CLICKED的響應(yīng)函數(shù)OnReceive()define ReadSize 500void CServerDlg:OnReceive()/ TODO: Add your control

17、notification handler code hereCFile file;char dataReadSize;long FileLength;long WriteOnce;long WriteCount = 0;char fn40;/ 接收文件長度while(SOCKET_ERROR =receiveSocket. Receive(&FileLength, sizeof(long)/ 接收文件名while(SOCKET_ERROR =receiveSocket. Receive(fn, 40) if(!file.Open(fn,CFile:modeCreate|CFile:mo

18、deWrite)AfxMessageBox("Create file error!");return;doWriteOnce =receiveSocket.Receive(data,ReadSize);if(WriteOnce =SOCKET_ERROR)continue;/ 統(tǒng)計(jì)已接受的字節(jié)數(shù)WriteCount=WriteCount+WriteOnce;/ 把收到的數(shù)據(jù)寫入文件file.WriteHuge(data,WriteOnce);while(WriteCount< FileLength);file.Close();AfxMessageBox("接

19、收文件成功");5 為“連接”按鈕添加事件BN_CLICKED的響應(yīng)函數(shù)OnConnecting()void CServerDlg:OnConnecting() / TODO: Add your control notification handler code herereceiveSocket.Create();receiveSocket.Connect("127.0.0.1",7000);看過了上面的程序后,是不是有個(gè)疑問為什么在發(fā)送端發(fā)送了固定數(shù)量的數(shù)據(jù)(ReadSize個(gè)字符),而在接受方WriteOnce =receiveSocket.Receive(

20、data,ReadSize);語句后的已接收字符統(tǒng)計(jì)時(shí)并沒有直接加ReadSize,而是加上了Receive()函數(shù)的返回值WriteOnce?請(qǐng)看下面的一個(gè)小實(shí)驗(yàn):發(fā)送方一次發(fā)送10000字節(jié)的數(shù)據(jù),而在接受方用下面的代碼接受數(shù)據(jù):CString str;int received=0, total=0;char data10000;while(total<10000)while(SOCKET_ERROR=(received= receiveSocket.Receive(data,10000)str.Format(“received=%d”,received);total=total+r

21、eceived;AfxMessageBox(str);str.Format(“total=%d”,total);AfxMessageBox(str);每次運(yùn)行實(shí)驗(yàn)程序后,變量total的值是不變的,它在兩重循環(huán)都結(jié)束后總保持值為10000。但每次實(shí)驗(yàn)彈出的對(duì)話框顯示的received的值總是變化的。一種可能的情況是,三次彈出顯示received值的對(duì)話框,每次的顯示依次為received =1987 , received= 4012 , received= 4001。這說明我們的SOCK_STREAM型的套接字總能保證數(shù)據(jù)的完整傳輸,但它并不能保證數(shù)據(jù)一次性的到達(dá)。實(shí)際上,在網(wǎng)絡(luò)參考模型的結(jié)構(gòu)

22、中socket處于傳輸層。在傳輸層發(fā)送的數(shù)據(jù)需要經(jīng)過底下多個(gè)層次的分解和打包,最后才達(dá)到的物理層(過程中的分段和打包的操作對(duì)于程序員是不可見的)。socket連接基于TCP協(xié)議,而TCP連接是字節(jié)流而非報(bào)文流(報(bào)文的邊界并不按頭尾銜接的方式保存)。例如,如果發(fā)送進(jìn)程將4塊512字節(jié)的數(shù)據(jù)寫到TCP流上,那么這些數(shù)據(jù)可能按4個(gè)512字節(jié)的順序或2個(gè)1024的順序或者1個(gè)2048字節(jié)的數(shù)據(jù)塊,或者是其他一些方式傳送到接收進(jìn)程的。接收方無法測(cè)出這些數(shù)據(jù)是以哪種單位寫入的。用Windows socket創(chuàng)建的每個(gè)套接字都有一塊屬于自己的緩沖區(qū)(Windows下默認(rèn)的socket發(fā)送和接收緩沖區(qū)大小均是

23、4096字節(jié),要想改變或獲取它的大小可使用函數(shù)setsockopt()和getsockopt()<詳見MSDN>)??刂苨ocket緩沖區(qū)的大小可以優(yōu)化網(wǎng)絡(luò)程序的傳輸性能,但同時(shí)也為我們帶來了一個(gè)問題:當(dāng)一個(gè)應(yīng)用程序把數(shù)據(jù)送給TCP實(shí)體時(shí),TCP根據(jù)自己的判斷,可能會(huì)立刻將其發(fā)送出去或?qū)⑵渚彺嫫饋恚榱耸占^大量的數(shù)據(jù),然后發(fā)送)。然而,有時(shí)候應(yīng)用程序很想將數(shù)據(jù)立即發(fā)送出去。例如,假設(shè)一個(gè)用戶登錄到了遠(yuǎn)端的機(jī)器上,他輸入一條命令并按下回車鍵之后,該命令行應(yīng)該立刻送往遠(yuǎn)端機(jī)器而不是暫存起來直到用戶輸入了下一條命令后再發(fā)送。為了強(qiáng)制立即發(fā)送數(shù)據(jù),應(yīng)用程序可以使用PUSH標(biāo)志,通知TCP

24、不能耽擱數(shù)據(jù)的發(fā)送。一些早期的應(yīng)用程序使用PUSH標(biāo)志作為一種記號(hào)來區(qū)分出報(bào)文的邊界。這種方法有時(shí)可以奏效,但有時(shí)也會(huì)失敗,因?yàn)椴⒎撬械腡CP實(shí)現(xiàn)都將PUSH標(biāo)志傳送到接收方的應(yīng)用程序。而且,如果在每個(gè)PUSH標(biāo)志發(fā)送前又有PUSH標(biāo)志輸入進(jìn)來(例如由于輸出線路很忙),那么TCP將會(huì)隨意地將所有帶PUSH標(biāo)志的數(shù)據(jù)聚集成一個(gè)IP數(shù)據(jù)報(bào)。在這些各種各樣的數(shù)據(jù)片之間不再加以區(qū)分(其實(shí),UNIX系統(tǒng)中的文件也有這種特性。讀取文件時(shí)無法分清文件是一次寫一個(gè)塊、一個(gè)字節(jié)還是一次全部寫入。如同對(duì)待UNIX文件一樣,TCP軟件不知道字節(jié)表示什么,并且不無疑去弄清楚,字節(jié)就是字節(jié)。) Andrew S.Ta

25、nenbaum 著計(jì)算機(jī)網(wǎng)絡(luò)(第三版)P403為了解決這個(gè)問題,有時(shí)不得不編寫程序,統(tǒng)計(jì)收到的字符數(shù),然后,根據(jù)再已知的數(shù)據(jù)段的長度來區(qū)分每個(gè)數(shù)據(jù)段。下面,讓來看一看如何在客戶端區(qū)分已知長度的數(shù)據(jù)段。(假設(shè)已知數(shù)據(jù)段的長度是2,并且以作為數(shù)據(jù)發(fā)送結(jié)束的標(biāo)志)服務(wù)器端:char data100="0123456789#"CSocket socketListen, socketSend;socketListen.Create(7000);socketListen.Listen();socketListen.Accept(socketSend);socketSend.Send(d

26、ata,strlen(data); / 服務(wù)器端一次把所有的數(shù)據(jù)發(fā)送出去socketListen.Close();socketSend.Close();客戶端:char buffer1; / 用以儲(chǔ)存每次收到的一個(gè)字符char temp100; / 用以儲(chǔ)存正在合并中的一個(gè)數(shù)據(jù)段int i=0;CSocket socketReceive;socketReceive.Create();socketReceive.Connect("127.0.0.1",7000);memset(temp,0,sizeof(temp);dowhile(SOCKET_ERROR=socketRec

27、eive.Receive(buffer,1); / 一次接收一個(gè)字符tempi+=buffer0; / 把接收到的字符加入正在形成的數(shù)據(jù)段的尾部if(i=2) / 如果收到的字符數(shù)等于已知數(shù)據(jù)段的長CString str;str.Format("收到兩個(gè)字節(jié) %s",temp);AfxMessageBox(str); / 顯示這個(gè)數(shù)據(jù)段memset(temp,0,sizeof(temp); / 清空這個(gè)數(shù)據(jù)段,為接收下一個(gè)數(shù)據(jù)段作準(zhǔn)備i=0;while(buffer0!='#'); / 判斷接收到的是否是結(jié)束標(biāo)志socketReceive.Close();三

28、多線程文件傳輸如果仔細(xì)地統(tǒng)計(jì)單線程傳輸文件的速度,便會(huì)發(fā)現(xiàn)即使在像湖南大學(xué)這種高速的校園網(wǎng)內(nèi),傳輸?shù)乃俾室膊粫?huì)超過400KB/S。然而,在用下載工具(比如:netants,flashfxp等)下載資料時(shí),時(shí)??梢钥吹酱笥?00KB/S的速度。疑問由此產(chǎn)生難道這些軟件采用了“五鬼搬運(yùn)術(shù)”?其實(shí),當(dāng)探究過其中的原理之后,便會(huì)驚奇的發(fā)現(xiàn)原來這些軟件真的使用了具有神話色彩的“五鬼搬運(yùn)術(shù)”,而驅(qū)使這些“鬼”來搬運(yùn)數(shù)據(jù)的正是多線程技術(shù)!多線程技術(shù)在網(wǎng)絡(luò)程序設(shè)計(jì)方面有著很大的作用,它不僅能提高程序的發(fā)送能力,而且極大地方便了程序員設(shè)計(jì)編寫文件服務(wù)器程序(同時(shí)處理多個(gè)連接)。但是,多線程程序設(shè)計(jì)有一個(gè)很大的問

29、題就是要涉及到線程的同步操作,這確實(shí)是一件令人頭痛的事情,但為了實(shí)現(xiàn)高性能的傳輸程序,也不得不把它弄個(gè)清楚(后文將介紹一些線程技術(shù),至少在文件傳輸方面會(huì)涉及到的一些知識(shí))。在MFC中,啟動(dòng)線程有多種方法:AfxBeginThread(),CreateThread(),還有就是process.h中定義的_beginthread()。傳輸文件時(shí),不僅應(yīng)使用多個(gè)線程傳輸文件數(shù)據(jù),另外,最好啟動(dòng)一個(gè)線程監(jiān)視文件傳輸?shù)臓顟B(tài)(傳輸進(jìn)度等),以便作一些必要的操作,比如:接收方在接收完成時(shí),合并每個(gè)線程接收的數(shù)據(jù)文件;又如:即時(shí)顯示傳輸進(jìn)度或計(jì)算傳輸速率。操作系統(tǒng)課程講述了線程PV同步原語,鎖和條件變量。在M

30、FC中,用CEvent等類來控制線程同步。CEvent和條件變量比較像,它的成員函數(shù)SetEvent()用來把這個(gè)事件對(duì)象設(shè)置為有信號(hào)的,ResetEvent()函數(shù)用來把這個(gè)事件對(duì)象設(shè)置為無信號(hào)的,可配套使用的函數(shù)WaitForSingleObject用來掛起線程直到事件對(duì)象從無信號(hào)的變?yōu)橛行盘?hào)的。向線程傳遞數(shù)據(jù)可以通過下面多種方式實(shí)現(xiàn),以下的兩種方式相對(duì)簡便:1. 以函數(shù)值參數(shù)方式,在創(chuàng)建線程時(shí)傳入;這種方式十分可靠,因?yàn)槊總€(gè)函數(shù)值參數(shù)(都會(huì)在函數(shù)體內(nèi)保存一個(gè)副本,不會(huì)受其他線程修改變量的影響),但是只能在創(chuàng)建線程的時(shí)候傳入,缺乏靈活性;2. 使用全局變量。使用全局變量要用CMutex類的

31、鎖機(jī)制保護(hù)好,否則會(huì)導(dǎo)致線程讀入數(shù)據(jù)的錯(cuò)誤。 下面的多線程程序?qū)⑹褂玫谝环N方式為每個(gè)線程“工人”分配工作編號(hào)(idx)。在發(fā)送端程序中,多個(gè)線程同時(shí)讀一個(gè)欲發(fā)送的文件。特別需要注意的是,如果不在打開文件時(shí)作一些特別的設(shè)置,雖然第一個(gè)程序可以正常的打開文件,但之后的程序?qū)⒁驗(yàn)榈谝粋€(gè)程序?qū)ξ募摹芭潘x”行為而打開文件失敗。因此所有的線程打開文件時(shí)都應(yīng)該設(shè)置共享讀寫屬性 CFile:shareDenyNone。設(shè)置方法如下:CFile file;File.Open(“abc.txt”,CFile:modeRead|CFile:shareDenyNone);上文曾把線程比作工人,它們把數(shù)據(jù)從一臺(tái)計(jì)算

32、機(jī)搬到另一臺(tái)計(jì)算機(jī)。為了使它們能完成這項(xiàng)任務(wù),必須教會(huì)它們同樣的搬運(yùn)技術(shù),也就是說,需要為它們編寫幾乎相同的工作代碼。然而,畢竟程序中的每個(gè)線程要負(fù)責(zé)一個(gè)文件中不同的數(shù)據(jù)段,所以它們還是有一些小小的區(qū)別,比如:它們必須知道各自負(fù)責(zé)搬動(dòng)的物品放在哪里(源文件的哪一段)和搬到哪里去(存放在接受方的什么文件中)。另外,要管理好這些工人并為它們分配工作也是一件重要的任務(wù),所以派一個(gè)工頭(也是一個(gè)線程)來完成這項(xiàng)任務(wù)是必不可少的不同的,但與現(xiàn)實(shí)不同的是,下面程序中的每個(gè)工頭手下只有一個(gè)工人。使用n個(gè)線程傳輸文件時(shí),應(yīng)當(dāng)把文件分為大小類似的n塊,每個(gè)線程負(fù)責(zé)其中的一塊。在客戶端,讓每個(gè)接收線程(工人)把它

33、們收到的數(shù)據(jù)保存為一個(gè)獨(dú)立的文件,當(dāng)所有的線程都工作結(jié)束時(shí),再把這些文件合并起來。·服務(wù)器端(負(fù)責(zé)發(fā)送數(shù)據(jù))的實(shí)現(xiàn)1 一個(gè)基于對(duì)話框的工程Server,并在建立的過程中選擇支持windows socket。2 在對(duì)話框上添加“偵聽”和“發(fā)送”按鈕。3 用ClassWizard添加一個(gè)派生于CAsyncSocket的類CMySocket4 定義CMySocket型的全局變量listenSocket和數(shù)組sendSockets5 /listenSocket用于偵聽,sendSockets中的5個(gè)套接字分別被五個(gè)線程用來收發(fā)數(shù)據(jù)下面的程序總共啟動(dòng)5個(gè)線程用來傳輸文件數(shù)據(jù)。每個(gè)線程根據(jù)自己的

34、編號(hào)idx使用套接字?jǐn)?shù)組sendSocket5中相應(yīng)的套接字傳送數(shù)據(jù)。注意,在Create這個(gè)套接字?jǐn)?shù)組的時(shí)候,完全可以讓它們共享一個(gè)端口號(hào)。使用CAsyncSocket類時(shí),我們無需做更多的設(shè)置就可以使用這項(xiàng)技術(shù)。5 雙擊“偵聽”按鈕,為它添加事件BN_CLICKED的響應(yīng)函數(shù) OnListen()void CServerDlg :OnListen()/ TODO: Add your control notification handler code herelistenSocket.Create(7000);listenSocket.Listen();6 為CMySocket類添加消息On

35、Accpect的響應(yīng)函數(shù)OnAccept()void CMySocket:OnAccept(int nErrorCode) / TODO: Add your specialized code here and/or call the base classstatic int File_Socket_Accepted=0;if(!listenSocket.Accept(sendSocketsFile_Socket_Accepted) / 接收對(duì)方套接字的連接AfxMessageBox("接收連接失敗!");return;sendSocketsFile_Socket_Accep

36、ted.AsyncSelect(FD_READ|FD_WRITE|FD_CLOSE);File_Socket_Accepted+; / 已接收的連接數(shù)加一if(File_Socket_Accepted=5) / 如果所有的連接已經(jīng)都接受了AfxMessageBox("連接成功");listenSocket.Close();CAsyncSocket:OnAccept(nErrorCode);7 編寫為線程分配要傳輸?shù)臄?shù)據(jù)塊的全局函數(shù)GetBeginPos()/獲取線程負(fù)責(zé)的文件數(shù)據(jù)塊起始位置及大小,TotalThreads為啟動(dòng)傳輸線程的總數(shù),ThreadIndex為線程的編

37、號(hào),BgPos為計(jì)算得到的數(shù)據(jù)塊相對(duì)于文件頭的偏移量,BlkSize為數(shù)據(jù)塊的大小void GetBeginPos(int TotalThreads,int ThreadIndex/*from 1*/,long file_length, long &BgPos, long &BlkSize)long BlockSize, lastBlockSize, BeginPos;int i;BlockSize=file_length/TotalThreads;lastBlockSize=file_length;BeginPos=0;for(i=1;i<=ThreadIndex-1;

38、i+)lastBlockSize=lastBlockSize-BlockSize;BeginPos=BeginPos+BlockSize;if (ThreadIndex=TotalThreads)BgPos=BeginPos;BlkSize=lastBlockSize;elseBgPos=BeginPos;BlkSize=BlockSize;8 添加全局變量CString filename; 和 char fn40;用以儲(chǔ)存被發(fā)送的文件的文件名把它們添加到ServerDlg.cpp文件的頭部。如下:/ ServerDlg.cpp : implementation file/#include &

39、quot;stdafx.h"#include "Server.h"#include "ServerDlg.h"#include "MySocket.h"#include "process.h"#ifdef _DEBUG#define new DEBUG_NEW#undef THIS_FILEstatic char THIS_FILE = _FILE_;#endif/ CAboutDlg dialog used for App AboutCString filename;char fn40;9 雙擊“發(fā)送”

40、按鈕,為它添加事件BN_CLICKED的響應(yīng)函數(shù)OnSend()注意:必須在使用_beingthread()和_endthread()函數(shù)的.cpp文件的頭部加上#include “process.h”void CServerDlg :OnSend ()CFileDialog fd(TRUE);if(IDOK=fd.DoModal() / 啟動(dòng)用于選擇文件的對(duì)話框/選擇了文件filename=fd.GetFileName(); /獲取選擇的文件的文件名strcpy(fn,filename.GetBuffer(0);else return; /按了取消按鈕for(int i=0;i<5;i

41、+)_beginthread(SendThreadFunction,0,(void *)i); /啟動(dòng)工頭線程(由工頭線程啟動(dòng)工人線程)用傳遞函數(shù)參數(shù)的方式為每個(gè)線程指定序號(hào),是因?yàn)檫@樣可以簡化程序,避免加入代碼處理因使用全局變量而帶來的線程同步問題。這些代碼的作用是,驅(qū)動(dòng)工頭們到它們的崗位上(每個(gè)工頭管理一個(gè)工人數(shù)據(jù)傳送線程)。10 編寫SendThreadFunction函數(shù)這個(gè)函數(shù)起到了工頭的作用,通過參數(shù)(void*)i為每個(gè)工人線程編號(hào)和分配任務(wù)。void SendThreadFunction(void * pParam)int idx=(int)pParam;SendThread(

42、idx+1); / 啟動(dòng)工人線程_endthread();11 編寫真正實(shí)現(xiàn)數(shù)據(jù)發(fā)送的函數(shù)SendThreaddefine ReadSize 500void SendThread(int idx) / 傳輸文件數(shù)據(jù)的函數(shù)(搬運(yùn)工人)CFile file;char dataReadSize;long BeginPos, Size;long FileLength;long ReadOnce, LeftToRead, count;if(!file.Open(fn,CFile:modeRead|CFile:shareDenyNone)AfxMessageBox("read file erro

43、r!");return;FileLength=file.GetLength();sendSocketsidx-1. Send(&FileLength, 4);sendSocketsidx-1. Send(fn, 40);/ 獲取本線程傳輸任務(wù)(傳送塊的大小和起始位置)GetBeginPos(5 , idx, FileLength ,BeginPos, Size); /其中的5表示總共有5個(gè)線程,idx表示本線程編號(hào)file.Seek(BeginPos, CFile:begin); /每個(gè)線程函數(shù)找到自己任務(wù)的起始點(diǎn)LeftToRead=Size;while(LeftToRea

44、d>0)ReadOnce=(LeftToRead>ReadSize)?ReadSize:LeftToRead;count=file.ReadHuge(data,ReadOnce);while(SOCKET_ERROR= sendSocketsidx-1.Send(data,count)LeftToRead=LeftToRead-count; file.Close();·客戶端(負(fù)責(zé)接收數(shù)據(jù))的實(shí)現(xiàn)1 一個(gè)基于對(duì)話框的工程,并在建立的過程中選擇支持windows socket。2 在對(duì)話框上添加“連接”按鈕和“接收”按鈕。3 用ClassWizard添加一個(gè)派生于CAsyn

45、cSocket的類CMySocket4 定義全局句柄數(shù)組HANDLE hEvent5;5 定義CMySocket型的全局變量listenSocket和數(shù)組receiveSockets56 定義全局變量char fn40;用以儲(chǔ)存接收文件的文件名7 為CMySocket類添加消息OnConnect的響應(yīng)函數(shù)OnConnect()void CMySocket:OnConnect(int nErrorCode) / TODO: Add your specialized code here and/or call the base classstatic int File_Socket_Connect

46、ed=0;if(nErrorCode=0)File_Socket_Connected+;this->AsyncSelect(FD_READ|FD_WRITE|FD_CLOSE);if(File_Socket_Connected=5)File_Socket_Connected=0;AfxMessageBox("連接成功");CAsyncSocket:OnConnect(nErrorCode);8 為“連接”按鈕添加事件BN_CLICKED的響應(yīng)函數(shù)OnConnect()void CServerDlg:OnConnect()for(int i=0;i<5;i+) r

47、eceiveSocketsi.Create();receiveSocketsi.Connect(“127.0.0.1”,7000);9 為“接收”按鈕添加事件BN_CLICKED的響應(yīng)函數(shù)OnReceive()void CServerDlg:OnReceive ()/以下for循環(huán)的作用是初始化事件對(duì)象為無信號(hào)的,用于監(jiān)視線程識(shí)別文件傳輸是否結(jié)束for(int i=0;i<5;i+)if(hEventi=CreateEvent(NULL,false,false,NULL)=NULL)AfxMessageBox("Create hE-Event Handle Failed&quo

48、t;);ResetEvent(hEventi);_beginthread(ReceiveNotifyFunction,0,NULL);for(i=0;i<5;i+)_beginthread(ReceiveThreadFunction,0,(void *)i);10 編寫ReceiveThreadFunction函數(shù)void ReceiveThreadFunction(void * pParam)int idx=(int)pParam;ReceiveThread(idx+1);_endthread();11 編寫真正實(shí)現(xiàn)數(shù)據(jù)發(fā)送的函數(shù)ReceiveThreaddefine ReadSize

49、 500void ReceiveThread (int idx) / 接收文件數(shù)據(jù)的函數(shù)(搬運(yùn)工人)CFile file;char dataReadSize;long BeginPos, Size;long FileLength;long WriteOnce;char filename200;sprintf(filename,"tmpsave-%d.dat",idx);if(!file.Open(filename,CFile:modeCreate|CFile:modeWrite)AfxMessageBox("write file error!");ret

50、urn;while(SOCKET_ERROR=receiveSocketsidx-1. Receive(&FileLength, 4)while(SOCKET_ERROR=receiveSocketsidx-1. Receive(fn, 40)/ 獲取本線程傳輸任務(wù)(傳送塊的大小和起始位置)GetBeginPos(5 , idx, FileLength ,BeginPos, Size); /其中的5表示總共有5個(gè)線程,idx表示本線程編號(hào)while(Size>0)if(SOCKET_ERROR=(WriteOnce= receiveSocketsidx-1.Receive(dat

51、a,ReadSize)continue;Size=Size-WriteOnce;file.WriteHuge(data,WriteOnce);file.Close();SetEvent(hEventidx-1); / 發(fā)信號(hào)通知監(jiān)視線程本線程任務(wù)完成12 編寫監(jiān)視文件接收是否結(jié)束的函數(shù)ReceiveNotifyFunctionvoid ReceiveNotifyFunction(void * pParam) / 監(jiān)視文件傳輸?shù)臓顟B(tài)的線程WaitForMultipleObjects(5,hEvent,true,INFINITE);/當(dāng)所有工人線程都報(bào)告自己完成任務(wù)后,準(zhǔn)備合并文件CombineF

52、iles(); / 拼接每個(gè)線程暫存數(shù)據(jù)的文件_endthread();13 編寫合并文件的函數(shù)CombineFilesvoid CombineFiles()CFile fileDest, fileSour;char sourname500;static char data10000;long count;int i;if(!fileDest.Open(fn,CFile:modeCreate|CFile:modeWrite) / 建立并打開目標(biāo)文件AfxMessageBox("Combine: Make Dest File Error");return;for(i=0;i&

53、lt;5;i+) / 一次打開一個(gè)線程暫存的臨時(shí)文件,把其中數(shù)據(jù)寫入目標(biāo)文件后,刪除臨時(shí)文件wsprintf(sourname,"tmpsave-%d.dat",i+1);if(!fileSour.Open(sourname,CFile:modeRead)AfxMessageBox("Combine: Open Part File Error");return;count=fileSour.ReadHuge(data,10000);while(count>0)fileDest.WriteHuge(data,count);count=fileSour.ReadHuge(data,10000);fileSour.Close();if(!DeleteFile(sourname)AfxMessageBox("Combine: File-Delete Error");return;fileDest.Close();上述的多線程傳輸程序的傳輸速度已經(jīng)相

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

評(píng)論

0/150

提交評(píng)論