深入探析c# Socket
2010-09-08 17:28 by 田志良, 14562 visits, 收藏, 編輯最近瀏覽了幾篇有關(guān)Socket發(fā)送消息的文章,發(fā)現(xiàn)大家對(duì)Socket Send方法理解有所偏差,現(xiàn)將自己在開發(fā)過程中對(duì)Socket的領(lǐng)悟?qū)懗鰜恚怨┐蠹覅⒖肌?/P>
?。ㄒ唬┘軜?gòu)
基于TCP協(xié)議的Socket通信,架構(gòu)類似于B/S架構(gòu),一個(gè)Socket通信服務(wù)器,多個(gè)Socket通信客戶端。Socket通信服務(wù)器啟動(dòng)時(shí),會(huì)建立一個(gè)偵聽Socket,偵聽Socket將偵聽到的Socket連接傳給接受Socket,然后由接受Socket完成接受、發(fā)送消息,當(dāng)Socket存在異常時(shí),斷開連接。在實(shí)際開發(fā)項(xiàng)目中,往往要求Socket通信服務(wù)器能提供高效、穩(wěn)定的服務(wù),一般會(huì)用到以下技術(shù):雙工通信、完成端口、SAEA、池、多線程、異步等。特別是池,用的比較多,池一般包括一下幾種:
1)Buffer池,用于集中管控Socket緩沖區(qū),防止內(nèi)存碎片。
2)SAEA池,用于集中管控Socket,重復(fù)利用Socket。
3)SQL池,用于分離網(wǎng)絡(luò)服務(wù)層與數(shù)據(jù)訪問層(SQL的執(zhí)行效率遠(yuǎn)遠(yuǎn)低于網(wǎng)絡(luò)層執(zhí)行效率)。
4)線程池,用于從線程池中調(diào)用空閑線程執(zhí)行業(yè)務(wù)邏輯,進(jìn)一步提高網(wǎng)絡(luò)層運(yùn)行效率。
?。ǘ㏒end
主服務(wù)器接受Socket為一端口,客戶端Socket為一端口,這兩個(gè)端口通過TCP協(xié)議建立連接,通信基礎(chǔ)系統(tǒng)負(fù)責(zé)管理此連接,它有兩個(gè)功能:
1)發(fā)送消息
2)接受消息
Socket的Send方法,并非大家想象中的從一個(gè)端口發(fā)送消息到另一個(gè)端口,它僅僅是拷貝數(shù)據(jù)到基礎(chǔ)系統(tǒng)的發(fā)送緩沖區(qū),然后由基礎(chǔ)系統(tǒng)將發(fā)送緩沖區(qū)的數(shù)據(jù)到連接的另一端口。值得一說的是,這里的拷貝數(shù)據(jù)與異步發(fā)送消息的拷貝是不一樣的,同步發(fā)送的拷貝,是直接拷貝數(shù)據(jù)到基礎(chǔ)系統(tǒng)緩沖區(qū),拷貝完成后返回,在拷貝的過程中,執(zhí)行線程會(huì)IO等待, 此種拷貝與Socket自帶的Buffer空間無關(guān),但異步發(fā)送消息的拷貝,是將Socket自帶的Buffer空間內(nèi)的所有數(shù)據(jù),拷貝到基礎(chǔ)系統(tǒng)發(fā)送緩沖區(qū),并立即返回,執(zhí)行線程無需IO等待,所以異步發(fā)送在發(fā)送前必須執(zhí)行SetBuffer方法,拷貝完成后,會(huì)觸發(fā)你自定義回調(diào)函數(shù)ProcessSend,在ProcessSend方法中,調(diào)用SetBuffer方法,重新初始化Buffer空間。
口說無憑,下面給個(gè)例子:
服務(wù)器端:
客戶端:
解釋:
客戶端第一次發(fā)送數(shù)據(jù):1234567890。
客戶端第一個(gè)接受數(shù)據(jù):1234567890,該數(shù)據(jù)由服務(wù)端用Send同步方法發(fā)送返回。
客戶端第二個(gè)接受數(shù)據(jù):1234567890,該數(shù)據(jù)由服務(wù)端用Send異步方法發(fā)送返回。
以上似乎沒什么異常,好,接下來,我只發(fā)送abc。
客戶端第一個(gè)接受數(shù)據(jù):abc,理所當(dāng)然,沒什么問題。
客戶端第二個(gè)接受數(shù)據(jù):abc4567890!為什么呢?應(yīng)該是abc才對(duì)呀!
好,現(xiàn)在為大家解釋一下:
異步發(fā)送是將其Buffer空間中所有數(shù)據(jù)拷貝到基礎(chǔ)系統(tǒng)發(fā)送緩沖區(qū),第一次拷貝1234567890到發(fā)送緩沖區(qū),所以收到1234567890,第二次拷貝abc到發(fā)送緩沖區(qū),替換了先前的123,所以收到abc4567890,大家明白的?
源碼:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
class AsyncUserToken
{
public Socket Socket;
}


