C#-网络通讯框架(一)-HPSocket

云逸星空科技工作室 / 2023-06-13 / 原文

一、序言

这里加一张图片

1、HPSocket.Net介绍

(1)HPSocket是一套高性能TCP/UDP/HTTP通信框架(适用于Windows、Linux和OSX的HP Socket),包括服务器、客户端和代理组件。
(2)它被广泛应用于各种场景中的TCP/UDP/HTTP通信应用。它提供了C/C++、C#、Delphi、Java、Python等编程语言接口。HP Socket完全封装了通信层,应用程序不需要关注通信层的任何细节。
(3)HPSocket支持的平台:Windows 7+x86/x64、Linux内核2.6.32+x86/x64、mac操作系统10.12+x64、树莓派4B。
(4)HPSocket除了TCP/UDP/HTTP协议,还支持HTTP、WebSocket等协议。

2、HPSocket.Net相对于System.Net.Sockets,具有的优势

(1)HPSocket提供了更多的协议支持,如TCP、UDP、HTTP、WebSocket等,而System.Net.Sockets只支持TCP、UDP等基本协议。
(2)HPSocket的性能更高,可以处理更多的并发连接,支持更高的吞吐量和更低的延迟。
(3)HPSocket提供了更多的高级功能,如SSL加密、压缩、心跳包等,而System.Net.Sockets只提供了基本的网络通信功能。
(4)HPSocket比System.Net.Sockets跨平台支持的早,兼容性或许更好。

3、与HPSocket差不多的Socket框架

(1)DotNetty(博客有讲)
(2)SuperSocket(可选)

二、基础使用

这里加一张图片

1、TCP

 

2、UDP

 

三、与线程池一起使用(TCP)

 

四、HPSocket扩展Easy组件(部分)

  HPSocket.Net目前提供6个Easy组件和2个WebSocket组件

  • ITcpPortForwarding
  • IHttpEasyServer
  • IHttpsEasyServer
  • IHttpEasyAgent
  • IHttpsEasyAgent
  • IHttpEasyClient
  • IHttpsEasyClient
  • IWebSocketServer
  • IWebSocketAgent
  • ITcpServer<TRequestBodyType>
  • ITcpClient<TRequestBodyType>
  • ITcpAgent<TRequestBodyType>
  • ISslServer<TRequestBodyType>
  • ISslClient<TRequestBodyType>
  • ISslAgent<TRequestBodyType>
  • AsyncQueue

1、TCP端口转发(ITcpPortForwarding)

private readonly ITcpPortForwarding _portForwarding = new HPSocket.Tcp.TcpPortForwarding();

 // tcp端口转发组件是支持通过代理连接到目标服务器的, http和socks5代理可以同时混用, 会随机挑选代理服务器, 支持无限多个
  /*
  _portForwarding.ProxyList = new List<IProxy>
  {
      // 支持http隧道代理
      new HttpProxy
      {
          Host = "127.0.0.1",
          Port = 1080,
          // 支持帐号和密码, 可选
          // UserName = "admin",
          // Password = "pass"
      },
      // 也支持socks5代理
      new Socks5Proxy
      {
          Host = "127.0.0.1",
          Port = 1080,
          // 支持帐号和密码, 可选
          // UserName = "admin",
          // Password = "pass"
      }
  };
  */

  // 事件绑定, 如果你不需要处理数据包和连接黑名单等业务, 只是纯做tcp转发, 事件也无需绑定, 直接[启动按钮]事件的代码设置好属性, 然后start()就ok了
  // 转发组件内部server组件的事件
  _portForwarding.OnServerAccept += OnServerAccept;
  _portForwarding.OnServerReceive += OnServerReceive;
  _portForwarding.OnServerClose += OnServerClose;
  // 转发组件内部agent组件的事件
  _portForwarding.OnAgentConnect += OnAgentConnect;
  _portForwarding.OnAgentReceive += OnAgentReceive;
  _portForwarding.OnAgentClose += OnAgentClose;

