天天看點

【RRQMSocket】C# 建立高并發、高性能TCP架構,可用于解析HTTP、FTP、MQTT、搭建遊戲伺服器等

說起TCP大家肯定都不陌生,傳輸控制協定(TCP,Transmission Control Protocol)是一種面向連接配接的、可靠的、基于位元組流的傳輸層通信協定,由IETF的RFC 793 定義。

TCP旨在适應支援多網絡應用的分層協定層次結構。 連接配接到不同但互連的計算機通信網絡的主計算機中的成對程序之間依靠TCP提供可靠的通信服務。TCP假設它可以從較低級别的協定獲得簡單的,可能不可靠的資料報服務。 原則上,TCP應該能夠在從硬線連接配接到分組交換或電路交換網絡的各種通信系統之上操作。

那麼

對于C#又應該怎麼使用TCP呢,實際上微軟已經設定了TCP協定的抽象模型類,也就是大家熟知的Socket,以及更進階的TcpClient等,諸如此類的使用,這裡也不做多解釋。但是對于高并發,高性能等問題,這些偏底層的類就需要自己開發了,是以這也是我自己開發

RRQMSocket

的初衷,希望這個程式集能給你帶來驚喜。

所需工具:

  1. VS2019及以上版本
  2. Internet網絡
  3. 電腦一台
  4. 還有必不可少的程式集。RRQMSocket(後面介紹)

文章目錄

    • 一、概述
    • 一、程式集源碼、Demo下載下傳及引用
        • 1.1 源碼位置
        • 1.2 Demo位置
        • 1.3 引用
    • 二、建立TcpService
        • 2.1 建立TcpService繼承類
        • 2.2 建立SocketClient繼承類
        • 2.3 啟動伺服器
    • 三、TcpClient
        • 3.1 建立TcpClient的繼承類
        • 3.2 配置、連接配接
    • 四、用戶端發送
        • 4.1 同步發送
        • 4.2 非獨立線程異步發送
        • 4.3 獨立線程異步發送
    • 五、伺服器發送
        • 5.1 SocketClient發送
            • 5.1.1、同步發送
            • 5.1.2 非獨立線程異步發送(伺服器不允許獨立線程發送)
        • 5.2 TcpService發送

一、概述

準備好了以上工具,我們就開始吧!

首先我們先來介紹一下該程式集的一些功能

  1. 簡單易用。
  2. 多線程。
  3. 多位址監聽(可以一次性監聽多個IP及端口)
  4. 擴充卡預處理,一鍵式解決分包、粘包、對象解析(如HTTP,Json)等。
  5. 超簡單的同步發送、異步發送、接收等操作。
  6. 基于事件驅動,讓每一步操作盡在掌握。
  7. 高性能(伺服器每秒可接收200w條資訊)
  8. 獨立線程記憶體池(每個線程擁有自己的記憶體池)

一、程式集源碼、Demo下載下傳及引用

1.1 源碼位置

RRQMSocket

1.2 Demo位置

RRQMBox

1.3 引用

在VS裡面的NuGet程式包功能裡面,聯網的情況下搜尋“RRQMSocket”,如果版本較低或者是破解版的話,就去Nuget官方網站自行下載下傳,然後解壓自行引用。

【RRQMSocket】C# 建立高并發、高性能TCP架構,可用于解析HTTP、FTP、MQTT、搭建遊戲伺服器等
【RRQMSocket】C# 建立高并發、高性能TCP架構,可用于解析HTTP、FTP、MQTT、搭建遊戲伺服器等

二、建立TcpService

TcpService是TCP伺服器基類,但是不參與實際的資料互動,實際的資料互動由SocketClient完成,是以TcpService的功能隻是配置、激活、管理、登出、重建SocketClient類執行個體,此二者均為抽象類,不可直接建立其執行個體。于TcpService而言,須實作OnCreatSocketCliect方法,該方法訓示激活SocketClient時的一些必要操作。于SocketClient而言,必須實作HandleReceivedData方法,該方法訓示如何處理已接收資料或經過擴充卡轉換的對象。是以具體建立過程如下。

2.1 建立TcpService繼承類

通過繼承的方式,實作OnCreatSocketCliect方法,并在初始化設定中設定具體的分包政策。

