说明
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事件中直接抛出。创建过程如下:
TcpService service = new TcpService();
service.Connecting += (client, e) => { };//有客户端正在连接
service.Connected += (client, e) => { };//有客户端连接
service.Disconnected += (client, e) => { };//有客户端断开连接
service.Received += (client, byteBlock, requestInfo) =>
{
//从客户端收到信息
string mes = Encoding.UTF8.GetString(byteBlock.Buffer, 0, byteBlock.Len);
Console.WriteLine($"已从{client.ID}接收到信息:{mes}");
client.Send(mes);//将收到的信息直接返回给发送方
//client.Send("id",mes);//将收到的信息返回给特定ID的客户端
var clients = service.GetClients();
foreach (var targetClient in clients)//将收到的信息返回给在线的所有客户端。
{
if (targetClient.ID != client.ID)
{
targetClient.Send(mes);
}
}
};
service.Setup(new TouchSocketConfig()//载入配置
.SetListenIPHosts(new IPHost[] { new IPHost("127.0.0.1:7789"), new IPHost(7790) })//同时监听两个地址
.SetMaxCount(10000)
.SetThreadCount(10)
.ConfigurePlugins(a =>
{
//a.Add();//此处可以添加插件
})
.ConfigureContainer(a =>
{
a.SetSingletonLogger<ConsoleLogger>();//添加一个日志注入
}))
.Start();//启动
泛型创建TcpService
通过泛型创建服务器,可以实现很多有意思,且能重写一些有用的功能。下面就演示,如何通过泛型创建服务器。
建立TcpService继承类
public class MyService : TcpService<MySocketClient>
{
protected override void LoadConfig(TouchSocketConfig config)
{
//此处加载配置,用户可以从配置中获取配置项。
base.LoadConfig(config);
}
protected override void OnConnecting(MySocketClient socketClient, ClientOperationEventArgs e)
{
//此处逻辑会多线程处理。
//e.ID:对新连接的客户端进行ID初始化,例如可以设置为其IP地址。
//e.IsPermitOperation:指示是否允许该客户端链接。
//对即将连接的客户端做初始化配置
socketClient.Protocol = new Protocol("MyProtocol");
base.OnConnecting(socketClient, e);
}
}
建立SocketClient继承类
public class MySocketClient : SocketClient
{
protected override void HandleReceivedData(ByteBlock byteBlock, IRequestInfo requestInfo)
{
//此处逻辑单线程处理。
//此处处理数据,功能相当于Received事件。
string mes = Encoding.UTF8.GetString(byteBlock.Buffer, 0, byteBlock.Len);
Console.WriteLine($"已接收到信息:{mes}");
}
}
主要方法简介
方法名 | 功能简介 |
---|---|
Start | 启动服务器 |
Stop | 停止服务器,可再次调用Start重新使用。插件、容器等仍然可用。 |
Dispose | 释放服务器,不可再调用Start使用 |
Clear | 清理当前连接的所有客户端,服务器工作不受任何影响 |
ResetID | 重新设置某个客户端的ID |
发送数据
每个客户端成功连接后,都会创建一个派生自SocketClient的实例,通过该实例即可将数据发送至客户端。
如何获取SocketClient?
(1)直接调用
通过service.GetClients
方法,获取当前在线的所有客户端。由于SocketClient的生命周期是由框架控制的,所以最好尽量不要直接引用该实例
SocketClient[] socketClients = service.GetClients();
(2)通过ID获取
先调用service.GetIDs
方法,获取当前在线的所有客户端的ID,然后选择需要的ID,通过TryGetSocketClient方法,获取到想要的客户端。
string[] ids = service.GetIDs();
if (service.TryGetSocketClient(ids[0], out SocketClient socketClient))
{
}
同步发送
SocketClient已经内置了三种同步发送方法,直接调用就可以发送,但需要注意的是,通过该方法发送的数据,会经过适配器,如果想要直接发送,请使用DefaultSend。如果发送失败,则会立即抛出异常。
public virtual void Send(byte[] buffer);
public virtual void Send(ByteBlock byteBlock);
public virtual void Send(byte[] buffer, int offset, int length);
调用顺序
- TcpClient.Send;
⇓ - DataHandlingAdapter.PreviewSend;(进入数据处理适配器)
⇓ - DataHandlingAdapter.GoSend;(通过数据处理适配器封装数据)
⇓ -
非独立线程异步发送(服务器不允许独立线程发送)
使用方法
TcpClient已经内置了三种异步发送方法,直接调用就可以发送。如果发送失败,会触发异常。public virtual void SendAsync(byte[] buffer);
public virtual void SendAsync(ByteBlock byteBlock);
public virtual void SendAsync(byte[] buffer, int offset, int length);
调用顺序
TcpClient.Send;
⇓- DataHandlingAdapter.PreviewSend;(进入数据处理适配器)
⇓ - DataHandlingAdapter.GoSend;(通过数据处理适配器封装数据)
⇓ - Socket.SendAsync;(通过Socket IOCP发送数据)
通过TcpService发送
通过ID发送数据。public virtual void Send(string id, ByteBlock byteBlock);
public virtual void Send(string id, byte[] buffer, int offset, int length);
public virtual void Send(string id, byte[] buffer);
public virtual void SendAsync(string id, ByteBlock byteBlock);
public virtual void SendAsync(string id, byte[] buffer, int offset, int length);
public virtual void SendAsync(string id, byte[] buffer);