...
#region 方法
        // ReSharper disable once MemberCanBeMadeStatic.Local
        private HandleResult OnServerReceive(IServer sender, IntPtr connId, byte[] data)
        {
            AddLog($"OnServerReceive({connId}), data length: {data.Length}");
            return HandleResult.Ok;
        }

        // ReSharper disable once MemberCanBeMadeStatic.Local
        private HandleResult OnServerClose(IServer sender, IntPtr connId, SocketOperation socketOperation, int errorCode)
        {
            AddLog($"OnServerClose({connId}), socket operation: {socketOperation}, error code: {errorCode}");
            return HandleResult.Ok;
        }

        // ReSharper disable once MemberCanBeMadeStatic.Local
        private HandleResult OnAgentConnect(IAgent sender, IntPtr connId, IProxy proxy)
        {
            AddLog($"OnAgentConnect({connId})");
            return HandleResult.Ok;
        }

        // ReSharper disable once MemberCanBeMadeStatic.Local
        private HandleResult OnAgentReceive(IAgent sender, IntPtr connId, byte[] data)
        {
            AddLog($"OnAgentReceive({connId}), data length: {data.Length}");
            return HandleResult.Ok;
        }

        // ReSharper disable once MemberCanBeMadeStatic.Local
        private HandleResult OnAgentClose(IAgent sender, IntPtr connId, SocketOperation socketOperation, int errorCode)
        {
            AddLog($"OnAgentClose({connId}), socket operation: {socketOperation}, error code: {errorCode}");
            return HandleResult.Ok;
        }
#endregion

2、Http-Easy通讯组件-IHttpEasyServer、IHttpEasyAgent与IHttpEasyClient

// 创建 HttpEasyServer 的实例
using(IHttpEasyServer httpServer = new HttpEasyServer())
{
    // ...其他设置

    // 绑定 OnEasyMessageData 事件
    httpServer.OnEasyMessageData += (sender, id, data) => 
    {
        // data 参数每次都是一个完整的数据包
        // ... 处理 data

        return HttpParseResult.Ok;
    };
}
// 创建 HttpEasyAgent 的实例
using(IHttpEasyAgent httpAgent = new HttpEasyAgent())
{
    // ...其他设置

    // 绑定 OnEasyMessageData 事件
    httpAgent.OnEasyMessageData += (sender, id, data) => 
    {
        // data 参数每次都是一个完整的数据包
        // ... 处理 data

        return HttpParseResult.Ok;
    };
}
// 创建 HttpEasyClient 的实例
using(IHttpEasyClient httpClient = new HttpEasyClient())
{
    // ...其他设置

    // 绑定 OnEasyMessageData 事件
    httpClient.OnEasyMessageData += (sender, data) => 
    {
        // data 参数每次都是一个完整的数据包
        // ... 处理 data

        return HttpParseResult.Ok;
    };
}

3、WebSocket-Easy通讯组件IWebSocketServerIWebSocketAgent

  略