public class MyTcpService : TcpService<MySocketClient>
    {
        protected override void OnCreateSocketCliect(MyTcpSocketClient socketClient, CreateOption createOption)
        {
            if (createOption.NewCreat)
            {
                socketClient.DataHandlingAdapter = new NormalDataHandlingAdapter();//普通TCP封包處理器
            }
        }
    }
           

2.2 建立SocketClient繼承類

繼承實作HandleReceivedData方法,才能具體的處理資料。

public class MySocketClient : SocketClient
{
    protected override void HandleReceivedData(ByteBlock byteBlock, object obj)
    {
            string mes = Encoding.UTF8.GetString(byteBlock.Buffer, 0, (int)byteBlock.Length);
            Console.WriteLine($"已接收到資訊:{mes},第{count}條");
    }
}
           

2.3 啟動伺服器

TcpService<MySocketClient> service = new TcpService<MySocketClient>();

//訂閱事件
//service.ClientConnected += Service_ClientConnected;//訂閱連接配接事件
//service.ClientDisconnected += Service_ClientDisconnected;//訂閱斷開連接配接事件

 //注入配置
 var config = new ServerConfig();
 config.SetValue(ServerConfig.ListenIPHostsProperty, new IPHost[] { new IPHost(this.Tb_iPHost.Text) })
     .SetValue(ServerConfig.LoggerProperty, new MsgLog(this.ShowMsg))//設定内部日志記錄器
     .SetValue(ServerConfig.ThreadCountProperty, int.Parse(this.Tb_ThreadCount.Text))//設定多線程數量
     .SetValue(TcpServerConfig.ClearIntervalProperty, 300)//300秒無資料互動将被清理
     .SetValue(ServerConfig.BufferLengthProperty, 1024);//設定緩存池大小,該數值在架構中經常用于申請ByteBlock,是以該值會影響記憶體池效率。

 //載入配置                                                       
 tcpService.Setup(config);

 //或通過執行個體注入配置,執行個體注入時須執行個體化對應配置,否則部分屬性不可見
 //var config1 = new TcpServerConfig();
 //config1.ListenIPHosts = new IPHost[] { new IPHost(this.Tb_iPHost.Text) };
 //config1.Logger = new MsgLog(this.ShowMsg);
 //config1.ThreadCount = int.Parse(this.Tb_ThreadCount.Text);
 //config1.ClearInterval = 300;
 //config1.BufferLength = 1024;

 //載入配置                                                       
 //tcpService.Setup(config1);


 //啟動
 tcpService.Start();

           

三、TcpClient

TcpClient是TCP用戶端的基類,為抽象類,不可建立執行個體,須通過繼承實作HandleReceivedData方法,該方法訓示如何處理接收到的資料。

3.1 建立TcpClient的繼承類

public class MyTcpClient : TcpClient
{
    protected override void HandleReceivedData(ByteBlock byteBlock, object obj)
    {
        string mes = Encoding.UTF8.GetString(byteBlock.Buffer, 0, (int)byteBlock.Length);
    }
}

           

3.2 配置、連接配接

private void CreateTcpClient()
{
    MyTcpClient  tcpClient = new MyTcpClient ();
    //tcpClient.ConnectedService += this.TcpClient_ConnectedService;//訂閱連接配接成功事件
    //tcpClient.DisconnectedService += this.TcpClient_DisconnectedService;//訂閱斷開連接配接事件
    
    try
    {
        var config = new TcpClientConfig();
        config.SetValue(TcpClientConfig.RemoteIPHostProperty, new IPHost(this.Tb_iPHost.Text))//遠端IPHost
            .SetValue(TcpClientConfig.SeparateThreadSendProperty, true)//獨立線程發送
            .SetValue(TcpClientConfig.DataHandlingAdapterProperty,new NormalDataHandlingAdapter());//資料處理擴充卡

        tcpClient.Setup(config);//載入配置
        tcpClient.Connect();//連接配接
    }
    catch (Exception ex)
    {
        ShowMsg(ex.Message);
    }
}


           

四、用戶端發送

4.1 同步發送

使用方法

TcpClient已經内置了三種同步發送方法,直接調用就可以發送。如果發送失敗,則會立即抛出異常。

public virtual void Send(byte[] buffer);
public virtual void Send(ByteBlock byteBlock);
public virtual void Send(byte[] buffer, int offset, int length);
           

調用順序

  1. TcpClient.Send;

  2. DataHandlingAdapter.PreviewSend;(進入資料處理擴充卡)

  3. DataHandlingAdapter.GoSend;(通過資料處理擴充卡封裝資料)

  4. Socket.Send;(通過Socket發送資料)

