.NET 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到這個請求,仍然處理等待接入狀態。

發佈了221 篇原創文章 · 獲贊 3 · 訪問量 33萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章