跳到主要内容
版本:4.0-beta

创建TcpService

定义

定义

命名空间:
TouchSocket
程序集:
安装:
dotnet add package TouchSocket

一、说明

TcpServiceTcp系服务器基类,它不参与实际的数据交互,只是配置、激活、管理、注销、重建SessionClient类实例。而SessionClient是当TcpClient(客户端)成功连接服务器以后,由服务器新建的一个实例类,后续的所有通信,也都是通过该实例完成的。

二、特点

  • 简单易用。
  • IOCP多线程。
  • 内存池支持
  • 高性能(实测服务器单客户端单线程,每秒可接收200w条8字节的信息,接收数据流量可达3GB/s)。
  • 多地址监听(可以一次性监听多个IP及端口)
  • 适配器预处理,一键式解决分包粘包、对象解析(如HTTP,Json)等。
  • 超简单的同步发送、异步发送、接收等操作。
  • 基于委托、插件驱动,让每一步都能执行AOP。

2.1 吞吐量性能测试

如下图所示,使用最简单数据接收,不做任何处理。数据吞吐量可达3Gb。 测试Demo示例

2.2 连接性能测试

如下图所示,使用最简单连接测试,不做任何处理。建立1000本地连接仅需0.1秒。 测试Demo示例

三、产品应用场景

  • 所有Tcp基础使用场景:可跨平台、跨语言使用。
  • 自定义协议解析场景:可解析任意数据格式的TCP数据报文。

四、服务器架构

4.1 连接架构

服务器在收到新客户端连接时,会创建一个SessionClient的派生类实例,与客户端TcpClient一一对应,后续的数据通信均由此实例负责。

SessionClientService里面以字典映射。ID为键,SessionClient本身为值。

4.2 Scoped 生命周期

TcpService支持ScopedIOC容器中工作时,也是支持Scoped区域划分的。

一般情况下,TcpServiceSetup时,首先会创建一个Scoped区域,用于整个TcpService的生命周期。在TcpService释放(Dispose)时释放。

然后,当有新客户端连接后,会为每个SessionClient的派生类实例也创建一个Scoped区域,用于SessionClient的生命周期。当连接断开时,会释放此区域。

五、可配置项

5.1 配置监听

简单情况下,直接设置统一的监听IP和端口号组,可以一次性设置多个地址。

🔄 正在加载代码...

简单设置时,无法进行更加个性化的配置。例如:是否启用Ssl加密、使用何种适配器等。

所以,可以使用SetListenOptions方法,进行个性化监听配置。

🔄 正在加载代码...

所有配置监听的项,都是从IPHost类创建而来。

IPHost支持以下格式创建:

  • 端口:直接按int入参,该操作一般在监听Ipv4时使用。
  • IPv4:按"127.0.0.1:7789"入参。
  • IPv6:按"[*::*]:7789"入参。
注意

在监听时,只能使用IP地址和端口号进行监听,不能使用域名。例如:

✅ "127.0.0.1:7789"

❌ "https://touchsocket.net/"

5.2 Id分配策略

TcpService会在每次新建SessionClient时,分配一个Id。默认情况下,Id是随机分配的。 如果需要自定义Id分配策略,可以使用SetGetDefaultNewId方法。

🔄 正在加载代码...

5.3 Tcp半连接队列

TcpService的半连接队列是指在服务器端,等待完成三次握手的连接请求队列。可以通过SetBacklogProperty方法设置。

🔄 正在加载代码...

5.4 设置最大连接数量

TcpService的最大连接数可以通过SetMaxCount方法设置。默认情况下,最大连接数为10000。

🔄 正在加载代码...

5.5 Ssl配置

TcpService支持Ssl配置,可以通过SetServiceSslOption方法设置。默认情况下,Ssl配置为Null,不启用Ssl加密。

🔄 正在加载代码...

5.6 设置Socket的NoDelay属性

TcpService支持设置Socket的NoDelay属性,可以通过UseNoDelay方法设置。默认情况下,NoDelay属性为false。

🔄 正在加载代码...

5.7 启用端口复用

TcpService支持启用端口复用,可以通过UseReuseAddress方法设置。默认情况下,端口复用为false。

🔄 正在加载代码...

5.8 设置最大、最小缓存容量

TcpService支持设置最大、最小缓存容量,可以通过SetMaxBufferSizeSetMinBufferSize方法设置。默认情况下,最大缓存容量为自动,最小缓存容量为自动。

🔄 正在加载代码...

5.9 服务器名称

TcpService支持设置服务器名称,可以通过SetServerName方法设置。默认情况下,服务器名称为Null。