4、TCP数据接收适配器组件(BinaryDataReceiveAdapter-必学

  数据接收适配器组件比HP-Socket的Pack组件更加灵活。  

    • ITcpServer<TRequestBodyType>/ITcpClient<TRequestBodyType>/ITcpAgent<TRequestBodyType>
    • ISslServer<TRequestBodyType>/ISslClient<TRequestBodyType>/ISslAgent<TRequestBodyType>

    注:<TRequestBodyType>泛型类型对象将在‘数据接收适配器组件’的OnParseRequestBody事件中回调。

  4种适配模式:固定包头数据接收适配器、定长包数据接收适配器、结束符数据接收适配器、区间数据接收适配器。以下举例为:ITcpServer<TRequestBodyType> + DataReceiveAdapter定长包数据接收适配器

using (ITcpServer<byte[]> server = new TcpServer<byte[]>
    {
        // 指定数据接收适配器-
        DataReceiveAdapter = new BinaryDataReceiveAdapter(),
    })
{
    // 不需要绑定OnReceive事件
    // 这里解析的请求体事件的data就是BinaryDataReceiveAdapter.ParseRequestBody()返回的数据
    // data的类型就是ITcpServer<byte[]>实例化时指定的byte[]
    server.OnParseRequestBody += (sender, id, data) =>
    {
        Console.WriteLine($"OnParseRequestBody({id}) -> data length: {data.Length}");

        return HandleResult.Ok;
    };
}

(1)FixedHeaderDataReceiveAdapter固定包头数据接收适配器

使用场景:数据包的包头长度固定且包头含包体长度。

示例:前4字节标识包体长度(小端字节序)。0x00000003表示包体长度为3字节{ 0x61, 0x62 0x63 }为包体。

{ 0x03, 0x00, 0x00, 0x00, 0x61, 0x62, 0x63 }

FixedHeaderDataReceiveAdapter专为这种结构设计

using System;
using System.Text;
using HPSocket.Adapter;
using Models;
using Newtonsoft.Json;

namespace TcpServerTestEchoAdapter.DataReceiveAdapter
{
    /// <summary>
    /// 固定包头数据接收适配器
    /// ① 子类继承FixedHeaderDataReceiveAdapter;
    /// ② 在自身的构造函数中调用父类构造函数,传入包头长度和最大允许的封包长度; 
    /// ③ 覆盖GetBodySize()方法, 其参数header的长度为构造函数中指定的包头长度, 需用户解析这个参数, 返回实际的body长度 
    /// ④ 覆盖ParseRequestBody()方法, 将当前的bytes反序列化为泛型类型(<TRequestBodyType>)对象
    /// </summary>
    public class PacketDataReceiveAdapter : FixedHeaderDataReceiveAdapter<Packet>
    {
        /// <summary>
        /// 调用父类构造函数,指定固定包头的长度及最大封包长度
        /// </summary>
        public PacketDataReceiveAdapter()
            : base(
                headerSize: 4,        // 这里指定4字节包头
                maxPacketSize: 0x1000 // 这里指定最大封包长度不能超过4K
                )
        {

        }

        /// <summary>
        /// 获取请求体长度
        /// <remarks>子类必须覆盖此方法</remarks>
        /// </summary>
        /// <param name="header">包头,header的长度是构造函数里指定的长度,当接收到了指定长度的包头再调用此方法</param>
        /// <returns>返回包体长度</returns>
        protected override int GetBodySize(byte[] header)
        {
            // 根据业务场景来适配字节序, 两端字节序要保持一致

            // 如果当前环境不是小端字节序
            if (!BitConverter.IsLittleEndian)
            {
                // 转换为小端字节序
                Array.Reverse(header);
            }

            // 因为包头是4字节,所以直接转int并返回
            return BitConverter.ToInt32(header, 0);
        }

        /// <summary>
        /// 解析请求体
        /// <remarks>子类必须覆盖此方法</remarks>
        /// </summary>
        /// <param name="header">包头</param>
        /// <param name="data">包体</param>
        /// <returns></returns>
        protected override Packet ParseRequestBody(byte[] header, byte[] data)
        {
            // 将data反序列化为对象
            // 这里是Packet类的对象
            return JsonConvert.DeserializeObject<Packet>(Encoding.UTF8.GetString(data));
        }
    }
}

(2)FixedSizeDataReceiveAdapter定长包数据接收适配器

包长度固定,每个包都是同样大小,使用这种适配器。

FixedSizeDataReceiveAdapter专为这种结构设计

using HPSocket.Adapter;

namespace TcpServerTestEchoAdapter.DataReceiveAdapter
{
    /// <summary>
    /// 定长包数据接收适配器
    /// </summary>
    public class BinaryDataReceiveAdapter : FixedSizeDataReceiveAdapter<byte[]>
    {
        /// <summary>
        /// 调用父类构造函数,指定定长包长度
        /// </summary>
        public BinaryDataReceiveAdapter()
            : base(
                packetSize: 1024 // 定长包包长1K字节
            )
        {
        }

        /// <summary>
        /// 解析请求体
        /// <remarks>子类必须覆盖此方法</remarks>
        /// </summary>
        /// <param name="data">父类处理好的定长数据</param>
        /// <returns></returns>
        protected override byte[] ParseRequestBody(byte[] data)
        {
            // 因为继承自FixedSizeDataReceiveAdapter<byte[]>,所以这里直接返回了,如果是其他类型请做完转换工作再返回
            return data;
        }
    }
}

(3)TerminatorDataReceiveAdapter结束符数据接收适配器

包头无特征, 包尾使用特定标志作为结束符,使用这种适配器

示例:下面这种包结构以\r\n结尾

hello world 1\r\n
hello world 2\r\n
hello world 3\r\n

TerminatorDataReceiveAdapter专为这种结构设计

using System.Text;
using HPSocket.Adapter;

namespace TcpServerTestEchoAdapter.DataReceiveAdapter
{
    /// <summary>
    /// 结束符数据接收适配器
    /// </summary>
    public class TextDataReceiveAdapter : TerminatorDataReceiveAdapter<string>
    {
        /// <summary>
        /// 调用父类构造函数,指定结束符
        /// </summary>
        public TextDataReceiveAdapter()
            : base(
                terminator: Encoding.UTF8.GetBytes("\r\n") // 指定结束符为\r\n,也就是说每条数据以\r\n结尾,注意编码问题,要两端保持一致
                )
        {
        }

        /// <summary>
        /// 解析请求体
        /// <remarks>子类必须覆盖此方法</remarks>
        /// </summary>
        /// <param name="data">父类已经处理好的不含结束符的数据</param>
        /// <returns></returns>
        protected override string ParseRequestBody(byte[] data)
        {
            // 转换成请求对象, 注意字符编码,要两端保持一致
            return Encoding.UTF8.GetString(data);
        }
    }
}

(4)BetweenAndDataReceiveAdapter区间数据接收适配器

示例:数据包头以某特征符号开始,包尾以其它某特征符号结束

##hello world!## // ##开头,##结尾
或
##hello world!|| // ##开头,||结尾
或
**hello world!|##| // **开头,|##|结尾

BetweenAndDataReceiveAdapter专为这种结构设计

using System.Text;
using HPSocket.Adapter;

namespace TcpServerTestEchoAdapter.DataReceiveAdapter
{
    /// <summary>
    /// 区间数据接收适配器
    /// </summary>
    public class HeadTailDataReceiveAdapter : BetweenAndDataReceiveAdapter<string>
    {
        /// <summary>
        /// 调用父类构造函数,指定区间开始和结束特征字符
        /// </summary>
        public HeadTailDataReceiveAdapter() 
            : base( // 例如数据格式是"#*123456*#",其中以#*开头,以*#结尾,中间123456部分是真实数据
                start : Encoding.UTF8.GetBytes("#*"), // 区间起始标志,这里以#*开始,注意编码问题,要两端保持一致
                end : Encoding.UTF8.GetBytes("*#")  // 区间结束标志,这里以*#结束,注意编码问题,要两端保持一致
                )
        {
        }

        /// <summary>
        /// 解析请求体
        /// <remarks>子类必须覆盖此方法</remarks>
        /// </summary>
        /// <param name="data">父类已处理好的不含区间起始标识符的数据</param>
        /// <returns></returns>
        protected override string ParseRequestBody(byte[] data)
        {
            // 转换成请求对象,注意编码问题,要两端保持一致
            return Encoding.UTF8.GetString(data);
        }
    }
}