说明

TcpService是TCP系服务器基类,但是不参与实际的数据交互,实际的数据交互由SocketClient完成,所以TcpService的功能只是配置、激活、管理、注销、重建SocketClient类实例,所以在TcpService中,须指定其SocketClient派生的泛型类型,然后通过SocketClient实现HandleReceivedData方法,该方法指示如何处理已接收数据或经过适配器转换的对象。

可配置项

属性名 属性描述
SetBufferLength 缓存池容量,默认1024*10。
设置建议:
1. 如果数据包较小,建议10k左右的值。更加节约内存。
1. 如果数据包较大,例如文件传输等,建议64k,甚至更大的值。
1. 该值虽然无上限,但是一般不要超过1Mb,不然不仅没意义,还很浪费
SetMaxPackageSize 数据包最大值,默认1024102410。该值会在适当时间,直接作用DataHandlingAdapter.MaxPackageSize。
SetThreadCount 多线程数量。该值在Auto模式下指示线程池的最少线程数量和IO线程数量。
设置建议:
1. 异步处理接收数据,此时线程数量设置为内核线程左右的值即可。
1. 同步处理接收数据,此时应当考虑两个因素。该操作是否为耗时操作,如果是,则该值在允许范围内,应当设置更可能大的值。如果不是,则设置为内核线程左右的值即可。
SetListenIPHosts 监听IP和端口号组,可以一次性设置多个地址。
SetServerName 服务器标识名称,无实际使用意义。
SetBacklogProperty Tcp半连接挂起连接队列的最大长度。默认为30
SetMaxCount 最大可连接数,默认为10000
SetClearInterval 清理无数据交互的SocketClient,默认60 * 1000 毫秒。如果不想清除,可使用-1。但是,并不建议设置-1,因为假如有客户端因为网络故障导致僵死的话,服务器将永久保留其实例。所以最好的方式是按照自己的业务需要,设置对应值,因为从普遍性而言,无数据交互的客户端,如果时间超出10s,则断开的策略是优于一直连接的。或者,自己规定心跳数据包,保持客户端活性。
SetClearType 清理统计类型。
- Receive:为在收到数据时,刷新统计,如果一直有数据接收,则不会被主动清理断开。
- Send:为在发送数据时,刷新统计,如果一直有数据发送,则不会被主动清理断开。
- 支持位域叠加。
SetReceiveType 接收类型。
- AUTO:自动接收模式。
- None:不投递IO接收申请,用户可通过GetStream,获取到流以后,自己处理接收。注意:连接端不会感知主动断开。
UsePlugin 是否启用插件。在启用时或许会带来一点点性能损耗,基本上不是千万数据交互根本不值一提。
SetServiceSslOption Ssl配置,为Null时则不启用。
UseReuseAddress 启用端口复用。该配置可在服务器、或客户端在监听端口时,运行监听同一个端口。可以一定程度缓解端口来不及释放的问题

支持插件接口客户端、服务器均支持

声明自定义实例类,然后实现ITcpPlugin接口,即可实现下列事务的触发。
或者继承自TcpPluginBase类,重写相应方法即可。

|

ITcpPlugin

OnConnected 客户端连接成功后触发
OnConnecting 在即将完成连接时触发。
OnDisconnected 会话断开后触发
OnReceivedData 在收到数据时触发
OnSendingData 当即将发送数据时,调用该方法在适配器之后,接下来即会发送数据。
OnIDChanged 当Client的ID被更改后触发

非泛型创建TcpService

非泛型创建TcpService时,实际上是使用了默认的SocketClient,此时会将收到的数据从Received事件中直接抛出。创建过程如下:

  1. TcpService service = new TcpService();
  2. service.Connecting += (client, e) => { };//有客户端正在连接
  3. service.Connected += (client, e) => { };//有客户端连接
  4. service.Disconnected += (client, e) => { };//有客户端断开连接
  5. service.Received += (client, byteBlock, requestInfo) =>
  6. {
  7. //从客户端收到信息
  8. string mes = Encoding.UTF8.GetString(byteBlock.Buffer, 0, byteBlock.Len);
  9. Console.WriteLine($"已从{client.ID}接收到信息:{mes}");
  10. client.Send(mes);//将收到的信息直接返回给发送方
  11. //client.Send("id",mes);//将收到的信息返回给特定ID的客户端
  12. var clients = service.GetClients();
  13. foreach (var targetClient in clients)//将收到的信息返回给在线的所有客户端。
  14. {
  15. if (targetClient.ID != client.ID)
  16. {
  17. targetClient.Send(mes);
  18. }
  19. }
  20. };
  21. service.Setup(new TouchSocketConfig()//载入配置
  22. .SetListenIPHosts(new IPHost[] { new IPHost("127.0.0.1:7789"), new IPHost(7790) })//同时监听两个地址
  23. .SetMaxCount(10000)
  24. .SetThreadCount(10)
  25. .ConfigurePlugins(a =>
  26. {
  27. //a.Add();//此处可以添加插件
  28. })
  29. .ConfigureContainer(a =>
  30. {
  31. a.SetSingletonLogger<ConsoleLogger>();//添加一个日志注入
  32. }))
  33. .Start();//启动