🔄 正在加载代码...

六、支持插件

插件方法功能
ITcpConnectingPlugin此时Socket实际上已经完成连接,但是并没有启动接收,然后触发。
ITcpConnectedPlugin同意连接,且成功启动接收后触发
ITcpClosingPlugin当客户端主动调用Close时触发
ITcpClosedPlugin当客户端断开连接后触发
ITcpReceivingPlugin在收到原始数据时触发,所有的数据均在ByteBlock里面。
ITcpReceivedPlugin在收到适配器数据时触发,根据适配器类型,数据可能在ByteBlock或者IRequestInfo里面。
ITcpSendingPlugin当即将发送数据时,调用该方法在适配器之后,接下来即会发送数据。
IIdChangedPlugin当SessionClient的Id发生改变时触发。

七、创建TcpService

7.1 简单创建

直接初始化TcpService,会使用默认的SessionClient。 简单的处理逻辑可通过ConnectingConnectedReceived等委托直接实现。

代码如下:

🔄 正在加载代码...
温馨提示

Service.StartAsync()方法并不会阻塞当前运行,所以当在控制台运行时,可能需要使用Console.ReadKey()等操作进行阻塞。

7.2 泛型创建

通过泛型创建服务器,可以实现很多有意思,且能重写一些有用的功能。下面就演示,如何通过泛型创建自定义服务器。

代码如下:

(1)建立SessionClient继承类。

🔄 正在加载代码...

(2)建立TcpService继承类。使用TcpService的泛型直接创建。

🔄 正在加载代码...

(3)启动服务器。

自定义服务器的配置和启动与普通服务器一致。

🔄 正在加载代码...
建议

由上述代码可以看出,通过继承,可以更加灵活的实现扩展。如有必要,还可以直接从TcpServiceBase<>继承,这样可以实现更底层的功能。

八、动态添加、移除监听配置

服务器支持在运行时,动态添加,和移除监听配置,这极大的为灵活监听提供了方便,并且还不影响现有连接。可以轻量级的实现Stop操作。

🔄 正在加载代码...
信息

在移除监听配置时,已完成连接的客户端不受影响。

九、接收数据

在TcpService中,接收数据的方式有很多种。多种方式可以组合使用。

9.1 Received委托处理

当使用TcpService(非泛型)创建服务器时,内部已经定义好了一个外置委托Received,可以通过该委托直接接收数据。

🔄 正在加载代码...

9.2 重写TcpSessionClient处理

正如7.2所示,可以直接在MySessionClient的重写OnTcpReceived中直接处理数据。

9.3 插件处理

按照TouchSocket的设计理念,使用插件处理数据,是一项非常简单,且高度解耦的方式。步骤如下:

(1)声明插件

插件可以先继承PluginBase,然后再实现需要的功能插件接口,可以按需选择泛型或者非泛型实现。

如果已经有继承类,直接实现IPlugin接口即可。

🔄 正在加载代码...

(2)配置使用插件处理的服务器

.ConfigurePlugins(a =>
{
a.Add<TcpServiceReceivedPlugin>();
})
信息

当接收数据时,MemoryRequestInfo的值会根据适配器类型不同而不同。

9.4 异步阻塞接收

异步阻塞接收,即使用await的方式接收数据。其特点是能在代码上下文中,直接获取到收到的数据。

只是在服务器使用异步阻塞时,建议直接在Connected触发时相关使用。

下列将以插件为例:

🔄 正在加载代码...
提示

异步阻塞接收,在等待接收数据时,不会阻塞线程资源,所以即使大量使用,也不会影响性能。

十、发送数据

按照架构图,每个客户端成功连接后,服务器都会创建一个派生自TcpSessionClient的实例,并将其存以生成的Id为键,存在一个字典中。

所以,可以直接使用TcpSessionClient直接发送。

例如,在Received委托中,直接回应数据:

🔄 正在加载代码...

或者,如果你知道目标客户端的Id,那么可以使用TcpServiceSendAsync方法直接发送数据。

例如:

🔄 正在加载代码...

亦或者,可以先用id查到对应的TcpSessionClient,然后用其提供的方法直接发送。

例如:

🔄 正在加载代码...
注意

由于TcpSessionClient的生命周期是由框架控制的,所以最好尽量不要直接引用该实例,可以引用TcpSessionClient.Id,然后再通过服务器查找。

注意

所有的发送,框架内部实际上只实现了异步发送,但是为了兼容性,仍然保留了同步发送的扩展。但是强烈建议如有可能,请务必使用异步发送来提高效率

十一、示例Demo