#1樓 dreamhappy 2010-09-08 18:48
希望博主能寫幾篇關(guān)于c# .net環(huán)境 c/s多線程socket開發(fā),如何及時(shí)的釋放線程 關(guān)閉線程和socket的順序應(yīng)該怎樣處理的博文,因?yàn)槲抑皊ocket編程時(shí)候客戶端和服務(wù)器端分別有一個(gè)線程和一個(gè)socket 往往線程不知道什么時(shí)候合理的釋放
#2樓 秋色 2010-09-08 19:27
線程的釋放,一般是定義開關(guān)變量。讓線程自己退出。while(開關(guān))
{
if(??)
{
開關(guān)=false;
}
}
#3樓 %admin 2010-09-09 10:36
實(shí)際測(cè)試了一下,還真出現(xiàn)了樓主描述的問題,我想這問題主要就出在了Buffer池的使用上,Send的時(shí)候發(fā)送的是 從e.buff 拷貝出來的真實(shí)大小的數(shù)據(jù),SendAsyn的時(shí)候發(fā)送的是e.buff 。 樓主還真是細(xì)心啊,不過好像實(shí)際中要發(fā)送數(shù)據(jù)給客戶端的時(shí)候不應(yīng)該在用e.buff了, 此問題有待繼續(xù)深入#4樓[樓主] 田志良 2010-09-09 10:37
@dreamhappy多線程在Socket開發(fā)中尤為重要,若處理不當(dāng),會(huì)嚴(yán)重影響效率,接下來,我會(huì)陸續(xù)寫些這類博文,謝謝大家關(guān)注。
#5樓[樓主] 田志良 2010-09-09 10:42
@安度如果你想使你的Socket服務(wù)器非常高效,池是一定用到的,如果不用池,高級(jí)消息隊(duì)列也行,這樣才能極大提高并發(fā)數(shù)和最大連接數(shù)。
#6樓 %admin 2010-09-09 10:42
還好SocketAsyncEventArgs 提供了SetBuffer ,遇到這種情況是,不妨在SendAsyn之前 動(dòng)態(tài)的 e.SetBuffer 一下,就沒問題了~~示例:
e.AcceptSocket.Send(data);
System.Threading.Thread.Sleep(1000);
e.SetBuffer(0, data.Length);
if (!e.AcceptSocket.SendAsync(e))
{
Console.WriteLine("asynsend error");
}
#7樓[樓主] 田志良 2010-09-09 10:44
@九九目前我開發(fā)的IM系統(tǒng)正在做壓力測(cè)試,基本上最大連接數(shù)能上到20000,并發(fā)能上到3000。你所提的問題平時(shí)我也有遇到過,有空大家一起研究研究。
#8樓 %admin 2010-09-09 10:51
其實(shí)把下面3處代碼關(guān)聯(lián)起來看下就比較容易了解為什么會(huì)出現(xiàn)這種情況了,#9樓[樓主] 田志良 2010-09-09 11:10
#10樓 %admin 2010-09-09 11:29
#11樓 henry 2010-09-09 11:46
其實(shí)SocketAsyncEventArgs性能不錯(cuò)的,在新的測(cè)試中cpu E5405 的服務(wù)器,服務(wù)端接收256byte數(shù)據(jù)并返回給client(有分包處理)其秒處理數(shù)據(jù)包的能力在2.5W. 而CPU只占用了50%,內(nèi)存在300M內(nèi).補(bǔ)充:這樣的處理方式在秒處理5000消息的時(shí)候估計(jì)會(huì)性能問題產(chǎn)生.
#12樓 阿三 2010-09-09 14:31
小伙子進(jìn)步不錯(cuò)嘛。#13樓 無為無知無欲 2010-09-09 16:27
樓主,我剛做了這個(gè).net 3.5 完成端口方法的測(cè)試,一臺(tái)普通的服務(wù)器,2G內(nèi)存,可以并發(fā)接受1500條消息/秒, 這個(gè)瓶頸主要是從消息隊(duì)列寫進(jìn)數(shù)據(jù)庫的瓶頸,超過后就會(huì)造成消息隊(duì)列的增長(zhǎng),但前端還是能不斷接收數(shù)據(jù)的。所以如果數(shù)據(jù)庫服務(wù)器更高效的話,能力還能大幅提高。最高連接我做到了20000,CPU,和內(nèi)存還沒怎么提高,所以應(yīng)該能更高,問題是測(cè)試的時(shí)候這么多的客戶端不好做啊。
#14樓[樓主] 田志良 2010-09-09 17:16
@無為無知無欲所以你要做一個(gè)SQL池,將要執(zhí)行的SQL語句放到池中,然后每隔一段時(shí)間,安排一條線程掃描SQL池,如果SQL池中有SQL語句,則批量執(zhí)行,如果沒有則退出。在對(duì)SQL池管理時(shí)要尤為小心,Push操作和Ececute操作要互斥,執(zhí)行SQL語句時(shí),不能Push SQL語句,相反,Push SQL語句時(shí),也不能執(zhí)行SQL語句。
#15樓 安度 2010-09-09 17:28
我是最近才接觸Socket的,貌似SocketAsyncEventArgs我沒有在網(wǎng)上看到,大概用的都是BeginXXX和EndXXX,服務(wù)端的話,基本是用多線程(Accpet在一個(gè)獨(dú)立的線程),然后做一個(gè)線程池的管理類(貌似.net有現(xiàn)成的線程池),不知道樓主這種方法有什么優(yōu)點(diǎn),不防解釋下#16樓 安度 2010-09-09 17:33
在.NET 3.5里System.Net.Sockets空間下有一組增強(qiáng)功能的類,提供可供專用的高性能套接字應(yīng)用程序使用的可選異步模式,SocketAsyncEventArgs 類就是這一組增強(qiáng)功能的一部分。該類專為需要高性能的網(wǎng)絡(luò)服務(wù)器應(yīng)用程序而設(shè)計(jì)。應(yīng)用程序可以完全使用增強(qiáng)的異步模式,也可以僅僅在目標(biāo)熱點(diǎn)區(qū)域(例如,在接收大量數(shù)據(jù)時(shí))使用此模式。以下是關(guān)于此類的介紹(摘自MSDN)原來是3.5里面的!是不是就是傳說中的IOCP?有機(jī)會(huì)樓主寫詳細(xì)點(diǎn),原來不知道樓主是用的這個(gè)!
#17樓[樓主] 田志良 2010-09-09 18:07
@安度我的做法是開通300或更多個(gè)Socket用于接受偵聽Socket傳遞的SAEA,為什么要開通這么多?你在做壓力測(cè)試時(shí)就明白了,開通多一點(diǎn),會(huì)使你的連接效率、連接速度大幅度提高。對(duì)于SAEA,它不能同時(shí)ReceiveAsync、SendAsync,所以采用雙工通信,讓收發(fā)數(shù)據(jù)在同一條連接上進(jìn)行,以提高效率。對(duì)于業(yè)務(wù)邏輯層上的處理,主要采用線程池、SQL池,用SQL池主要將網(wǎng)絡(luò)層與數(shù)據(jù)訪問層分離,為什么要分離?數(shù)據(jù)庫操作會(huì)極大影響效率,如果不分離,數(shù)據(jù)操作會(huì)拖垮網(wǎng)絡(luò)層。
#18樓 Leon Weng 2010-09-11 02:07
前段時(shí)間搞視頻通信時(shí)用到了sokect,順便研究了一下,感覺效率的確比較高,但是在多線程方面自我感覺很差,所以沒有使用socket完成改成WCF了,WCF封裝了SOCKET,更好用了。#19樓 ToBin 2011-03-01 11:12
雙工通信時(shí)需要一個(gè)客戶端連接兩個(gè)端口嘛,一個(gè)收,一個(gè)發(fā)?現(xiàn)在已經(jīng)正在使用此 SocketAsyncEventArgs 實(shí)例進(jìn)行異步套接字操作。
為什么會(huì)出現(xiàn)這個(gè)問題呢?
#20樓[樓主] 田志良 2011-03-01 11:30
@ToBin當(dāng)一個(gè)SAEA對(duì)象已處于StartReceive狀態(tài)時(shí),就不能用此SAEA發(fā)送消息。也就是說SAEA對(duì)象在一個(gè)時(shí)刻中只能處于StartAccept、StartReceive、StartSend狀態(tài)中的一種。解決的辦法就是用雙工通信,為一條連接開辟兩個(gè)SAEA對(duì)象,一個(gè)用于收,一個(gè)用于發(fā)。
#21樓 ToBin 2011-03-01 13:02
剛又把文章仔細(xì)讀了一遍,很多東西還是沒有理解!剛好看到您的回復(fù)!很有幫助,我再研究研究您的文章!
還有這個(gè)saea的三種狀態(tài),我現(xiàn)在在發(fā)送的時(shí)候報(bào)錯(cuò)"
現(xiàn)在已經(jīng)正在使用此 SocketAsyncEventArgs 實(shí)例進(jìn)行異步套接字操作"
但我這個(gè)確實(shí)是clientsocket.sendasync(saeasender)是發(fā)生的。
我接受的時(shí)候用的clientsocket.receiveasync(saeareceiver),兩個(gè)沒有沖突啊,很奇怪!
#22樓[樓主] 田志良 2011-03-01 14:55
@ToBin建議Receive用異步模式,Send用同步模式。
#23樓 ToBin 2011-03-01 17:31
錯(cuò)誤“現(xiàn)在已經(jīng)正在使用此 SocketAsyncEventArgs 實(shí)例進(jìn)行異步套接字操作”是不是因?yàn)楫惒桨l(fā)送了就返回了,但實(shí)際上還沒有發(fā)送到,當(dāng)?shù)诙萎惒桨l(fā)送的時(shí)候,第一次還沒發(fā)送完,就出了這個(gè)錯(cuò)誤?我猜測(cè)!
#24樓[樓主] 田志良 2011-03-02 16:51
@ToBin當(dāng)Socket處于StartSend狀態(tài)時(shí),也不能執(zhí)行發(fā)送操作,必須等到發(fā)送回調(diào)事件觸發(fā)后,才能繼續(xù)執(zhí)行StartSend。解決的辦法是:記錄Socket的當(dāng)前狀態(tài),并存儲(chǔ)在Socket的UserToken對(duì)象下,當(dāng)要執(zhí)行StartSend時(shí),判斷狀態(tài)。不過這樣效率會(huì)很慢,當(dāng)并發(fā)量達(dá)到3000時(shí),會(huì)報(bào)很多錯(cuò),推薦的方法是用同步發(fā)送。不要覺得同步發(fā)送就一定會(huì)比異步發(fā)送慢,事實(shí)證明,對(duì)于SocketAsyncEventArgs,同步發(fā)送比異步發(fā)送快多了。
#25樓 ToBin 2011-03-02 17:17
拜讀了,現(xiàn)在還有一個(gè)問題請(qǐng)教,異步的時(shí)候是“但異步發(fā)送消息的拷貝,是將Socket自帶的Buffer空間內(nèi)的所有數(shù)據(jù),拷貝到基礎(chǔ)系統(tǒng)發(fā)送緩沖區(qū),并立即返回”這個(gè)基礎(chǔ)系統(tǒng)緩沖區(qū)是對(duì)應(yīng)winsocket的,有點(diǎn)疑惑,就是只有一個(gè)緩存區(qū),接受也從基礎(chǔ)系統(tǒng)緩沖區(qū)中拷貝處來,發(fā)送也是拷貝到這,如果我接收的時(shí)候同時(shí)發(fā)送,基礎(chǔ)系統(tǒng)緩沖區(qū)里的數(shù)據(jù)還沒取出來,將要取出來的時(shí)候發(fā)送,拷貝進(jìn)去,會(huì)不是導(dǎo)致接收出來的數(shù)據(jù)不正確?導(dǎo)致臟讀,數(shù)據(jù)錯(cuò)誤!有這樣的問題嘛?
#26樓[樓主] 田志良 2011-03-02 18:04
@ToBin不會(huì)導(dǎo)致這個(gè)問題,基礎(chǔ)系統(tǒng)緩沖區(qū)為每個(gè)Socket分配發(fā)送緩沖區(qū)和接受緩沖區(qū),這兩個(gè)不沖突。
#27樓 ToBin 2011-03-02 18:33
要西,外瑞thank you !哈哈#28樓[樓主] 田志良 2011-03-02 18:45
@ToBin呵呵,不客氣。
#29樓 ToBin 2011-03-03 17:02
呵呵,又碰到問題了,可能我太笨了,您在這篇文章“Socket服務(wù)器整體架構(gòu)概述”中說到一個(gè)“消息隊(duì)列調(diào)度器”,這個(gè)東西該怎么實(shí)現(xiàn),能大概說說嘛?謝謝了!呵呵
#30樓[樓主] 田志良 2011-03-04 09:03
@ToBin呵呵,把你的郵箱發(fā)給我,這個(gè)周末我寫個(gè)Demo給你。
#31樓 ToBin 2011-03-04 09:26
太感謝了,激動(dòng)!我的郵箱:tuablove@126.com辛苦您了!
#32樓 ToBin 2011-03-07 10:07
您的郵件已經(jīng)收到,思路已經(jīng)了解,非常感謝能得到您的幫助,希望能繼續(xù)得到您的幫助,思路也行,呵呵,非常感謝!#33樓 ToBin 2011-03-08 17:26
又碰到個(gè)問題,不知道怎么處理了,問題是這樣的getdata方法是socket 發(fā)送命令,接收返回值的方法,因?yàn)閟ocket 服務(wù)器發(fā)送,接受時(shí)分開的,我怎么在getdata中讓方法阻塞,讓服務(wù)器接收到命令,然后再把結(jié)果發(fā)送過來!類似于javascript 的ajax!這種東西該怎么寫?。?BR>類似于使用memcache 里
這里面這個(gè)mc.Get("key") 方法是怎么實(shí)現(xiàn)的啊 ?
#34樓 ToBin 2011-03-09 21:02
志良哥,您好,我在socket 開發(fā)中碰到了一些問題,想請(qǐng)教您,已經(jīng)發(fā)送您郵箱了,思路解說在郵件里,代碼在附件!等待您的回復(fù)!#35樓 edwardxh 2011-11-16 09:33
樓主您好,不知您是否可以把這個(gè)“Socket服務(wù)器整體架構(gòu)概述”Demo也發(fā)給我一份,因?yàn)槲易罱苍谘芯縎ocket通信,很希望能得到您的技術(shù)心得分享,謝謝!
我的郵箱:79668157@qq.com
#36樓 glf 2011-11-23 17:19
現(xiàn)在公司要求能夠接受2W左右的服務(wù)端,每5秒訪問一次,高能不能給點(diǎn)意見啊#37樓 鵬@ 2012-01-12 17:03
@ToBin大哥,能不能給小弟也發(fā)一個(gè)demo:lipeng1988011@126.com!!!
多謝啦?。?!