泛型创建TcpService

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

建立TcpService继承类

  1. public class MyService : TcpService<MySocketClient>
  2. {
  3. protected override void LoadConfig(TouchSocketConfig config)
  4. {
  5. //此处加载配置,用户可以从配置中获取配置项。
  6. base.LoadConfig(config);
  7. }
  8. protected override void OnConnecting(MySocketClient socketClient, ClientOperationEventArgs e)
  9. {
  10. //此处逻辑会多线程处理。
  11. //e.ID:对新连接的客户端进行ID初始化,例如可以设置为其IP地址。
  12. //e.IsPermitOperation:指示是否允许该客户端链接。
  13. //对即将连接的客户端做初始化配置
  14. socketClient.Protocol = new Protocol("MyProtocol");
  15. base.OnConnecting(socketClient, e);
  16. }
  17. }

建立SocketClient继承类

  1. public class MySocketClient : SocketClient
  2. {
  3. protected override void HandleReceivedData(ByteBlock byteBlock, IRequestInfo requestInfo)
  4. {
  5. //此处逻辑单线程处理。
  6. //此处处理数据,功能相当于Received事件。
  7. string mes = Encoding.UTF8.GetString(byteBlock.Buffer, 0, byteBlock.Len);
  8. Console.WriteLine($"已接收到信息:{mes}");
  9. }
  10. }

主要方法简介

方法名 功能简介
Start 启动服务器
Stop 停止服务器,可再次调用Start重新使用。插件、容器等仍然可用。
Dispose 释放服务器,不可再调用Start使用
Clear 清理当前连接的所有客户端,服务器工作不受任何影响
ResetID 重新设置某个客户端的ID

发送数据

每个客户端成功连接后,都会创建一个派生自SocketClient的实例,通过该实例即可将数据发送至客户端。

如何获取SocketClient?

(1)直接调用
通过service.GetClients方法,获取当前在线的所有客户端。由于SocketClient的生命周期是由框架控制的,所以最好尽量不要直接引用该实例

  1. SocketClient[] socketClients = service.GetClients();

(2)通过ID获取
先调用service.GetIDs方法,获取当前在线的所有客户端的ID,然后选择需要的ID,通过TryGetSocketClient方法,获取到想要的客户端。

  1. string[] ids = service.GetIDs();
  2. if (service.TryGetSocketClient(ids[0], out SocketClient socketClient))
  3. {
  4. }

同步发送

SocketClient已经内置了三种同步发送方法,直接调用就可以发送,但需要注意的是,通过该方法发送的数据,会经过适配器,如果想要直接发送,请使用DefaultSend。如果发送失败,则会立即抛出异常。

  1. public virtual void Send(byte[] buffer);
  2. public virtual void Send(ByteBlock byteBlock);
  3. public virtual void Send(byte[] buffer, int offset, int length);

调用顺序

  1. TcpClient.Send;
  2. DataHandlingAdapter.PreviewSend;(进入数据处理适配器)
  3. DataHandlingAdapter.GoSend;(通过数据处理适配器封装数据)
  4. Socket.Send;(通过Socket发送数据)

    非独立线程异步发送(服务器不允许独立线程发送)

    使用方法
    TcpClient已经内置了三种异步发送方法,直接调用就可以发送。如果发送失败,会触发异常。

    1. public virtual void SendAsync(byte[] buffer);
    2. public virtual void SendAsync(ByteBlock byteBlock);
    3. public virtual void SendAsync(byte[] buffer, int offset, int length);

    调用顺序

  5. TcpClient.Send;

  6. DataHandlingAdapter.PreviewSend;(进入数据处理适配器)
  7. DataHandlingAdapter.GoSend;(通过数据处理适配器封装数据)
  8. Socket.SendAsync;(通过Socket IOCP发送数据)

    通过TcpService发送

    通过ID发送数据。
    1. public virtual void Send(string id, ByteBlock byteBlock);
    2. public virtual void Send(string id, byte[] buffer, int offset, int length);
    3. public virtual void Send(string id, byte[] buffer);
    4. public virtual void SendAsync(string id, ByteBlock byteBlock);
    5. public virtual void SendAsync(string id, byte[] buffer, int offset, int length);
    6. public virtual void SendAsync(string id, byte[] buffer);

视频介绍