C#-网络通讯框架(一)-HPSocket
一、序言
这里加一张图片
1、HPSocket.Net介绍
2、HPSocket.Net相对于System.Net.Sockets,具有的优势
3、与HPSocket差不多的Socket框架
二、基础使用
这里加一张图片
1、TCP
2、UDP
三、与线程池一起使用(TCP)
四、HPSocket扩展Easy组件(部分)
HPSocket.Net目前提供6个Easy组件和2个WebSocket组件
ITcpPortForwardingIHttpEasyServerIHttpsEasyServerIHttpEasyAgentIHttpsEasyAgentIHttpEasyClientIHttpsEasyClientIWebSocketServerIWebSocketAgentITcpServer<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通讯组件IWebSocketServer与IWebSocketAgent
略
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);
}
}
}