前幾天“X教授”發(fā)Email與我討論Socket通信方面的問(wèn)題,主要涉及4個(gè)方面內(nèi)容,現(xiàn)將回信公布出來(lái),希望園友們能積極參與討論,提出更好的解決方案。
下面是“X教授”的來(lái)信:
===========================================================
您好,我看了您寫的幾篇博文,想請(qǐng)教你幾個(gè).net Socket通信方面的問(wèn)題,這些問(wèn)題以前一直困擾著我。
1、按照你深入探析C# Socket的文中的說(shuō)法。
對(duì)于Socket的同步操作來(lái)說(shuō),Socket的RceiveBuffer和SendBuffer沒有用處,因我發(fā)送時(shí)是從應(yīng)用程序緩存(這里指字節(jié)數(shù)據(jù))直接拷貝到基礎(chǔ)系統(tǒng)發(fā)送緩沖區(qū),接收時(shí)是從基礎(chǔ)系統(tǒng)接收緩沖區(qū)拷貝到應(yīng)用程序的緩存。
Socket本身的接收緩存與發(fā)送緩存,是否只與異步Socket操作有關(guān)。在異步操作中又分為兩種,一種是以BeginXXX系列操作,以BeginSend為例,對(duì)這種操作我的理解是,從線程池中取一個(gè)線程,把數(shù)據(jù)拷貝到Socket的自帶的SendBuffer中,調(diào)用BenginSend的主線程然后立刻返回,該發(fā)送線程掛起,由操作系統(tǒng)把SendBuffer中的數(shù)據(jù)拷貝到基礎(chǔ)系統(tǒng)緩沖區(qū),發(fā)送完成后,掛起的線程激活,調(diào)用EndSend進(jìn)行相應(yīng)的操作,釋放異步對(duì)象等資源。
以上我的理解是否正確。
2、除XXXAsync系列操作外, BeginXXX系統(tǒng)的Socket操作是否也是用的完成端口機(jī)制。
3、.net與C/C++寫的結(jié)構(gòu)體通信。目前我熟悉的就兩種:
第一種是了MarShal類進(jìn)行結(jié)構(gòu)體的轉(zhuǎn)換操作,但托管與非拖管之間交互時(shí)的封送開銷,使得其效率不高。
第二種是用流的方式去解析數(shù)據(jù)包。然后按包結(jié)構(gòu),一個(gè)字段一個(gè)字段的去讀取。比如比流出讀取一個(gè)浮點(diǎn)型,binr.ReadSingle();此種效率還可以。接收與解析好幾M的數(shù)據(jù)沒有問(wèn)題。
我想問(wèn)一下,有沒有效率更高的方法。
4、服務(wù)器給客戶端轉(zhuǎn)發(fā)數(shù)據(jù),當(dāng)客戶端接收包的地方堵塞(可能是socket.Receive),有沒有什么好的方案,讓服務(wù)器自動(dòng)丟包。
===========================================================
下面是我的回信:
===========================================================
X教授,您好:
非常抱歉,最近比較忙,耽誤給您回復(fù)了。對(duì)于您提出的幾個(gè)問(wèn)題,我只能作淺層次的回答,更深的研究,我們以后慢慢探討,共同學(xué)習(xí)。
第一個(gè)問(wèn)題:
你的理解是正確的,對(duì)于同步發(fā)送,是直接拷貝發(fā)送數(shù)據(jù)到基礎(chǔ)系統(tǒng)的發(fā)送緩沖區(qū),與Socket本身的Buffer無(wú)關(guān)。對(duì)于異步發(fā)送,是將Socket自帶的Buffer空間內(nèi)的所有數(shù)據(jù),拷貝到基礎(chǔ)系統(tǒng)發(fā)送緩沖區(qū),并立即返回。
在實(shí)際開發(fā)中,個(gè)人覺得采用異步接受、同步發(fā)送。也許你會(huì)感到很驚訝,很明顯,異步操作明顯比同步操作效率高很多,為什么發(fā)送要采用同步呢?不知道你在開發(fā)過(guò)程中有沒有遇到過(guò)“現(xiàn)在已經(jīng)正在使用此 SocketAsyncEventArgs 實(shí)例進(jìn)行異步套接字操作”錯(cuò)誤,這個(gè)錯(cuò)誤是由于SAEA對(duì)象在一個(gè)時(shí)刻中只能處于StartAccept、StartReceive、StartSend狀態(tài)中的一種,試想,當(dāng)Socket服務(wù)器處于高壓力情形下,一條Socket連接在很短的時(shí)間內(nèi)要發(fā)送大量數(shù)據(jù),當(dāng)Socket處于StartSend狀態(tài)下尚未觸發(fā)回調(diào)函數(shù)時(shí),又一次調(diào)用此Socket的StartSend方法,便會(huì)拋出該異常。這也有改進(jìn)方案:記錄Socket的當(dāng)前狀態(tài),并存儲(chǔ)在Socket的UserToken對(duì)象下,當(dāng)要執(zhí)行StartSend時(shí),判斷狀態(tài)。不過(guò)這樣效率會(huì)很慢,遠(yuǎn)遠(yuǎn)低于同步發(fā)送的效率,當(dāng)高并發(fā)時(shí),還要用大量的鎖機(jī)制來(lái)維護(hù)線程的同步,得不償失。不知道你有沒有更好的改進(jìn)方案,如果有,請(qǐng)告知我,不勝感激。
第二個(gè)問(wèn)題:
BeginXXX系統(tǒng)的Socket操作采用的是完成端口機(jī)制,在初始化SAEA對(duì)象時(shí),會(huì)為SAEA對(duì)象設(shè)定回調(diào)函數(shù)。
SocketAsyncEventArgs acceptEventArg = new SocketAsyncEventArgs();
acceptEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(AcceptEventArg_Completed);
第三個(gè)問(wèn)題:
建議使用流的方式解析數(shù)據(jù)包。
在解析數(shù)據(jù)包的過(guò)程中,不知道您有沒有遇到過(guò)粘包的現(xiàn)象,我覺得對(duì)粘包的處理是影響解析效率的關(guān)鍵,請(qǐng)參見博文:http://www.cnblogs.com/tianzhiliang/archive/2010/08/31/1813659.html。
第四個(gè)問(wèn)題:
我覺得采用“心跳”是一個(gè)不錯(cuò)的解決方案。
“心跳”分為兩種,第一種是客戶端發(fā)起的心跳,第二種是服務(wù)端發(fā)起的心跳。
客戶端發(fā)起的心跳:客戶端每隔一段時(shí)間發(fā)送策略消息給Socket服務(wù)器,Socket服務(wù)器原路返回策略消息,如果客戶端在設(shè)定時(shí)間段內(nèi)沒有收到Socket服務(wù)器的返回消息,經(jīng)重試機(jī)制后,判定Socket服務(wù)器已Down,關(guān)閉連接。
服務(wù)端發(fā)起的心跳:服務(wù)端實(shí)時(shí)記錄每條Socket的IO操作時(shí)間,每隔一段時(shí)間獲取所有Socket列表的快照,掃描每條Socket,如果該Socket的IO操作時(shí)間距當(dāng)前時(shí)間已超出設(shè)定值,則判定客戶端Down,關(guān)閉連接。