创建TcpService
定义
命名空间:TouchSocket.Sockets
程序集:TouchSocket.dll
一、说明
TcpService是Tcp系服务器基类,它不参与实际的数据交互,只是配置、激活、管理、注销、重建SocketClient类实例。而SocketClient是当**TcpClient(客户端)**成功连接服务器以后,由服务器新建的一个实例类,后续的所有通信,也都是通过该实例完成的。
二、特点
- 简单易用。
- IOCP多线程。
- 内存池支持
- 高性能(实测服务器单客户端单线程,每秒可接收200w条8字节的信息,接收数据流量可达3GB/s)。
- 多地址监听(可以一次性监听多个IP及端口)
- 适配器预处理,一键式解决分包、粘包、对象解析(如HTTP,Json)等。
- 超简单的同步发送、异步发送、接收等操作。
- 基于委托、插件驱动,让每一步都能执行AOP。
2.1 吞吐量性能测试
如下图所示,使用最简单数据接收,不做任何处理。数据吞吐量可达3Gb。 测试Demo示例
2.2 连接性能测试
如下图所示,使用最简单连接测试,不做任何处理。建立1000本地连接仅需0.1秒。 测试Demo示例
三、产品应用场景
- 所有Tcp基础使用场景:可跨平台、跨语言使用。
- 自定义协议解析场景:可解析任意数据格式的TCP数据报文。
四、服务器架构
服务器在收到新客户端连接时,会创建一个SocketClient的派生类实例,与客户端TcpClient一一对应,后续的数据通信均由此实例负责。
SocketClient在Service里面以字典映射。ID为键,SocketClient本身为值。
五、可配置项
可配置项
SetMaxPackageSize
数据包最大值(单位:byte),默认1024×1024×10。该值会在适当时间,直接作用DataHandlingAdapter.MaxPackageSize。
SetGetDefaultNewId
配置初始Id的分配策略。
SetListenIPHosts
设置统一的监听IP和端口号组,可以一次性设置多个地址。
SetListenOptions
设置独立的监听IP和端口号,可以独立控制当前地址监听的个性化配置。
SetServerName
服务器标识名称,无实际使用意义。
SetBacklogProperty
Tcp半连接挂起连接队列的最大长度。默认为30
SetMaxCount
最大可连接数,默认为10000
SetReceiveType
接收类型。
- AUTO:自动接收模式。
- None:不投递IO接收申请,用户可通过GetStream,获取到流以后,自己处理接收。注意:连接端不会感知主动断开。
SetServiceSslOption
Ssl配置,为Null时则不启用。
UseNoDelay
设置Socket的NoDelay属性,默认false。
UseDelaySender
使用延迟发送。众所周知,tcp数据报文为了发送效率,会默认启用延迟算法。但是这种设置,只能一定程度的缓解小数据发送效率低的问题,因为它为了保证多线程发送的有序性,在send函数中设置了线程同步,所以说,每调用一次send,实际上都是巨大的性能消耗(此处用iocp发送亦然)。所以,要解决该问题, 最终还是要将小数据,组合成大数据,这样才能更高效率的发送。所以,DelaySender正是负责此类工作的。
使用DelaySender,会一定程度的降低发送的及时性,但是降低程度并不高,简单来说:
- 如果一 个包大于512kb,则不会延迟,直接发送。
- 如果发送第一个包,与第二个包的时间间隔小于一个线程池线程调度的时间(这个时间极短,一般来说会在10微秒左右),则会将这两个包压缩为一个包发送。
UseReuseAddress
启用端口复用。该配置可在服务器、或客户端在监听端口时,运行监听同一个端口。可以一定程度缓解端口来不及释放的问题。
SetSendTimeout
设置发送超时时间,默认0ms,即禁用该配置。
六、支持插件
插件方法 | 功能 |
---|---|
ITcpConnectingPlugin | 此时Socket实际上已经完成连接,但是并没有启动接收,然后触发。 |
ITcpConnectedPlugin | 同意连接,且成功启动接收后触发 |
ITcpDisconnectingPlugin | 当客户端主动调用Close时触发 |
ITcpDisconnectedPlugin | 当客户端断开连接后触发 |
ITcpReceivingPlugin | 在收到原始数据时触发,所有的数据均在ByteBlock里面。 |
ITcpReceivedPlugin | 在收到适配器数据时触发,根据适配器类型,数据可能在ByteBlock或者IRequestInfo里面。 |
ITcpSendingPlugin | 当即 将发送数据时,调用该方法在适配器之后,接下来即会发送数据。 |
IIdChangedPlugin | 当SocketClient的ID发生改变时触发。 |
七、创建TcpService
7.1 简单创建
直接初始化TcpService,会使用默认的SocketClient。 简单的处理逻辑可通过Connecting、Connected、Received等委托直接实现。
代码如下:
var service = new TcpService();
service.Connecting = (client, e) => { return EasyTask.CompletedTask; };//有客户端正在连接
service.Connected = (client, e) => { return EasyTask.CompletedTask; };//有客户端成功连接
service.Disconnecting = (client, e) => { return EasyTask.CompletedTask; };//有客户端正在断开连接,只有当主动断开时才有效。
service.Disconnected = (client, e) => { return EasyTask.CompletedTask; };//有客户端断开连接
service.Received = (client, e) =>
{
//从客户端收到信息
var mes = Encoding.UTF8.GetString(e.ByteBlock.Buffer, 0, e.ByteBlock.Len);//注意:数据长度是byteBlock.Len
client.Logger.Info($"已从{client.Id}接收到信息:{mes}");
client.Send(mes);//将收到的信息直接返回给发送方
//client.Send("id",mes);//将收到的信息返回给特定ID的客户端
//将收到的信息返回给在线的所有客户端。
//注意:这里只是一个建议思路,实际上群发应该使用生产者消费者模式设计
//var ids = service.GetIds();
//foreach (var clientId in ids)
//{
// if (clientId != client.Id)//不给自己发
// {
// service.Send(clientId, mes);
// }
//}
return EasyTask.CompletedTask;
};
service.Setup(new TouchSocketConfig()//载入配置
.SetListenIPHosts("tcp://127.0.0.1:7789", 7790)//同时监听两个地址
.ConfigureContainer(a =>//容器的配置顺序应该在最前面
{
a.AddConsoleLogger();//添加一个控制台日志注入(注意:在maui中控制台日志不可用)
})
.ConfigurePlugins(a =>
{
//a.Add();//此处可以添加插件
}));
service.Start();//启动
Service.Start()方法并不会阻塞当前运行,所以当在控制台运行时,可能需要使用Console.ReadKey()等操作进行阻塞。
7.2 泛型创建
通过泛型创建服务器,可以实现很多有意思,且能重写一些有用的功能。下面就演示,如何通过泛型创建服务器。
代码如下:
(1)建立SocketClient继承类。
public class MySocketClient : SocketClient
{
protected override async Task ReceivedData(ReceivedDataEventArgs e)
{
//此处逻辑单线程处理。
//此处处理数据,功能相当于Received委托。
var mes = Encoding.UTF8.GetString(e.ByteBlock.Buffer, 0, e.ByteBlock.Len);
Console.WriteLine($"已接收到信息:{mes}");
await base.ReceivedData(e);
}
}
(2)建立TcpService继承类。实际上如果业务不涉及服务器配置的话,可以省略该步骤,使用TcpService的泛型直接创建。
public class MyService : TcpService<MySocketClient>
{
protected override void LoadConfig(TouchSocketConfig config)
{
//此处加载配置,用户可以从配置中获取配置项。
base.LoadConfig(config);
}
protected override async Task OnConnecting(MySocketClient socketClient, ConnectingEventArgs e)
{
//此处逻辑会多线程处理。
//e.Id:对新连接的客户端进行ID初始化,默认情况下是按照设定的规则随机分配的。
//但是按照需求,您可以自定义设置,例如设置为其IP地址。但是需要注意的是id必须在生命周期内唯一。
//e.IsPermitOperation:指示是否允许该客户端链接。
await base.OnConnecting(socketClient, e);
}
}
(3)创建服务器(包含MyService)。
MyService service = new MyService();
service.Setup(new TouchSocketConfig()//载入配置
.SetListenIPHosts("tcp://127.0.0.1:7789")
.ConfigureContainer(a =>//容器的配置顺序应该在最前面
{
a.AddConsoleLogger();//添加一个控制台日志注入(注意:在maui中控制台日志不可用)
})
.ConfigurePlugins(a =>
{
//a.Add();//此处可以添加插件
}));
service.Start();//启动
(4)创建服务器(不含MyService)。
TcpService<MySocketClient> service = new TcpService<MySocketClient>();
service.Setup(new TouchSocketConfig()//载入配置
.SetListenIPHosts(7789, 7790)//同时监听两个地址
.ConfigureContainer(a =>//容器的配置顺序应该在最前面
{
a.AddConsoleLogger();//添加一个控制台日志注入(注意:在maui中控制台日志不可用)
})
.ConfigurePlugins(a =>
{
//a.Add();//此处可以添加插件
}));
service.Start();//启动
由上述代码可以看出,通过继承,可以更加灵活的实现扩展。但实际上,很多业务我们希望大家能通过插件完成。
八、配置监听
所有配置监听的项,都是从IPHost
类创建而来。
IPHost
支持以下格式创建:
- 端口:直接按
int
入参,该操作一般在监听Ipv4
时使用。 - IPv4:按"127.0.0.1:7789"入参。
- IPv6:按"[*::*]:7789"入参。
8.1 Config直接配置
服务器在配置监听时,有多种方式实现。其中最简单、最常见的配置方式就是通过Config直接配置。
TcpService service = new TcpService();
service.Setup(new TouchSocketConfig()//载入配置
.SetListenIPHosts("tcp://127.0.0.1:7789", 7790));
service.Start();//启动
8.2 直 接添加监听配置
直接添加监听配置是更加个性化的监听配置,它可以单独控制指定监听地址的具体配置,例如:是否启用Ssl加密、使用何种适配器等。
var service = new TcpService();
service.Setup(new TouchSocketConfig()//载入配置
.SetListenOptions(option =>
{
option.Add(new TcpListenOption()
{
IpHost = "127.0.0.1:7789",
Name = "server1",//名称用于区分监听
ServiceSslOption = null,//可以针对当前监听,单独启用ssl加密
Adapter = () => new NormalDataHandlingAdapter(),//可以单独对当前地址监听,配置适配器
//还有其他可配置项,都是单独对当前地址有效。
});
option.Add(new TcpListenOption()
{
IpHost = 7790,
Name = "server2",//名称用于区分监听
ServiceSslOption = null,//可以针对当前监听,单独启用ssl加密
Adapter = () => new FixedHeaderPackageAdapter(),//可以单独对当前地址监听,配置适配器
//还有其他可配置项,都是单独对当前地址有效。
});
}));
service.Start();//启动
SetListenIPHosts
可以和SetListenOptions
可以同时使用,但是需要注意的是,Config的全局配置仅会对SetListenIPHosts
单独生效的。SetListenOptions
的地址配置均是单独配置的。
8.3 动态添加、移除监听配置
服务器支持在运行时,动态添加,和移除监听配置,这极大的为灵活监听提供了方便,并且还不影响现有连接。可以轻量级的实现Stop操作。
TcpService service = new TcpService();
service.Setup(new TouchSocketConfig());
service.Start();//启动
service.AddListen(new TcpListenOption()//在Service运行时,可以调用,直接添加监听
{
IpHost = 7791,
Name = "server3",//名称用于区分监听
ServiceSslOption = null,//可以针对当前监听,单独启用ssl加密
Adapter = () => new FixedHeaderPackageAdapter(),//可以单独对当前地址监听,配置适配器
//还有其他可配置项,都是单独对当前地址有效。
});
foreach (var item in service.Monitors)
{
service.RemoveListen(item);//在Service运行时,可以调用,直接移除现有监听
}