基于完成端口的文件传输设计
完成端口
說(shuō)到完成端口,我想很多人都不太陌生,下面是一段摘錄:
“完成端口”模型是迄今為止最為復(fù)雜的一種I/O模型。然而,假若一個(gè)應(yīng)用程序同時(shí)需要管理為數(shù)眾多的套接字,那么采用這種模型,往往可以達(dá)到最佳的系統(tǒng)性能!但不幸的是,該模型只適用于Windows NT和Windows 2000操作系統(tǒng)。因其設(shè)計(jì)的復(fù)雜性,只有在你的應(yīng)用程序需要同時(shí)管理數(shù)百乃至上千個(gè)套接字的時(shí)候,而且希望隨著系統(tǒng)內(nèi)安裝的CPU數(shù)量的增多,應(yīng)用程序的性能也可以線性提升,才應(yīng)考慮采用“完成端口”模型。要記住的一個(gè)基本準(zhǔn)則是,假如要為Windows NT或Windows 2000開發(fā)高性能的服務(wù)器應(yīng)用,同時(shí)希望為大量套接字I/O請(qǐng)求提供服務(wù)(Web服務(wù)器便是這方面的典型例子),那么I/O完成端口模型便是最佳選擇!(節(jié)選自《Windows網(wǎng)絡(luò)編程》第八章)
在.net中,一旦說(shuō)到完成端口,我們就不得不提到SocketAsyncEventArgs這個(gè)高性能的類,其內(nèi)部是基于完成端口實(shí)現(xiàn)的,然后被微軟提供了諸多封裝,所以使用起來(lái)也比較簡(jiǎn)單一些.由于這個(gè)類利用完成端口并且結(jié)合異步事件的方式進(jìn)行設(shè)計(jì)的,所以我們可以大致的知道他的一些特點(diǎn).
處理流程
關(guān)于此類,微軟的解釋很多,但是始終離不開高性能三個(gè)字.根據(jù)我的理解,如果利用此類設(shè)計(jì)一個(gè)服務(wù)端的消息傳送中心,那么其運(yùn)作流程如下:
1.創(chuàng)建SocketAsyncEventArgs池,并且創(chuàng)建緩沖管理中心,負(fù)責(zé)對(duì)池中的對(duì)象分配緩沖大小.
2.創(chuàng)建服務(wù)器套接字,并處于監(jiān)聽(tīng)狀態(tài).同時(shí)創(chuàng)建基于SocketAsyncEventArgs的客戶端接收對(duì)象,接收客戶端的連接.
3.如果有客戶端連接, 客戶端接收對(duì)象將會(huì)把控制權(quán)移交給數(shù)據(jù)接收對(duì)象,數(shù)據(jù)接收對(duì)象開始接收數(shù)據(jù).
這只是一個(gè)簡(jiǎn)單的流程,從這里我們可以看出,服務(wù)端套接字只是負(fù)責(zé)監(jiān)聽(tīng),一旦有客戶端連接,就會(huì)把連接事件拋給接收對(duì)象;所以客戶端一個(gè)一個(gè)的來(lái),服務(wù)端一個(gè)一個(gè)的拋,性能自然會(huì)好了不少.
說(shuō)了這么多,我們來(lái)淺析一下SocketAsyncEventArgs對(duì)象中的一些基本的知識(shí)點(diǎn).
緩沖
首先,說(shuō)到緩沖,我們很容易理解為一個(gè)存儲(chǔ)池,里面可以放入東西,也可以拿走.在SocketAsyncEventArgs類中,我們可以利用其SetBuffer方法初始化緩沖區(qū).
比如說(shuō): receiveArgs.SetBuffer(new byte[10],0,5);他的意思就是為當(dāng)前接收數(shù)據(jù)對(duì)象設(shè)置的緩沖區(qū)大小為10字節(jié),位置從0開始,并且允許接收的最大的數(shù)據(jù)長(zhǎng)度為5字節(jié).
當(dāng)數(shù)據(jù)被接收,然后寫入到緩沖區(qū)的時(shí)候,數(shù)據(jù)會(huì)按照預(yù)先設(shè)定好的緩沖規(guī)則進(jìn)行放置.比如這里我輸入aaaaa,那么在緩沖區(qū)會(huì)放入如下數(shù)據(jù):
?
但是當(dāng)有新的數(shù)據(jù)再進(jìn)來(lái)的時(shí)候,比如這里我輸入了bbbb,那么緩沖區(qū)就變成了
看到了沒(méi)有,緩沖區(qū)中第五位依然是我們前一次插入的值,所以,在這里我提醒大家,緩沖區(qū)的東西取完之后,一定要重置一下,否則臟數(shù)據(jù)會(huì)導(dǎo)致數(shù)據(jù)錯(cuò)誤.
講到這里,也許有人會(huì)說(shuō),假如我插入aaaaab會(huì)怎么樣,其實(shí),這個(gè)長(zhǎng)度已經(jīng)超過(guò)了緩沖區(qū)的長(zhǎng)度,緩沖區(qū)將會(huì)做截?cái)嗵幚?然后當(dāng)作兩段放入緩存中,首先會(huì)是
,然后會(huì)是
?
所以如果這個(gè)時(shí)候你的數(shù)據(jù)沒(méi)有被及時(shí)取走的話,將會(huì)得到最終結(jié)果
?這不,已經(jīng)發(fā)生粘包現(xiàn)象了.
?拋出方式
說(shuō)完緩沖,這里我們需要說(shuō)到的是服務(wù)端套接字是如何將接收的客戶端通過(guò)事件的方式拋給SocketAsyncEventArgs對(duì)象的.
在進(jìn)行服務(wù)端,我們一般都會(huì)有一個(gè)用于監(jiān)聽(tīng)的套接字,套接字會(huì)利用serverSocket.AcceptAsync(SocketAsyncEventArgs)異步方法注冊(cè)SocketAsyncEventArgs對(duì)象,然后一旦有客戶端連接,就會(huì)激發(fā)SocketAsyncEventArgs對(duì)象的Completed事件,然后,在這個(gè)事件中,serverSocket會(huì)通過(guò)ReceiveAsync(SocketAsyncEventArgs)異步方法將數(shù)據(jù)處理的過(guò)程交給另外一個(gè)專門負(fù)責(zé)數(shù)據(jù)處理的對(duì)象去完成,這樣,客戶端連接,服務(wù)端只負(fù)責(zé)將連接事件拋給SocketAsyncEventArgs對(duì)象即可,比起傳統(tǒng)的編程方式: 服務(wù)端既負(fù)責(zé)監(jiān)聽(tīng),又負(fù)責(zé)接收, 效率極大的提升.
同步方式
最后說(shuō)到的是同步問(wèn)題,因?yàn)樵诋惒浇换サ南到y(tǒng)中,同步問(wèn)題確實(shí)很重要,尤其是當(dāng)客戶端和服務(wù)器同步的時(shí)候.
客戶端里面的線程同步,我們可以利用AutoResetEvent來(lái)實(shí)現(xiàn),客戶端和服務(wù)端同步的時(shí)候,我們需要AutoResetEvent并且結(jié)合一些標(biāo)記信息來(lái)進(jìn)行,也就是客戶端往服務(wù)器發(fā)送的一些小標(biāo)記,比如1代表可以進(jìn)行,0代表取消等等.
在設(shè)計(jì)的時(shí)候,我們還需要記住的是,代碼的核心只是負(fù)責(zé)數(shù)據(jù)的傳輸,不要加過(guò)多的邏輯判斷在里面,否則會(huì)影響性能,邏輯判斷等最好移到界面處理處來(lái)進(jìn)行.
代碼精解
好了,下面上代碼來(lái)解釋解釋.
首先我們先從服務(wù)端開始:
View Code private int listenClientCount;private int poolSize;private int bufferSize;private EndPoint endPoint;private Socket serverSocket;private SocketAsyncEventArgsPool readWritePool;private BufferManager bufferManager;private SocketAsyncEventArgs acceptArgs;private SocketAsyncEventArgs receiveArgs;private SocketAsyncEventArgs sendArgs;public Action<Socket> OnStarted;public Action<Socket> OnConnected;public Action<SocketAsyncEventArgs> OnReceive;public Action<SocketAsyncEventArgs> OnSent;public Action<SocketAsyncEventArgs> OnDisconnect;public Action OnStopped;在服務(wù)端,我們會(huì)定義服務(wù)端套接字,用于連接對(duì)象的acceptArgs,用于接收數(shù)據(jù)的receiveArgs以及用于拋出事件的一些Action等等,這些Action讓外部引用進(jìn)行注冊(cè)并進(jìn)行操縱.
然后這里是Start函數(shù),
View Code public void Start(){if (this.serverSocket != null)throw new Exception("Server is running, can't init another one.");this.serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);try{this.serverSocket.Bind(endPoint);this.serverSocket.Listen(listenClientCount);}catch (ArgumentNullException ex){throw new Exception(ex.Message);}catch (SocketException ex){throw new Exception(ex.Message);}if (OnStarted != null)OnStarted(serverSocket);StartAccept(null);}這個(gè)函數(shù)的作用就是開啟監(jiān)聽(tīng),并且將接收事件傳遞給acceptArgs對(duì)象.
然后,當(dāng)客戶端連接過(guò)來(lái)的時(shí)候,服務(wù)端套接字開始將控制權(quán)傳遞給receiveArgs對(duì)象:
注意,這里有一個(gè)IO_Completed事件,表示一旦當(dāng)前數(shù)據(jù)的數(shù)據(jù)接收完成,receiveArgs對(duì)象將會(huì)觸發(fā)該事件,用于進(jìn)行下一步操作,一般會(huì)取lastOperation來(lái)判斷是否繼續(xù)接收.
最后需要說(shuō)的就是真正的數(shù)據(jù)接收函數(shù):
這個(gè)函數(shù)利用了循環(huán)的方式來(lái)接收數(shù)據(jù),以便保證所有的數(shù)據(jù)都能夠進(jìn)來(lái),直至數(shù)據(jù)接收完成.在這個(gè)接收函數(shù)中,不應(yīng)該有任何的邏輯判斷,否則會(huì)比較影響吞吐性能.
測(cè)試結(jié)果
好了,下面就看下界面截圖吧(請(qǐng)注意,這里我得客戶端是在上海,服務(wù)器端在香港,真實(shí)模擬數(shù)據(jù)傳輸情況).
(圖1,這是服務(wù)端開啟時(shí)候的情景)
(圖2,這是客戶端開啟時(shí)候的情景)
(圖3,客戶端準(zhǔn)備發(fā)送文件給服務(wù)器端的情景,請(qǐng)注意這里的同步方式,客戶端會(huì)等待服務(wù)端準(zhǔn)備完成,才會(huì)開始傳送數(shù)據(jù))
(圖4,數(shù)據(jù)傳送中,請(qǐng)看,左邊是客戶端的緩沖區(qū)的緩沖數(shù)據(jù),右邊是服務(wù)器端的接收緩沖區(qū)的緩沖數(shù)據(jù))
(圖5,數(shù)據(jù)傳輸完畢的情景)
(圖6,請(qǐng)注意,即使我們服務(wù)端設(shè)置的接收緩沖區(qū)為327670,長(zhǎng)度為32767,但是有時(shí)候數(shù)據(jù)還是不會(huì)完整的到達(dá)的,遇到這種情況,我們只需等待下一批數(shù)據(jù)到達(dá)即可.)
源碼下載
點(diǎn)擊這里下載
轉(zhuǎn)載于:https://www.cnblogs.com/scy251147/archive/2013/05/09/3069720.html
總結(jié)
以上是生活随笔為你收集整理的基于完成端口的文件传输设计的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: VS2008文件操作出现问题
- 下一篇: (转载)Shutter 0.90 发布