4.2 非獨立線程異步發送

使用方法

TcpClient已經内置了三種異步發送方法,直接調用就可以發送。如果發送失敗,會通過 日志輸出 , 不會觸發異常 。

public virtual void SendAsync(byte[] buffer);
public virtual void SendAsync(ByteBlock byteBlock);
public virtual void SendAsync(byte[] buffer, int offset, int length);
           

調用順序

  1. TcpClient.Send;

  2. DataHandlingAdapter.PreviewSend;(進入資料處理擴充卡)

  3. DataHandlingAdapter.GoSend;(通過資料處理擴充卡封裝資料)

  4. Socket.SendAsync;(通過Socket IOCP發送資料)

4.3 獨立線程異步發送

說明

獨立線程異步發送資料顧名思義,就是采用單獨的線程完成發送操作,其整體發送效率較高。如果發送失敗,會通過 日志輸出 , 不會觸發異常 。

使用方法

使用與TcpClient異步發送一緻,隻需在配置中注入TcpClientConfig.SeparateThreadSendProperty的值為True即可。如果發送失敗,會通過 日志輸出 , 不會觸發異常 。

public virtual void SendAsync(byte[] buffer);
public virtual void SendAsync(ByteBlock byteBlock);
public virtual void SendAsync(byte[] buffer, int offset, int length);
           

調用順序

  1. TcpClient.Send;

  2. DataHandlingAdapter.PreviewSend;(進入資料處理擴充卡)

  3. DataHandlingAdapter.GoSend;(通過資料處理擴充卡封裝資料)

  4. AsyncSender.AsyncSend;(通過AsyncSend把資料壓入隊列,同時喚醒發送線程)

  5. AsyncSender.tryGet;(從隊列擷取待發送資料,并将小資料壓縮至已設定的長度)

  6. Socket.SendAsync;(通過Socket IOCP發送資料)

五、伺服器發送

5.1 SocketClient發送

每個用戶端成功連接配接後,都會建立(或擷取)一個派生自SocketClient的執行個體,通過該執行個體即可将資料發送至用戶端。使用方法和TcpClient一緻。

5.1.1、同步發送

** 使用方法**

TcpClient已經内置了三種同步發送方法,直接調用就可以發送。如果發送失敗,則會立即抛出異常。

public virtual void Send(byte[] buffer);
public virtual void Send(ByteBlock byteBlock);
public virtual void Send(byte[] buffer, int offset, int length);
           

** 調用順序**

  1. TcpClient.Send;

  2. DataHandlingAdapter.PreviewSend;(進入資料處理擴充卡)

  3. DataHandlingAdapter.GoSend;(通過資料處理擴充卡封裝資料)

  4. Socket.Send;(通過Socket發送資料)

5.1.2 非獨立線程異步發送(伺服器不允許獨立線程發送)

使用方法

TcpClient已經内置了三種異步發送方法,直接調用就可以發送。如果發送失敗,會通過 日志輸出 , 不會觸發異常 。

public virtual void SendAsync(byte[] buffer);
public virtual void SendAsync(ByteBlock byteBlock);
public virtual void SendAsync(byte[] buffer, int offset, int length);
           

調用順序

  1. TcpClient.Send;

  2. DataHandlingAdapter.PreviewSend;(進入資料處理擴充卡)

  3. DataHandlingAdapter.GoSend;(通過資料處理擴充卡封裝資料)

  4. Socket.SendAsync;(通過Socket IOCP發送資料)

5.2 TcpService發送

實際上每個SocketClient成功連接配接後,都會擁有一個ID,是以可通過ID發送資料。

public virtual void Send(string id, ByteBlock byteBlock);
public virtual void Send(string id, byte[] buffer, int offset, int length);
public virtual void Send(string id, byte[] buffer);

public virtual void SendAsync(string id, ByteBlock byteBlock);
public virtual void SendAsync(string id, byte[] buffer, int offset, int length);
public virtual void SendAsync(string id, byte[] buffer);
           

亦或者,通過ID在TcpService的SocketClients屬性中先查找到TcpSocketClient的繼承執行個體,然後通過執行個體,直接發送。

SocketClient socketClient = TcpService.SocketClients["1-TCP"];
socketClient.Send(new byte[] { 1, 2, 3 });