在.NET上編寫網絡服務深入都有2,3年了,而這些時間時如何在.NET里實現網絡服務積累了一些經驗.在接下來的時間里會把這方面的經驗通過博客的方式分享出來.而這一章主要是講解在如果提高服務連接接入的效率,從而讓服務連接接入的並發量有高吞吐的性能.
其實.NET提供了一個非常強大的網絡模型給我們使用,而我們只需要把這個模型用好那基於是不存在多大問題.不過由於很多開發人員對這方面並沒有了解和深入所以感覺.Net編寫一個高效能的服務比較因難.下面通過不同的示例來描述問題的所在從而避免這些問題的出現,讓編寫的服務更高效.
示例1
try { listener.Bind(localEndPoint); listener.Listen(10); // Start listening for connections. while (true) { Console.WriteLine("Waiting for a connection..."); // Program is suspended while waiting for an incoming connection. Socket handler = listener.Accept(); data = null; // An incoming connection needs to be processed. while (true) { bytes = new byte[1024]; int bytesRec = handler.Receive(bytes); data += Encoding.ASCII.GetString(bytes,0,bytesRec); if (data.IndexOf("<EOF>") > -1) { break; } } // Show the data on the console. Console.WriteLine( "Text received : {0}", data); // Echo the data back to the client. byte[] msg = Encoding.ASCII.GetBytes(data); handler.Send(msg); handler.Shutdown(SocketShutdown.Both); handler.Close(); } } catch (Exception e) { Console.WriteLine(e.ToString()); }
這是從MSDN得到的一個服務端示例的代碼地址來源http://msdn.microsoft.com/en-us/library/6y0e13d3(v=vs.110).aspx以上代碼說實話真沒多大的參考意義,不過作為一個演示如何構建一個服務監聽那還是起到作用;用在服務器應用上是完全不可行,因為這只會導致會話串行,同時只能處理一個.接下來在網上找一個支持連接並發的示例看一下
示例2
namespace SocketServer { class Program { private static byte[] result = new byte[1024]; private static int myProt = 8885; //端口 static Socket serverSocket; static void Main(string[] args) { //服務器IP地址 IPAddress ip = IPAddress.Parse("127.0.0.1"); serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); serverSocket.Bind(new IPEndPoint(ip, myProt)); //綁定IP地址:端口 serverSocket.Listen(10); //設定最多10個排隊連接請求 Console.WriteLine("啟動監聽{0}成功", serverSocket.LocalEndPoint.ToString()); //通過Clientsoket發送數據 Thread myThread = new Thread(ListenClientConnect); myThread.Start(); Console.ReadLine(); } /// <summary> /// 監聽客戶端連接 /// </summary> private static void ListenClientConnect() { while (true) { Socket clientSocket = serverSocket.Accept(); clientSocket.Send(Encoding.ASCII.GetBytes("Server Say Hello")); Thread receiveThread = new Thread(ReceiveMessage); receiveThread.Start(clientSocket); } } /// <summary> /// 接收消息 /// </summary> /// <param name="clientSocket"></param> private static void ReceiveMessage(object clientSocket) { Socket myClientSocket = (Socket)clientSocket; while (true) { ... int receiveNumber = myClientSocket.Receive(result); Console.WriteLine("接收客戶端{0}消息{1}", myClientSocket.RemoteEndPoint.ToString(), Encoding.ASCII.GetString(result, 0, receiveNumber)); } } } }
以上示例可以接受多個連接同時進行處理,但缺點是非常明顯如果服務支撐的連接數比較大的情況那這種方式是不可行.你想象一下如果這個服務端要支撐1W,3W或者10W連接的情況那需要開多少個線程去處理這些連接呢,即使可以這樣做那線程的開銷也足夠讓服務器受的了.接下來看MSDN提供的異步示例
示例3
while (true) { // Set the event to nonsignaled state. allDone.Reset(); // Start an asynchronous socket to listen for connections. Console.WriteLine("Waiting for a connection..."); listener.BeginAccept( new AsyncCallback(AcceptCallback), listener ); // Wait until a connection is made before continuing. allDone.WaitOne(); } public static void AcceptCallback(IAsyncResult ar) { // Signal the main thread to continue. allDone.Set(); // Get the socket that handles the client request. Socket listener = (Socket) ar.AsyncState; Socket handler = listener.EndAccept(ar); // Create the state object. StateObject state = new StateObject(); state.workSocket = handler; handler.BeginReceive( state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReadCallback), state); }
這個示例來源於http://msdn.microsoft.com/en-us/library/6y0e13d3(v=vs.110).aspx,其實這個代碼已經非常高效的體現我們在編寫服務的時候如何實現一個接入監聽.此示例用在應用開發上完全勝任的.
改進
以上示例3已經提供非常不錯的代碼,那是否可以進行一些規范的改進呢.其實在過往的經驗中來看是可以,首先我們了解.NET有兩種線程,一種是我們常用的而別一種則是IO線程.其實用一些測試工具可以看到AcceptCallback是由IO線程回調,那我們希望回調線程更快速度的釋放出來,那我們需要做一些隔離上的規划.其實在很多范例代碼中都是一連串地把事件做完,接入->接收->發送.這樣一個連貫性的代碼實現導致后其線程資源的控制和規划就變得非常因難.
從代碼設計可以通過隊列把回調線程需要的工作隔離出來,可以讓回調線程更快的歸隊來處理其接接入的工作.當隔離后即使以后連接接入需要加一些邏輯控制也不會影響回調線程的快速回歸.這樣就可以讓整個異步線程資源更高效.
public static void AcceptCallback(IAsyncResult ar) { // Signal the main thread to continue. allDone.Set(); // Get the socket that handles the client request. Socket listener = (Socket) ar.AsyncState; Socket handler = listener.EndAccept(ar); Queue.Enqueue(handler); }
總結
其實隊列分離和控制在整個網絡通訊實施過程會經常用到,其主要作是處理資源的分塊和線程資源控制.畢竟任何一台服務器的資源都是有限的,如何分配線程資源給不同的任何來完成工作是非常重要,畢竟大量線程的開銷會對系統造成比較大的壓力.