[转] C# SuperSocket 服务端使用总结
简介
SuperSocket 是一个轻量级, 跨平台而且可扩展的 .Net/Mono Socket 服务器程序框架。你无须了解如何使用 Socket, 如何维护 Socket 连接和 Socket 如何工作,但是你却可以使用 SuperSocket 很容易的开发出一款 Socket 服务器端软件,例如游戏服务器,GPS 服务器, 工业控制服务和数据采集服务器等等。
官网地址:
我们目前使用的1.6的版本。目前已经出到2.0,支持.net core
安装
这个包专门用于构建服务端:
服务端简单构建
配置文件
App.config中添加代码如下:
- <?xml version="1.0" encoding="utf-8"?>
- <configuration>
- <!-- superSocket 相关配置-->
- <configSections>
- <section name="superSocket"
- type="SuperSocket.SocketEngine.Configuration.SocketServiceConfig, SuperSocket.SocketEngine" />
- </configSections>
-
-
- <superSocket>
- <servers>
- <!-- UTF-8 Ascii gb2312-->
- <server name="TestSvr"
- textEncoding="UTF-8"
- serverType="TxSocketLib.Server.TestSvr, TxSocketLib"
- ip="Any"
- port="8053"
- maxConnectionNumber="100">
- </server>
-
- <!-- 可以配置多个Server-->
- </servers>
- </superSocket>
-
-
- <startup>
- <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
- </startup>
-
-
- </configuration>
这里有个坑,要注意,就是SuperSocket的相关节点必须放到最前面,不然会导致服务启动失败!
服务类
配置文件中指定的类:TestSvr
- public class TestSvr: AppServer
- {
- }
构建类很简单,继承一下AppServer就OK了,其他的都不用写。
不过这是最简单的一种写法,后续再进行扩展。
这里其实是一个反射过程,serverType="TxSocketLib.Server.TestSvr, TxSocketLib"就是指定TestSvr位置。
配置的加载与服务的启动
这个配置文件是需要配合一段后台代码进行加载的。如下:
- public void StartSvr()
- {
- IBootstrap bootstrap = BootstrapFactory.CreateBootstrap();
- if (!bootstrap.Initialize())//读取配置文件; 如果读取失败了;
- {
- MessageBox.Show("初始化服务失败了。。。。");
- return;
- }
- logger.Debug("开始启动服务~~");
- StartResult result = bootstrap.Start();//启动服务
- foreach (var server in bootstrap.AppServers)
- {
- if (server.State == ServerState.Running)
- {
- if (server.Name == "TestSvr")
- {
- TestSvr svr = server as TestSvr;
- svr.NewRequestReceived += Svr_test; ;
- }
-
- }
- else
- {
-
- logger.Error($"{server.Name} 服务启动失败。");
- }
- }
- }
-
-
- private void Svr_test(AppSession session, SuperSocket.SocketBase.Protocol.StringRequestInfo requestInfo)
- {
- //合起来才是全部的数据
- var result = requestInfo.Key + requestInfo.Body;
- logger.Debug($"{session.Config.Name}收到数据: {result} ");
- }
1、因为配置文件中是可以配置多个服务端的,使用这里用到了for循环,通过配置服务名称进行区分。
2、NewRequestReceived 事件,会在服务端接收到完整的数据后促发。
服务启动测试
接下来就调用StartSvr启动测试一下:
我们开启一个TCP的客户端发送数据。
Session 和 RequestInfo
我们首先要看的是事件里面的:
AppSession session, SuperSocket.SocketBase.Protocol.StringRequestInfo requestInfo
我们的TestSvr继承了 AppServer之后,就能订阅这个事件。
每个连接的客户端都以Session的方式管理,发送数据给客户端也通过Session的Send方法,
AppSession就是默认的Session,我们可以自定义自己的Session。
ReqestInfo包含了接收数据内容,他的目的是将接收到的数据进行解析,或者说是格式化。里面默认包含了Key和Body。StringRequestInfo 就是默认的数据格式。
Session 和 RequestInfo以及Server是一一对应的。
默认的格式
1 默认情况下,我们发送的数据需要以回车换行结尾,这样表示一条的结束。服务端才会触发接收事件。
2 数据中的一个或多个连续的空格会被当做分隔符,分割的首个字符串会被保存到StringRequestInfo的Key中,其他的会被保存到Body中。这个看上面的图,非常清楚。
自定义服务
之前我们构建服务的时候非常简单:
- public class TestSvr: AppServer
- {
- }
其实这里省略了,Session 和 RequestInfo,Session默认的就是AppSession ,RequestInfo默认是的StringRequestInfo 。
如果想构建一个Server,就必须对于构建Session 和 RequestInfo。要构建一个Session,就必须构建一个RequestInfo。
自定义RequestInfo
自定义RequestInfo需要继承IRequestInfo:
- /// <summary>
- /// 简单的将过来的数据进行格式化
- /// </summary>
- public class SimpleRequestInfo : IRequestInfo
- {
- public SimpleRequestInfo(byte[] header, byte[] body)
- {
- //消息包头部,大小端转换
- Key = (((int)header[0] << 8) + header[1]).ToString();
- //正文部分
- Body = Encoding.UTF8.GetString(body, 0, body.Length);
- //固定头含义(1:平台数据,2,表示心跳)
- IsHeart = string.Equals("2", Key);
- }
-
- //接口必须实现的部分
- public string Key { get; set; }
-
- public bool IsHeart { get; set; }
-
- public string Body { get; set; }
- }
RequestInfo的职责就是将接收的数据进行格式化,或者说是解析。这里header,和body会按照规则传递过来,这个会引出过滤器的概念,后面再讲。类似之前提到的默认规则。
自定义Session
自定义Session就需要关联一个RequestInfo,我们就关联刚刚自定义的SimpleRequestInfo。
- public class SimpleSession : AppSession<SimpleSession, SimpleRequestInfo>
- {
- /// <summary>
- /// 异常处理
- /// </summary>
- /// <param name="e"></param>
- protected override void HandleException(Exception e)
- {
- this.Send("Application error: {0}", e.Message);
- }
-
- /// <summary>
- /// 有新的命令执行的时候触发;
- /// 只有服务不去订阅NewRequestReceived事件的时候,才会触发这个函数
- /// </summary>
- /// <param name="requestInfo"></param>
- protected override void HandleUnknownRequest(SimpleRequestInfo requestInfo)
- {
- base.HandleUnknownRequest(requestInfo);
- }
-
- protected override void OnSessionStarted()
- {
- base.OnSessionStarted();
- }
-
- protected override void OnSessionClosed(CloseReason reason)
- {
- //add you logics which will be executed after the session is closed
- base.OnSessionClosed(reason);
- }
-
- }
连接的客户端都以Session的方式管理,自定义的Session,可以重写很多方法,这些方法提供了一些切面,来方便我们管控客户端连接。
自定义服务
有了Session 和 RequestInfo之后,我们就可以自定义服务了:
- /// <summary>
- /// 服务
- /// </summary>
- public class MySvr : AppServer<SimpleSession, SimpleRequestInfo>
- {
- }
这才是完整的写法。
过滤器
结束符协议
我们可以通过服务的构造函数装配过滤器。
- /// <summary>
- /// 服务
- /// </summary>
- public class MySvr : AppServer<SimpleSession, SimpleRequestInfo>
- : base(new TerminatorReceiveFilterFactory("##"))
- {
- }
之前我们的数据的结束是回车换行,现在这么写的话,结束符就变成了##。
固定头协议的
自定义过滤器
- using SuperSocket.Facility.Protocol;
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Threading.Tasks;
- using TxSocketLib.RequestInfo;
-
- namespace TxSocketLib.Filter
- {
- //数据格式:
- // -------+----------+------------------------------------------------------+
- // 0001 | 00000010 | 4C36 3150 2D43 4D2B 4C30 3643 5055 2D43 4D2B 4C4A |
- // 固定头 | 数据长度 | 数据 |
- // 2byte | 4byte | |
- // -------+----------+------------------------------------------------------+
- public class MyFixedHeaderFilter : FixedHeaderReceiveFilter<SimpleRequestInfo>
- {
- public MyFixedHeaderFilter()
- : base(6)
- {
-
- }
-
-
- /// <summary>
- /// 获取数据长度部分
- /// </summary>
- /// <param name="header"></param>
- /// <param name="offset"></param>
- /// <param name="length"></param>
- /// <returns></returns>
- protected override int GetBodyLengthFromHeader(byte[] header, int offset, int length)
- {
- //大小端转换(从网络的大端转到小端)
- int l = (int)(header[offset + 2] << 3 * 8)
- + (int)(header[offset + 3] << 2 * 8)
- + (int)(header[offset + 4] << 1 * 8)
- + (int)header[offset + 5];
- return l;
- }
-
-
- /// <summary>
- /// 加过滤器的好处是,会将没有用的信息自动跳出去
- /// 就体现在下面这段代码了!!!!
- /// </summary>
- /// <param name="header"></param>
- /// <param name="bodyBuffer"></param>
- /// <param name="offset"></param>
- /// <param name="length"></param>
- /// <returns></returns>
- protected override SimpleRequestInfo ResolveRequestInfo(ArraySegment<byte> header, byte[] bodyBuffer, int offset, int length)
- {
- if (bodyBuffer == null) return null;
- // 获取body内容,length就是body的长度
- var body = bodyBuffer.Skip(offset).Take(length).ToArray();
- // 构建消息实例
- var info = new SimpleRequestInfo(header.ToArray(), body);
- return info;
- }
- }
- }
这里有几个注意的点:
1 大小端问题,网络应该用大端协议(C#是默认的小端,所以需要转换),这里的协议头还有数据长度,应该转换为大端。数据部分内容是规定的格式(如UTF8),不用转大小端。大小端是针对数字型变量,不针对字符串。
2 这里描述数据长度的变量是四个字节,所以应该用uint,如果是两个字节应该用ushort。大小端就是针对uint和ushort类型的变量。
3 过滤器相当于是Session前方的筛子,所以它也和RequestInfo一一对应的,他会将过滤后的值构建成一个RequestInfo。
拥有自定义过滤器的服务
- /// <summary>
- /// 固定头协议服务
- /// </summary>
- public class FixedHeaderSvr : AppServer<SimpleSession, SimpleRequestInfo>
- {
- public FixedHeaderSvr()
- : base(new DefaultReceiveFilterFactory<MyFixedHeaderFilter, SimpleRequestInfo>()) //使用默认的接受过滤器工厂 (DefaultReceiveFilterFactory)
- {
-
- }
- }
命令
更为优雅的处理方式是通过命令的方式,当服务不去订阅NewRequestReceived事件的时候,这个时候才有命令出场的机会。
这个以后再讲把,今天写的太累。
踩坑记录
2022年10月13日:(客户端无故被踢下线)
现象,数据过大时,服务端接收不到数据,客户端被踢下线。
通过调试:在Session中重写了OnSessionClosed,查看断线原因为 Protocol Error
- protected override void OnSessionClosed(CloseReason reason)
- {
- //add you logics which will be executed after the session is closed
- base.OnSessionClosed(reason);
- TcpSvr._eventAggregator.GetEvent<LogEvent>().Publish("客户端断开原因: " + reason.ToString());
- }
最后定位到参数:maxRequestLength
- maxRequestLength: 最大允许的请求长度,默认值为1024;
随后修改配置文件,加上maxRequestLength给了一个较大的值,问题解决。
也就是说,supersocket会判断接收数据的大小(一开始确实能收到信息),但是如果过大,是不接收事件的。并且会把这个链接断开。
以下是supersocket的其它设置,大家可以参考以下:
服务器实例配置
在根节点中,有一个名为 "servers" 的子节点,你可以定义一个或者多个server节点来代表服务器实例。 这些服务器实例可以是同一种 AppServer 类型, 也可以是不同的类型。
Server 节点的所有属性如下:
- name: 服务器实例的名称;
- serverType: 服务器实例的类型的完整名称;
- serverTypeName: 所选用的服务器类型在 serverTypes 节点的名字,配置节点 serverTypes 用于定义所有可用的服务器类型,我们将在后面再做详细介绍;
- ip: 服务器监听的ip地址。你可以设置具体的地址,也可以设置为下面的值 Any - 所有的IPv4地址 IPv6Any - 所有的IPv6地址
- port: 服务器监听的端口;
- listenBacklog: 监听队列的大小;
- mode: Socket服务器运行的模式, Tcp (默认) 或者 Udp;
- disabled: 服务器实例是否禁用了;
- startupOrder: 服务器实例启动顺序, bootstrap 将按照此值的顺序来启动多个服务器实例;
- sendTimeOut: 发送数据超时时间;
- sendingQueueSize: 发送队列最大长度, 默认值为5;
- maxConnectionNumber: 可允许连接的最大连接数;
- receiveBufferSize: 接收缓冲区大小;
- sendBufferSize: 发送缓冲区大小;
- syncSend: 是否启用同步发送模式, 默认值: false;
- logCommand: 是否记录命令执行的记录;
- logBasicSessionActivity: 是否记录session的基本活动,如连接和断开;
- clearIdleSession: true 或 false, 是否定时清空空闲会话,默认值是 false;
- clearIdleSessionInterval: 清空空闲会话的时间间隔, 默认值是120, 单位为秒;
- idleSessionTimeOut: 会话空闲超时时间; 当此会话空闲时间超过此值,同时clearIdleSession被配置成true时,此会话将会被关闭; 默认值为300,单位为秒;
- security: Empty, Tls, Ssl3. Socket服务器所采用的传输层加密协议,默认值为空;
- maxRequestLength: 最大允许的请求长度,默认值为1024;
- textEncoding: 文本的默认编码,默认值是 ASCII;
- defaultCulture: 此服务器实例的默认 thread culture, 只在.Net 4.5中可用而且在隔离级别为 'None' 时无效;
- disableSessionSnapshot: 是否禁用会话快照, 默认值为 false.
- sessionSnapshotInterval: 会话快照时间间隔, 默认值是 5, 单位为秒;
- keepAliveTime: 网络连接正常情况下的keep alive数据的发送间隔, 默认值为 600, 单位为秒;
- keepAliveInterval: Keep alive失败之后, keep alive探测包的发送间隔,默认值为 60, 单位为秒;
certificate: 这各节点用于定义用于此服务器实例的X509Certificate证书的信息
2022年10月25日 SuperSocket 中文乱码问题
写了一个Terminator的服务,修改了结束符,一改发现中文就乱码了!我在配置中配置了服务默认为UTF8:
- <server name="TerminatorSvr" textEncoding="UTF-8" serverType="TxSocketLib.Server.TerminatorSvr, TxSocketLib" ip="Any" port="8054" maxConnectionNumber="100" maxRequestLength="1073741824">
- </server>
发送也是按UTF8发送的,但是接收就乱码了,后来发现,TerminatorReceiveFilterFactory有个重载函数,可以指定编码!改了就没乱码了!
- public TerminatorSvr()
- : base(new TerminatorReceiveFilterFactory("##",Encoding.UTF8))
- {
-
- }
默认情况下:TerminatorReceiveFilterFactory是ASCII
转自 https://blog.csdn.net/songhuangong123/article/details/126878951