创建TcpClient
一、说明
TcpClient是Tcp系客户端基类,他直接参与tcp的连接、发送、接收、处理、断开等,他的业务与服务器的SocketClient是一一对应的。
二、特点
- 简单易用。
- IOCP多线程。
- 内存池支持
- 高性能
- 适配器预处理,一键式解决分包、粘包、对象解析(如HTTP,Json)等。
- 超简单的同步发送、异步发送、接收等操作。
- 基于委托、插件驱动,让每一步都能执行AOP。
三、产品应用场景
- 所有Tcp基础使用场景:可跨平台、跨语言使用。
- 自定义协议解析场景:可解析任意数据格式的TCP数据报文。
四、可配置项
可配置项
SetBufferLength
发送、接收缓存容量(单位:byte),默认1024×64。设置建议:
- 如果数据包较小,建议10k左右的值。更加节约内存。
- 如果数据包较大,例如文件传输等,建议64k,甚至更大的值。
- 该值虽然无上限,但是一般不要超过1Mb,不然不仅没意义,还很浪费
SetMaxPackageSize
数据包最大值(单位:byte),默认1024×1024×10。该值会在适当时间,直接作用DataHandlingAdapter.MaxPackageSize。
SetThreadCount
多线程数量。该值在Auto模式下指示线程池的最少线程数量和IO线程数量。
设置建议:
- 异步处理接收数据,此时线程数量设置为内核线程左右的值即可。
- 同步处理接收数据,此时应当考虑两个因素。该操作是否为耗时操作,如果是,则该值在允许范围内,应当设置更可能大的值。如果不是,则设置为内核线程左右的值即可。
SetGetDefaultNewID
配置初始ID的分配策略
SetListenIPHosts
监听IP和端口号组,可以一次性设置多个地址。
SetServerName
服务器标识名称,无实际使用意义。
SetBacklogProperty
Tcp半连接挂起连接队列的最大长度。默认为30
SetMaxCount
最大可连接数,默认为10000
SetReceiveType
接收类型。
- AUTO:自动接收模式。
- None:不投递IO接收申请,用户可通过GetStream,获取到流以后,自己处理接收。注意:连接端不会感知主动断开。
UsePlugin
是否启用插件。在启用时或许会带来一点点性能损耗,基本上不是千万数据交互根本不值一提。
SetServiceSslOption
Ssl配置,为Null时则不启用。
UseNoDelay
设置Socket的NoDelay属性,默认false。
UseDelaySender
使用延迟发送。众所周知,tcp数据报文为了发送效率,会默认启用延迟算法。但是这种设置,只能一定程度的缓解小数据发送效率低的问题,因为它为了保证多线程发送的有序性,在send函数中设置了线程同步,所以说,每调用一次send,实际上都是巨大的性能消耗(此处用iocp发送亦然)。所以,要解决该问题, 最终还是要将小数据,组合成大数据,这样才能更高效率的发送。所以,DelaySender正是负责此类工作的。
使用DelaySender,会一定程度的降低发送的及时性,但是降低程度并不高,简单来说:
- 如果一个包大于512kb,则不会延迟,直接发送。
- 如果发送第一个包,与第二个包的时间间隔小于一个线程池线程调度的时间(这个时间极短,一般来说会在10微秒左右),则会将这两个包压缩为一个包发送。
UseReuseAddress
启用端口复用。该配置可在服务器、或客户端在监听端口时,运行监听同一个端口。可以一定程度缓解端口来不及释放的问题。
SetRemoteIPHost
链接到的远程IPHost,支持域名。支持类型:
- 使用IP&Port,传入形如:127.0.0.1:7789的字符串即可。
- 使用域名,必须包含协议类型,形如:http://baidu.com或者https://baidu.com:80
SetClientSslOption
客户端Ssl配置,为Null时则不启用。 注意,当RemoteIPHost使用https、wss的域名时,该配置会使用系统默认配置生效。
SetKeepAliveValue
为Socket设置的属性。 注意:该配置仅在window平台生效。
SetBindIPHost
绑定端口。
- 在UdpSessionBase中表示本地监听地址
- 在TcpClient中表示固定客户端端口号。
UseDelaySender
使用延迟发送。众所周知,tcp数据报文为了发送效率,会默认启用延迟算法。但是这种设置,只能一定程度的缓解小数据发送效率低的问题,因为它为了保证多线程发送的有序性,在send函数中设置了线程同步,所以说,每调用一次send,实际上都是巨大的性能消耗(此处用iocp发送亦然)。所以,要解决该问题, 最终还是要将小数据,组合成大数据,这样才能更高效率的发送。所以,DelaySender正是负责此类工作的。
使用DelaySender,会一定程度的降低发送的及时性,但是降低程度并不高,简单来说:
如果一个包大于512kb,则不会延迟,直接发送。 如果发送第一个包,与第二个包的时间间隔小于一个线程池线程调度的时间(这个时间极短,一般来说会在10微秒左右),则会将这两个包压缩为一个包发送。
UseNoDelay
设置Socket的NoDelay属性,默认false。
UseBroadcast
该值指定可以发送或接收广播数据包。
五、支持插件
支持ITcpPlugin接口,或者继承自TcpPluginBase类,重写相应方法即可。
插件方法 | 功能 |
---|---|
OnConnecting | 在Socket完成初始化,但是并未连接时触发。 |
OnConnected | 在Socket完成连接,且成功后触发 |
OnDisconnecting | 当客户端主动调用Close时触发 |
OnDisconnected | 当客户端断开连接后触发 |
OnReceivingData | 在收到原始数据时触发,所有的数据均在ByteBlock里面。 |
OnReceivedData | 在收到适配器数据时触发,根据适配器类型,数据可能在ByteBlock或者IRequestInfo里面。 |
OnSendingData | 当即将发送数据时,调用该方法在适配器之后,接下来即会发送数据。 |
六、创建TcpClient
简单的处理逻辑可通过Connecting、Connected、Received等委托直接实现。
代码如下:
TcpClient tcpClient = new TcpClient();
tcpClient.Connected += (client, e) => { };//成功连接到服务器
tcpClient.Disconnected += (client, e) => { };//从服务器断开连接,当连接不成功时不会触发。
tcpClient.Received += (client, byteBlock, requestInfo) =>
{
//从服务器收到信息
string mes = Encoding.UTF8.GetString(byteBlock.Buffer, 0, byteBlock.Len);
Console.WriteLine($"接收到信息:{mes}");
};
//声明配置
TouchSocketConfig config = new TouchSocketConfig();
config.SetRemoteIPHost(new IPHost("127.0.0.1:7789"))
.UsePlugin();
//载入配置
tcpClient.Setup(config);
tcpClient.Connect();
tcpClient.Send("RRQM");
七、接收数据
在TcpClient中,接收数据的方式有很多种。多种方式可以组合使用。
7.1 Received委托处理
当使用TcpClient创建客户端时,内部已经定义好了一个外置委托Received,可以通过该委托直接接收数据。
TcpClient tcpClient = new TcpClient();
tcpClient.Received += (client, byteBlock, requestInfo) =>
{
//从服务器收到信息
string mes = Encoding.UTF8.GetString(byteBlock.Buffer, 0, byteBlock.Len);
Console.WriteLine($"接收到信息:{mes}");
};
//声明配置
TouchSocketConfig config = new TouchSocketConfig();
config.SetRemoteIPHost(new IPHost("127.0.0.1:7789"))
.UsePlugin();
//载入配置
tcpClient.Setup(config);
tcpClient.Connect();
7.2 插件处理推荐
按照TouchSocket的设计理念,使用插件处理数据,是一项非常简单,且高度解耦的方式。步骤如下:
- 服务器配置启用 插件(UsePlugin)
- 新建插件类
- 添加插件
代码如下:
(1)声明插件
public class MyPlugin : TcpPluginBase<TcpClient>
{
public MyPlugin()
{
this.Order = 0;//此值表示插件的执行顺序,当多个插件并存时,该值越大,越在前执行。
}
protected override void OnReceivedData(TcpClient client, ReceivedDataEventArgs e)
{
//这里处理数据接收
//根据适配器类型,e.ByteBlock与e.RequestInfo会呈现不同的值,具体看文档=》适配器部分。
ByteBlock byteBlock = e.ByteBlock;
IRequestInfo requestInfo = e.RequestInfo;
//e.Handled = true;//表示该数据已经被本插件处理,无需再投递到其他插件。
base.OnReceivedData(client, e);
}
}
(2)创建使用插件处理的客户端
TcpClient client = new TcpClient();
client.Setup(new TouchSocketConfig()
.SetRemoteIPHost(new IPHost("127.0.0.1:7789"))
.UsePlugin()
.ConfigureContainer(a=>
{
a.AddConsoleLogger();
})
.ConfigurePlugins(a =>
{
a.Add<MyPlugin>();
}))
.Connect();
八、发送数据
【同步发送】
TcpClient已经内置了三种同步发送方法,直接调用就可以发送,但需要注意的是,通过该方法发送的数据,会经过适配器,如果想要直接发送,请使用DefaultSend。如果发送失败,则会立即抛出异常。
public virtual void Send(byte[] buffer);
public virtual void Send(ByteBlock byteBlock);
public virtual void Send(byte[] buffer, int offset, int length);
【异步发送】
TcpClient已经内置了三种异步发送方法,直接调用就可以发送。如果发送失败,await就会触发异常。
public virtual Task SendAsync(byte[] buffer);
public virtual Task SendAsync(byte[] buffer, int offset, int length);