計(jì)算機(jī)網(wǎng)絡(luò)最根本的目的是實(shí)現(xiàn)網(wǎng)絡(luò)中計(jì)算機(jī)之間的分布式進(jìn)程間通信 即時(shí)通訊所使用的傳輸協(xié)議是TCP和UDP windows系統(tǒng)和linux系統(tǒng)都支持socket 按照這樣的規(guī)范 我們可以實(shí)現(xiàn)垮平臺(tái)的信息交換 所以我們都經(jīng)過(guò)socket傳輸數(shù)據(jù),TCP和UDP位于網(wǎng)絡(luò)中的傳輸層,,位于IP層之上 是用戶(hù)功能的最底層 TCP:面向連接的,、可靠的、基于字節(jié)流的運(yùn)輸層通信協(xié)議 數(shù)據(jù)一定是可靠地到達(dá),,先發(fā)送的先到,,丟包重傳 可以提供流控制機(jī)制,建立一個(gè)TCP連接需要經(jīng)過(guò)3次握手,,關(guān)閉一個(gè)TCP連接需要經(jīng)過(guò)4此握手 ,,在一個(gè)TCP連接中只支持兩方的通信,不支持廣播,,采用字節(jié)流方式,,如果字節(jié)流太長(zhǎng),將其分段,。 UDP:無(wú)連接的傳輸層協(xié)議,,提供面向事務(wù)的簡(jiǎn)單不可靠信息傳送服務(wù)。選擇UDP必須要謹(jǐn)慎,。在網(wǎng)絡(luò)質(zhì)量令人不十分滿(mǎn)意的環(huán)境下,,UDP協(xié)議數(shù)據(jù)包丟失會(huì)比較嚴(yán)重,但是UDP不是面向連接的 它有資源消耗小,處理速度快的特點(diǎn) 對(duì)于那些要求不是特別嚴(yán)格的數(shù)據(jù) 如音頻流,,視頻流和普通數(shù)據(jù) 即時(shí)偶爾丟幾個(gè)包也不會(huì)對(duì)結(jié)果產(chǎn)生多大的影響,。并且它能減輕對(duì)網(wǎng)絡(luò)的壓力 對(duì)于即時(shí)通訊軟件,例如像騰訊QQ 百度Hai 阿里旺旺 以及大型的聊天室,,我的理解應(yīng)該是這樣的 有時(shí)候它們需要可靠的連接,,有時(shí)候可能需要UDP就夠了 所以像QQ那樣的軟件是同時(shí)使用TCP和UDP這兩種協(xié)議的 可以設(shè)想服務(wù)端負(fù)責(zé)處理客戶(hù)端的數(shù)據(jù) 具體包括如響應(yīng)客戶(hù)端的好友列表請(qǐng)求,通知其它用戶(hù)好友的狀態(tài)更改上線(xiàn)下線(xiàn),,轉(zhuǎn)發(fā)文字消息 而客戶(hù)端直接可以進(jìn)行視頻和語(yǔ)言聊天 視頻語(yǔ)音數(shù)據(jù)量大,,服務(wù)器也無(wú)法承擔(dān)這樣的壓力,所以服務(wù)端不會(huì)對(duì)語(yǔ)音視頻進(jìn)行轉(zhuǎn)發(fā) 所以應(yīng)當(dāng)在兩個(gè)客戶(hù)端直接建立連接實(shí)現(xiàn)兩個(gè)客戶(hù)端進(jìn)程間的通信 此部分服務(wù)器不管,。 所以即時(shí)通訊軟件應(yīng)該具有這樣的特點(diǎn): 1.服務(wù)端處理小的數(shù)據(jù) 2.大的數(shù)據(jù)(如語(yǔ)音 視頻 文件)在客戶(hù)端直接進(jìn)行傳送 無(wú)須服務(wù)器轉(zhuǎn)發(fā) C#局域網(wǎng)聊天的程序源代碼在網(wǎng)上到處都是 千篇一律地同一種方式,,都是基于TCP協(xié)議的 服務(wù)端:?jiǎn)?dòng)后在內(nèi)存中維持一個(gè)在線(xiàn)客戶(hù)列表(數(shù)據(jù)結(jié)構(gòu)為哈希表或者字典),之后開(kāi)啟一個(gè)線(xiàn)程使用循環(huán)監(jiān)聽(tīng)客戶(hù)端的TCP連接 客戶(hù)端點(diǎn)擊登錄后請(qǐng)求建立連接,,服務(wù)端則發(fā)送在線(xiàn)列表并通知其它用戶(hù)有新用戶(hù)上線(xiàn)了,,并將這個(gè)TCP連接交給一個(gè)新的線(xiàn)程去執(zhí)行,這個(gè)新線(xiàn)程的任務(wù)就是循環(huán)地接受新連接的數(shù)據(jù) 并處理這些數(shù)據(jù),。于是服務(wù)端便這樣工作了 不停地響應(yīng)TCP連接,,一旦有新用戶(hù)就創(chuàng)建新的線(xiàn)程專(zhuān)門(mén)為該用戶(hù)服務(wù) 這樣服務(wù)端就可以做到同時(shí)響應(yīng)多個(gè)客戶(hù)的多條請(qǐng)求 很容易地就實(shí)現(xiàn)了局域網(wǎng)聊天 但是這里就有一些問(wèn)題了:運(yùn)行于Internet上的大型聊天室能這樣做嗎? 個(gè)人覺(jué)得肯定不能.如果這個(gè)程序是運(yùn)行于Internet上的 在線(xiàn)用戶(hù)數(shù)以萬(wàn)計(jì) 服務(wù)端就需要和數(shù)萬(wàn)個(gè)用戶(hù)保持這樣一個(gè)TCP連接 并且服務(wù)端上有上萬(wàn)個(gè)線(xiàn)程在運(yùn)行著 而用戶(hù)大多數(shù)時(shí)候只是掛著聊天工具 于是發(fā)生了這樣一種情況,,TCP連接數(shù)等于在線(xiàn)用戶(hù)數(shù) 線(xiàn)程數(shù)等于在線(xiàn)用戶(hù)數(shù),,而這些TCP連接絕大多數(shù)時(shí)間是沒(méi)有數(shù)據(jù)傳輸?shù)?而這些線(xiàn)程絕大多數(shù)時(shí)間也是沒(méi)有執(zhí)行實(shí)際任務(wù)的,但是它們依然消耗著網(wǎng)絡(luò)資源和CPU資源 線(xiàn)程依然在輪流地使用著CPU時(shí)間片,,它們消耗著資源卻什么也沒(méi)做,。還會(huì)產(chǎn)生一個(gè)矛盾,給服務(wù)器帶來(lái)極大的壓力 我相信沒(méi)有哪個(gè)程序能運(yùn)行上萬(wàn)個(gè)線(xiàn)程吧,!于是有了以下的突出問(wèn)題: 1.如何解決線(xiàn)程數(shù) 有效地理由CPU 2.如何解決連接數(shù) 先說(shuō)第一個(gè)問(wèn)題,,解決線(xiàn)程數(shù) .NET 提供了ThreadPool類(lèi),我們通過(guò)向線(xiàn)程池指派任務(wù)而不用使用new Thread()來(lái)為每一個(gè)客戶(hù)端創(chuàng)建一個(gè)線(xiàn)程,。但是這樣又會(huì)發(fā)生新的問(wèn)題了 由于線(xiàn)程池中的線(xiàn)程執(zhí)行任務(wù)的時(shí)間是從客戶(hù)登陸到客戶(hù)下線(xiàn),,所以可以把任務(wù)耗時(shí)看做的無(wú)限的 線(xiàn)程池規(guī)定了最大連接數(shù),盡管可以通過(guò)設(shè)置來(lái)更改最大任務(wù)數(shù)容納更多的線(xiàn)程 但太多的認(rèn)為仍然需要排隊(duì),,這可以有效的控制線(xiàn)程數(shù)量 但是依然是治標(biāo)不治本的,,運(yùn)行的線(xiàn)程依然絕大多數(shù)時(shí)候只是占著CPU而沒(méi)有處理用戶(hù)發(fā)送過(guò)來(lái)的數(shù)據(jù) 因?yàn)橛脩?hù)在線(xiàn)10個(gè)小時(shí) 服務(wù)器處理10個(gè)小時(shí)信息可能只需要1秒鐘 執(zhí)行這些任務(wù)的線(xiàn)程大多數(shù)時(shí)候只是在等待毫無(wú)無(wú)用武之地,當(dāng)用戶(hù)數(shù)太多之后服務(wù)端的響應(yīng)就變得十分遲鈍。所以像這種服務(wù)端的工作方式就決定了它只能用于在線(xiàn)人數(shù)很少局域網(wǎng)聊天 無(wú)法進(jìn)行大型多人的即時(shí)通訊,。經(jīng)過(guò)一段時(shí)間之后 我開(kāi)始接觸到異步編程,,于是對(duì)此進(jìn)行了一些更改,以充分地利用CPU 具體方式是這樣的: 1.服務(wù)器運(yùn)行我們依然需要一個(gè)線(xiàn)程持續(xù)地響應(yīng)客戶(hù)端的連接請(qǐng)求 以下是監(jiān)聽(tīng)線(xiàn)程執(zhí)行的任務(wù)
1 private void WaitingClient()
2 { 3 while (running) 4 { 5 remoteClient = listener.AcceptTcpClient();//同步方法 6 RemoteClient newclient = new RemoteClient(remoteClient); 7 } 8 9 }
每當(dāng)我們接收到一個(gè)新的連接后 我們就實(shí)例化一個(gè)新的RemoteClient對(duì)象 而在實(shí)例化這個(gè)對(duì)象中進(jìn)行異步操作,,這里關(guān)鍵就是這個(gè)RemoteClient類(lèi)
代碼
1 public class RemoteClient
2 { 3 private TcpClient client; 4 private NetworkStream streamToClient; 5 private const int bufferSize = 8192; 6 private byte[] buffer; 7 private string userName; 8 9 public RemoteClient(TcpClient client) 10 { 11 this.client = client; 12 streamToClient = client.GetStream(); 13 buffer = new byte[bufferSize]; 14 15 //異步操作完成時(shí)調(diào)用的方法 16 AsyncCallback callBack = new AsyncCallback(ReadComplete); 17 streamToClient.BeginRead(buffer, 0, bufferSize, callBack, null); //異步操作完成后將通知回調(diào)方法 18 } 19 20 private void ReadComplete(IAsyncResult ar) //參數(shù)表示異步操作的狀態(tài) 21 { 22 int bytesRead = 0; 23 CustomMessage message; 24 try 25 { 26 lock (streamToClient) 27 { 28 bytesRead = streamToClient.EndRead(ar); 29 } 30 if (bytesRead == 0) 31 throw new Exception("讀取到0字節(jié)"); 32 message = (CustomMessage)SerializationHelper.Deserialize(buffer); 33 switch (message.cmd) 34 { 35 case Cmds.online: 36 { 37 //把鏈接的用戶(hù)加入到哈希表中 38 Server.userTable.Add(message.message, client); 39 userName = message.message; 40 Server.richTextBox1.AppendText("已響應(yīng)連接請(qǐng)求" + client.Client.LocalEndPoint.ToString() + "<---" + client.Client.RemoteEndPoint.ToString() + "\n"); ; 41 Server.richTextBox1.AppendText("用戶(hù) " + message.message + " 上線(xiàn)了\n"); 42 43 CustomMessage tempMessage = new CustomMessage(userName, Cmds.online);//上線(xiàn)信息 44 //通知其它用戶(hù) 45 foreach (object c in Server.userTable.Values) 46 { 47 NetworkStream tempStream = ((TcpClient)c).GetStream(); 48 byte[] tempBuffer = SerializationHelper.Serialize(tempMessage);//序列化 49 tempStream.Write(tempBuffer, 0, tempBuffer.Length); 50 } 51 break; 52 } 53 case Cmds.messageToAll: 54 { 55 Server.richTextBox1.AppendText("接收到數(shù)據(jù)" + message.message + "\n"); 56 break; 57 } 58 default: 59 { 60 break; 61 } 62 } 63 64 65 66 Array.Clear(buffer, 0, buffer.Length);//清空緩存 67 68 streamToClient.Flush();//刷新流中數(shù)據(jù) 保留此方法供將來(lái)使用 69 70 lock (streamToClient) 71 { 72 AsyncCallback callBack = new AsyncCallback(ReadComplete); 73 streamToClient.BeginRead(buffer, 0, bufferSize, callBack, null); 74 } 75 } 76 catch (Exception ex) 77 { 78 Server.userTable.Remove(userName); 79 Server.richTextBox1.AppendText(userName + "下線(xiàn)了,!\n"); 80 //從哈希表刪除該用戶(hù) 81 82 if (streamToClient == null) 83 streamToClient.Dispose(); 84 client.Close(); 85 } 86 } 87 }
這里我們看到在實(shí)例化對(duì)象的時(shí)候定義了一個(gè)異步回調(diào)函數(shù)的委托 我們獲取streamToClient(TCP連接的數(shù)據(jù)流)之后開(kāi)始異步讀數(shù)據(jù)操作,,當(dāng)?shù)谝淮巫x操場(chǎng)完成后它將調(diào)用回調(diào)方法,回調(diào)方法處理接收到的數(shù)據(jù) 并再次進(jìn)行異步讀操作,,并在讀操作完成后回調(diào)自身再次處理數(shù)據(jù) 再次異步讀 再回調(diào)自身,,這樣形成了一個(gè)類(lèi)似在while循環(huán)中使用同步的Read()方法的效果 線(xiàn)程數(shù)會(huì)得到更有效的控制,異步編程本質(zhì)上雖然是多線(xiàn)程,,但是它在某些環(huán)境下比直接使用多線(xiàn)程有莫大的好處 這里我們用異步代替了創(chuàng)建新的線(xiàn)程或者是直接使用線(xiàn)程池 ,。服務(wù)端的線(xiàn)程數(shù)將得到真正的有效控制,并且由于是異步調(diào)用 將能充分地利用CPU提高服務(wù)端的性能.當(dāng)采用這種異步方式后 服務(wù)端處理能力的提高將是之前局域網(wǎng)聊天中服務(wù)端的成千上萬(wàn)倍,。到這里 對(duì)于線(xiàn)程數(shù),,服務(wù)端性能和充分利用CPU算有那么一個(gè)小小的成果了 采用這種方式我們可以根據(jù)服務(wù)能承載用戶(hù)數(shù)設(shè)置一些參數(shù) 這樣在監(jiān)聽(tīng)線(xiàn)程中可以加以判斷,當(dāng)在線(xiàn)用戶(hù)已達(dá)到服務(wù)器極限的時(shí)候 我們給新的客戶(hù)端服務(wù)器忙的提示,,拒絕其登陸,,就像web服務(wù)器那樣 當(dāng)連接數(shù)超過(guò)最大并發(fā)連接數(shù)的時(shí)候就返回一個(gè)錯(cuò)誤頁(yè)面
2.對(duì)于TCP連接數(shù) TCP既然是面向連接的,只要沒(méi)有顯示的關(guān)閉連接 則這個(gè)連接將一直存在 由于我剛接觸網(wǎng)絡(luò)編程不久,,對(duì)于一臺(tái)服務(wù)器是否有TCP連接數(shù)的限制也并不是很清楚 并且這些連接對(duì)服務(wù)器會(huì)產(chǎn)生怎樣的影響也不清楚,,我將繼續(xù)探索這方面的知識(shí) 。目前我有這樣的初步想法 只是在用戶(hù)請(qǐng)求關(guān)鍵數(shù)據(jù)的時(shí)候建立TCP連接 傳輸完畢后立即關(guān)閉,,需要時(shí)再連接 用完后再關(guān)閉,。
希望對(duì)網(wǎng)絡(luò)編程以及即時(shí)通訊有經(jīng)驗(yàn)的的朋友能在回復(fù)中給我提供一個(gè)大致學(xué)習(xí)的方向或者是某些建議 或者是對(duì)即時(shí)通訊軟件的工作和處理方式能有一點(diǎn)提示,或者通過(guò)電子郵箱發(fā)過(guò)來(lái)一點(diǎn)資料 |
|
來(lái)自: 修行的嘟嘟 > 《軟件開(kāi)發(fā)》