來源:中國IT實驗室
今天,我們來講一下在。NET 網路應用程式開發中同步Socket的應用,很多人認為在網路應用的服務端Socket不應該使用同步Socket.是的,在大多數情況下是這樣的,但是也有一些情境下我們使用同步Socket可能會得到更的結果。如在下面的兩種情境下我們便可以考慮使用同步的Socket.
一、用戶端數量比較少:
數量比較少是指會同時串連到伺服器的用戶端數量一般在50人以下。這種情況下我們可以考慮使用同步Socket+Thread來實現我們的服務端。這樣會讓我們編寫邏輯更清晰的代碼而效能不會下降太多。
二、用戶端數量較多但都是短串連:
短串連是指用戶端的串連在處理完一次收發之後就產即斷開的情境,比如說HTTP協議就是一種短串連。HTTP在用戶端發出請求時建立一個Socket串連,並通過Socket發出一個URL請求,服務端在處理完這個請求並回傳相應的頁面後便會斷開這個串連。那麼在這種情境下我們也可以使用同步Socket來實現我們的需求。
那麼應該如果實現我上面提到的兩種需求呢。對於這兩種需求,我將採用不同的方案來實現它們。
首先我們來看看第一種需求,這裡我採用Socket+Thread來實現,基本的流程如下:
首先建立一個Socket,並且給它綁定一個EndPoint後開始監聽。接下來我們建立一個線程,在這個線程中我們用一個無限迴圈來接收來自用戶端的串連請求。在接收到一個請求後,為這個用戶端建立一個新的線程,並且在這個線程中也使用一個無限迴圈接收來自這個用戶端的資料。下面讓我們來看看代碼:
首先我們建立一個Socket用來偵聽用戶端的串連:
Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); IPEndPoint locEP= new IPEndPoint(IPAddress.Any, 2000); listener.Bind(locEP); listener.Listen(100); |
然後建立一個線程來處理用戶端的串連請求:
Thread acceptThread = new Thread(new ThreadStart(AcceptWorkThread)); acceptThread.Start(); private void AcceptWorkThread() ...{ Thread.CurrentThread.IsBackground = true; while (true) ...{ Socket accept = listener.Accept(); IPEndPoint remoEP = (IPEndPoint)accept.RemoteEndPoint; string recString = "接收到來自" + remoEP.Address.ToString() + "的串連。"; this.Invoke(new AddListItemHandler(this.AddListItem), new string[] ... { recString }); Thread receiveThread = new Thread(new ParameterizedThreadStart (ReceiveWorkThread)); receiveThread.Start(accept); } } |
最後我們來看看如何接收資料:
private void ReceiveWorkThread(object obj) ...{ Thread.CurrentThread.IsBackground = true; Socket socket = (Socket)obj; byte[] buffer = new byte[1024]; while (true) ...{ int receiveCount = socket.Receive(buffer); if (receiveCount > 0) ...{ IPEndPoint remoEP = (IPEndPoint)socket.RemoteEndPoint; string recString = "來自用戶端" + remoEP.Address.ToString() + "的 訊息:" + Encoding.Default.GetString(buffer, 0, receiveCount); this.Invoke(new AddListItemHandler(this.AddListItem), new string[] ...{ recString }); socket.Send(buffer, receiveCount, SocketFlags.None); } else ...{ socket.Close(); break; } } } |
好了,整個實現就完成了。
現在讓我們來看看第二個需求:
這個方案我們將採用另外一個方法來實現,為什麼不採用上一個方法來實現呢?讓我們來分析一下。我們知道,在上一個實現中,每接入一個用戶端就要建立一個線程,如果有大量的用戶端接入的話,就會建立過多的線程。但是如果線程過多的話,Windows就需要更多的CPU時間來切換線程的上下文(這也是上一個實現不能接入很多用戶端的原因)。
我們知道,在這個方案中每一個串連都是短串連。而且順序都是固定的。都是:接入->接收->發送這樣的順序,那麼我們就可以在一個方法中完成整個處理。這樣,我們就可以利用線程池來實現我們所需要的。好了,讓我們用代碼來說話吧:
首先我們建立一個Socket用來偵聽用戶端的串連:
Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); IPEndPoint locEP= new IPEndPoint(IPAddress.Any, 2000); listener.Bind(locEP); listener.Listen(100); |
接下來我們要建立一個線程池:
Thread[] ClientThreadList = new Thread[30]; foreach (Thread th in ClientThreadList) ...{ th = new Thread(new ThreadStart(ClientWorkThread)); th.Start(); } |
最後讓我們看看線程都要做些什麼:
private void ClientWorkThread() ...{ byte[] buffer = new byte[1024]; while (true) ...{ Socket socket = listener.Accept(); string recString = "接收到來自" + remoEP.Address.ToString() + "的串連。 "; this.Invoke(new AddListItemHandler(this.AddListItem), new string[] ...{ recString }); int receCount = socket.Receive(buffer); if (receCount>0) ...{ string recString = "來自用戶端" + remoEP.Address.ToString() + " 的訊息:" + Encoding.Default.GetString(buffer, 0, receiveCount); this.Invoke(new AddListItemHandler(this.AddListItem), new string[] ...{ recString }); socket.Send(buffer, receCount, SocketFlags.None); } socket.Shutdown(SocketShutdown.Both); socket.Close(); } } |
為什麼我們要這樣做呢?
首先我們建立了一個Socket用於偵聽用戶端的串連請求,接下我們建立了一個擁有30個線程的線程池。並在每個線程中實現了Accept、Receive、Send和Close(),以完成串連、接收、發送、關閉的操作。
現在我們假設有一個客戶串連到伺服器了,這時會有一個線程Accept到這個請求,並開始接收用戶端發送過來的資料,接收到資料之後處理完髮送給用戶端,然後關閉這個串連,再次進入等待串連狀態。而其它29個線程由於沒有Accept到這個請求,仍然處理等待接入狀態。