[转] C# SuperSocket 服务端使用总结

简介

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

官网地址:

Home - SuperSocket

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

安装

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

服务端简单构建

配置文件

App.config中添加代码如下:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <configuration>
  3. <!-- superSocket 相关配置-->
  4. <configSections>
  5. <section name="superSocket"
  6. type="SuperSocket.SocketEngine.Configuration.SocketServiceConfig, SuperSocket.SocketEngine" />
  7. </configSections>
  8. <superSocket>
  9. <servers>
  10. <!-- UTF-8 Ascii gb2312-->
  11. <server name="TestSvr"
  12. textEncoding="UTF-8"
  13. serverType="TxSocketLib.Server.TestSvr, TxSocketLib"
  14. ip="Any"
  15. port="8053"
  16. maxConnectionNumber="100">
  17. </server>
  18. <!-- 可以配置多个Server-->
  19. </servers>
  20. </superSocket>
  21. <startup>
  22. <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
  23. </startup>
  24. </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. /// <summary>
  2. /// 简单的将过来的数据进行格式化
  3. /// </summary>
  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. /// <summary>
  4. /// 异常处理
  5. /// </summary>
  6. /// <param name="e"></param>
  7. protected override void HandleException(Exception e)
  8. {
  9. this.Send("Application error: {0}", e.Message);
  10. }
  11. /// <summary>
  12. /// 有新的命令执行的时候触发;
  13. /// 只有服务不去订阅NewRequestReceived事件的时候,才会触发这个函数
  14. /// </summary>
  15. /// <param name="requestInfo"></param>
  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. /// <summary>
  2. /// 服务
  3. /// </summary>
  4. public class MySvr : AppServer<SimpleSession, SimpleRequestInfo>
  5. {
  6. }

这才是完整的写法。

过滤器

结束符协议

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

  1. /// <summary>
  2. /// 服务
  3. /// </summary>
  4. public class MySvr : AppServer<SimpleSession, SimpleRequestInfo>
  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<SimpleRequestInfo>
  17. {
  18. public MyFixedHeaderFilter()
  19. : base(6)
  20. {
  21. }
  22. /// <summary>
  23. /// 获取数据长度部分
  24. /// </summary>
  25. /// <param name="header"></param>
  26. /// <param name="offset"></param>
  27. /// <param name="length"></param>
  28. /// <returns></returns>
  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. /// <summary>
  39. /// 加过滤器的好处是,会将没有用的信息自动跳出去
  40. /// 就体现在下面这段代码了!!!!
  41. /// </summary>
  42. /// <param name="header"></param>
  43. /// <param name="bodyBuffer"></param>
  44. /// <param name="offset"></param>
  45. /// <param name="length"></param>
  46. /// <returns></returns>
  47. protected override SimpleRequestInfo ResolveRequestInfo(ArraySegment<byte> 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. /// <summary>
  2. /// 固定头协议服务
  3. /// </summary>
  4. public class FixedHeaderSvr : AppServer<SimpleSession, SimpleRequestInfo>
  5. {
  6. public FixedHeaderSvr()
  7. : base(new DefaultReceiveFilterFactory<MyFixedHeaderFilter, SimpleRequestInfo>()) //使用默认的接受过滤器工厂 (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<LogEvent>().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

转自 https://blog.csdn.net/songhuangong123/article/details/126878951

posted @ 2023-07-19 09:02  CastleWu  阅读(464)  评论(0编辑  收藏  举报