• 【C#】SuperSocket 服务端使用总结(未完成)


    简介

    SuperSocket 是一个轻量级, 跨平台而且可扩展的 .Net/Mono Socket 服务器程序框架。你无须了解如何使用 Socket, 如何维护 Socket 连接和 Socket 如何工作,但是你却可以使用 SuperSocket 很容易的开发出一款 Socket 服务器端软件,例如游戏服务器,GPS 服务器, 工业控制服务和数据采集服务器等等。

    官网地址:

    Home - SuperSocket

    我们目前使用的1.6的版本。目前已经出到2.0,支持.net core

    安装

    这个包专门用于构建服务端:

    服务端简单构建

    配置文件

    App.config中添加代码如下:

    1. "1.0" encoding="utf-8"?>
    2. <configuration>
    3. <configSections>
    4. <section name="superSocket"
    5. type="SuperSocket.SocketEngine.Configuration.SocketServiceConfig, SuperSocket.SocketEngine" />
    6. configSections>
    7. <superSocket>
    8. <servers>
    9. <server name="TestSvr"
    10. textEncoding="UTF-8"
    11. serverType="TxSocketLib.Server.TestSvr, TxSocketLib"
    12. ip="Any"
    13. port="8053"
    14. maxConnectionNumber="100">
    15. server>
    16. servers>
    17. superSocket>
    18. <startup>
    19. <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
    20. startup>
    21. configuration>

    这里有个坑,要注意,就是SuperSocket的相关节点必须放到最前面,不然会导致服务启动失败

    服务类

    配置文件中指定的类:TestSvr

    1. public class TestSvr: AppServer
    2. {
    3. }

    构建类很简单,继承一下AppServer就OK了,其他的都不用写。

    不过这是最简单的一种写法,后续再进行扩展。

    这里其实是一个反射过程,serverType="TxSocketLib.Server.TestSvr, TxSocketLib"就是指定TestSvr位置。

    配置的加载与服务的启动

    这个配置文件是需要配合一段后台代码进行加载的。如下:

    1. public void StartSvr()
    2. {
    3. IBootstrap bootstrap = BootstrapFactory.CreateBootstrap();
    4. if (!bootstrap.Initialize())//读取配置文件; 如果读取失败了;
    5. {
    6. MessageBox.Show("初始化服务失败了。。。。");
    7. return;
    8. }
    9. logger.Debug("开始启动服务~~");
    10. StartResult result = bootstrap.Start();//启动服务
    11. foreach (var server in bootstrap.AppServers)
    12. {
    13. if (server.State == ServerState.Running)
    14. {
    15. if (server.Name == "TestSvr")
    16. {
    17. TestSvr svr = server as TestSvr;
    18. svr.NewRequestReceived += Svr_test; ;
    19. }
    20. }
    21. else
    22. {
    23. logger.Error($"{server.Name} 服务启动失败。");
    24. }
    25. }
    26. }
    27. private void Svr_test(AppSession session, SuperSocket.SocketBase.Protocol.StringRequestInfo requestInfo)
    28. {
    29. //合起来才是全部的数据
    30. var result = requestInfo.Key + requestInfo.Body;
    31. logger.Debug($"{session.Config.Name}收到数据: {result} ");
    32. }

    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中。这个看上面的图,非常清楚。

    自定义服务

    之前我们构建服务的时候非常简单:

    1. public class TestSvr: AppServer
    2. {
    3. }

    其实这里省略了,Session 和 RequestInfo,Session默认的就是AppSession ,RequestInfo默认是的StringRequestInfo 。

    如果想构建一个Server,就必须对于构建Session 和 RequestInfo。要构建一个Session,就必须构建一个RequestInfo。

    自定义RequestInfo

    自定义RequestInfo需要继承IRequestInfo:

    1. ///
    2. /// 简单的将过来的数据进行格式化
    3. ///
    4. public class SimpleRequestInfo : IRequestInfo
    5. {
    6. public SimpleRequestInfo(byte[] header, byte[] body)
    7. {
    8. //消息包头部,大小端转换
    9. Key = (((int)header[0] << 8) + header[1]).ToString();
    10. //正文部分
    11. Body = Encoding.UTF8.GetString(body, 0, body.Length);
    12. //固定头含义(1:平台数据,2,表示心跳)
    13. IsHeart = string.Equals("2", Key);
    14. }
    15. //接口必须实现的部分
    16. public string Key { get; set; }
    17. public bool IsHeart { get; set; }
    18. public string Body { get; set; }
    19. }

    RequestInfo的职责就是将接收的数据进行格式化,或者说是解析。这里header,和body会按照规则传递过来,这个会引出过滤器的概念,后面再讲。类似之前提到的默认规则。

    自定义Session

    自定义Session就需要关联一个RequestInfo,我们就关联刚刚自定义的SimpleRequestInfo。

    1. public class SimpleSession : AppSession<SimpleSession, SimpleRequestInfo>
    2. {
    3. ///
    4. /// 异常处理
    5. ///
    6. ///
    7. protected override void HandleException(Exception e)
    8. {
    9. this.Send("Application error: {0}", e.Message);
    10. }
    11. ///
    12. /// 有新的命令执行的时候触发;
    13. /// 只有服务不去订阅NewRequestReceived事件的时候,才会触发这个函数
    14. ///
    15. ///
    16. protected override void HandleUnknownRequest(SimpleRequestInfo requestInfo)
    17. {
    18. base.HandleUnknownRequest(requestInfo);
    19. }
    20. protected override void OnSessionStarted()
    21. {
    22. base.OnSessionStarted();
    23. }
    24. protected override void OnSessionClosed(CloseReason reason)
    25. {
    26. //add you logics which will be executed after the session is closed
    27. base.OnSessionClosed(reason);
    28. }
    29. }

    连接的客户端都以Session的方式管理,自定义的Session,可以重写很多方法,这些方法提供了一些切面,来方便我们管控客户端连接。

    自定义服务

    有了Session 和 RequestInfo之后,我们就可以自定义服务了:

    1. ///
    2. /// 服务
    3. ///
    4. public class MySvr : AppServer<SimpleSession, SimpleRequestInfo>
    5. {
    6. }

    这才是完整的写法。

    过滤器

    结束符协议

    我们可以通过服务的构造函数装配过滤器。

    1. ///
    2. /// 服务
    3. ///
    4. public class MySvr : AppServer
    5. : base(new TerminatorReceiveFilterFactory("##"))
    6. {
    7. }

    之前我们的数据的结束是回车换行,现在这么写的话,结束符就变成了##。

    固定头协议的

    自定义过滤器

    1. using SuperSocket.Facility.Protocol;
    2. using System;
    3. using System.Collections.Generic;
    4. using System.Linq;
    5. using System.Text;
    6. using System.Threading.Tasks;
    7. using TxSocketLib.RequestInfo;
    8. namespace TxSocketLib.Filter
    9. {
    10. //数据格式:
    11. // -------+----------+------------------------------------------------------+
    12. // 0001 | 00000010 | 4C36 3150 2D43 4D2B 4C30 3643 5055 2D43 4D2B 4C4A |
    13. // 固定头 | 数据长度 | 数据 |
    14. // 2byte | 4byte | |
    15. // -------+----------+------------------------------------------------------+
    16. public class MyFixedHeaderFilter : FixedHeaderReceiveFilter
    17. {
    18. public MyFixedHeaderFilter()
    19. : base(6)
    20. {
    21. }
    22. ///
    23. /// 获取数据长度部分
    24. ///
    25. ///
    26. ///
    27. ///
    28. ///
    29. protected override int GetBodyLengthFromHeader(byte[] header, int offset, int length)
    30. {
    31. //大小端转换(从网络的大端转到小端)
    32. int l = (int)(header[offset + 2] << 3 * 8)
    33. + (int)(header[offset + 3] << 2 * 8)
    34. + (int)(header[offset + 4] << 1 * 8)
    35. + (int)header[offset + 5];
    36. return l;
    37. }
    38. ///
    39. /// 加过滤器的好处是,会将没有用的信息自动跳出去
    40. /// 就体现在下面这段代码了!!!!
    41. ///
    42. ///
    43. ///
    44. ///
    45. ///
    46. ///
    47. protected override SimpleRequestInfo ResolveRequestInfo(ArraySegment header, byte[] bodyBuffer, int offset, int length)
    48. {
    49. if (bodyBuffer == null) return null;
    50. // 获取body内容,length就是body的长度
    51. var body = bodyBuffer.Skip(offset).Take(length).ToArray();
    52. // 构建消息实例
    53. var info = new SimpleRequestInfo(header.ToArray(), body);
    54. return info;
    55. }
    56. }
    57. }

    这里有几个注意的点:

    1 大小端问题,网络应该用大端协议(C#是默认的小端,所以需要转换),这里的协议头还有数据长度,应该转换为大端。数据部分内容是规定的格式(如UTF8),不用转大小端。大小端是针对数字型变量,不针对字符串

    2 这里描述数据长度的变量是四个字节,所以应该用uint,如果是两个字节应该用ushort。大小端就是针对uint和ushort类型的变量。

    3 过滤器相当于是Session前方的筛子,所以它也和RequestInfo一一对应的,他会将过滤后的值构建成一个RequestInfo。

     拥有自定义过滤器的服务

    1. ///
    2. /// 固定头协议服务
    3. ///
    4. public class FixedHeaderSvr : AppServer
    5. {
    6. public FixedHeaderSvr()
    7. : base(new DefaultReceiveFilterFactory()) //使用默认的接受过滤器工厂 (DefaultReceiveFilterFactory)
    8. {
    9. }
    10. }

    命令

    更为优雅的处理方式是通过命令的方式,当服务不去订阅NewRequestReceived事件的时候,这个时候才有命令出场的机会。

    这个以后再讲把,今天写的太累。

    踩坑记录

    2022年10月13日:(客户端无故被踢下线)

    现象,数据过大时,服务端接收不到数据,客户端被踢下线。

    通过调试:在Session中重写了OnSessionClosed,查看断线原因为 Protocol Error

    1. protected override void OnSessionClosed(CloseReason reason)
    2. {
    3. //add you logics which will be executed after the session is closed
    4. base.OnSessionClosed(reason);
    5. TcpSvr._eventAggregator.GetEvent().Publish("客户端断开原因: " + reason.ToString());
    6. }

    最后定位到参数: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:

    1. <server name="TerminatorSvr" textEncoding="UTF-8" serverType="TxSocketLib.Server.TerminatorSvr, TxSocketLib" ip="Any" port="8054" maxConnectionNumber="100" maxRequestLength="1073741824">
    2. server>

    发送也是按UTF8发送的,但是接收就乱码了,后来发现,TerminatorReceiveFilterFactory有个重载函数,可以指定编码!改了就没乱码了!

    1. public TerminatorSvr()
    2.     : base(new TerminatorReceiveFilterFactory("##",Encoding.UTF8))
    3. {
    4. }

    默认情况下:TerminatorReceiveFilterFactory是ASCII

  • 相关阅读:
    21天学Python --- 打卡7:Spider爬虫入门
    sklearn【MAPE】平均相对误差介绍,以及案例学习!
    Mongodb的基本操作
    用微服务平台框架,实现高效的流程化办公!
    Java并发编程学习十:线程协作
    SpringBoot如何将项目打成jar包,并运行jar包呢?
    在【windows server 2012】下安装MySQL5.7
    LeetCode2109:向字符串添加空格
    需求可追溯性的四个最佳实践
    Kolmogorov-Smirnov正态性检验
  • 原文地址:https://blog.csdn.net/songhuangong123/article/details/126878951