SuperSocket SuperSocket 2.0 中文文档是一个轻量级, 跨平台而且可扩展的 .Net/Mono Socket 服务器程序框架。你无须了解如何使用 Socket, 如何维护 Socket 连接和 Socket 如何工作,但是你却可以使用 SuperSocket 很容易的开发出一款 Socket 服务器端软件,例如游戏服务器,GPS 服务器, 工业控制服务和数据采集服务器等等。
简单易用,只需要几个类就能创建出健壮的Socket服务器端程序
支持各种协议, 内置的协议解析工具让你把实现通信协议这种复杂的工作变得很简单
自动支持SSL/TLS传输层加密
支持多个socket服务器实例运行,而且支持多个服务器实例的隔离
SuperSocket能以控制台或者Windows服务形式运行,一个脚本就能将SuperSocket安装成服务
灵活的日志策略能够记录大部分socket活动
支持UDP
支持IPv6
支持Windows Azure
支持Linux/Unix操作系统(通过Mono 2.10或以上版本)
内置可直接使用的Flash/Silverlight Socket策略服务器高性能的事件驱动通信。
会话级别的发送队列能够让你通过会话并发的发送数据,并保持高性能和可控性。
强大且高性能的协议解析实现工具帮你简化了网络数据的分析工作:自带多种数据帧过滤器,可实现自己的数据帧过滤器,可自定义数据内容协议,,粘包拆包都是浮云。
轻量级意味着组件可以自由选择使用,可根据环境变化变更。
RequestInfo>ReceiveFilter>AppSession>AppServer
using SuperSocket.SocketBase;
using SuperSocket.SocketBase.Config;
using SuperSocket.SocketBase.Protocol;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp1
{
internal class Program
{
static void Main(string[] args)
{
var appServer = new AppServer();
//Encoding.Default
ServerConfig config = new ServerConfig()
{
MaxRequestLength = 1024 * 8,
ReceiveBufferSize = 1024 * 8,
TextEncoding = "UTF-8",
Port = 9900
};
if (!appServer.Setup(config)) // Setup with listening port
{
Console.WriteLine("Failed to Setup!");
Console.ReadKey();
return;
}
Console.WriteLine();
if (!appServer.Start()) //Try to start the appServer
{
Console.WriteLine("Failed to start!");
Console.ReadKey();
return;
}
Console.WriteLine("The server started successfully, press key 'q' to stop it!");
Console.WriteLine(appServer.Config.MaxRequestLength);
Console.WriteLine(appServer.Config.ReceiveBufferSize);
Console.WriteLine(appServer.Config.TextEncoding);
//SuperSocket自定义了三个事件 ,连接事件,接收事件,关闭事件
appServer.NewSessionConnected += appServer_NewSessionConnected; //连接事件
appServer.NewRequestReceived += appServer_NewRequestReceived1; //接收事件
appServer.SessionClosed += appServer_SessionClosed; //关闭事件
while (Console.ReadKey().KeyChar != 'q')
{
Console.WriteLine();
continue;
}
appServer.Stop();
Console.WriteLine("The Server was stopped!");
Console.ReadKey();
}
static void appServer_NewSessionConnected(AppSession session)
{
Console.WriteLine("已连接! " + session.RemoteEndPoint);
session.Send("Connect Success! " + session.RemoteEndPoint);
}
static void appServer_NewRequestReceived1(AppSession session, SuperSocket.SocketBase.Protocol.StringRequestInfo requestInfo)
{
var key = requestInfo.Key;
var body = requestInfo.Body;
switch (key)
{
case "1":
Console.WriteLine("Hello Word");
session.Send("1. Hello World");
break;
case "2":
Console.WriteLine("SuperSocket Demo");
session.Send("2. SuperScoket Demo");
break;
case "3":
// Console.WriteLine(body);
session.Send(body);
break;
default:
session.Send("unKnow");
break;
}
}
static void appServer_NewRequestReceived(AppSession session, BinaryRequestInfo requestInfo)
{
var key = requestInfo.Key;
var body = requestInfo.Body;
switch (key)
{
case "1":
Console.WriteLine("Hello Word");
session.Send("1. Hello World");
break;
case "2":
Console.WriteLine("SuperSocket Demo");
session.Send("2. SuperScoket Demo");
break;
case "3":
// Console.WriteLine(body);
session.Send(requestInfo.Body.ToString());
break;
default:
session.Send("unKnow");
break;
}
}
static void appServer_SessionClosed(AppSession session, CloseReason value)
{
string ipAddress_Close = session.RemoteEndPoint.ToString();
session.Send("已关闭连接! " + session.RemoteEndPoint + ";" + value.ToString());
Console.WriteLine("已关闭连接! " + ipAddress_Close);
Console.WriteLine("已关闭连接! " + session.RemoteEndPoint + ";" + value.ToString());
}
}
}
自定义自己服务器中相关的类,建议类的建立顺序:RequestInfo>ReceiveFilter>AppSession>AppServer
通信协议格式如下:
在FixedHeaderReceiveFilter,头部指数据内容之前的数据(即数据长度L之前的部分),以上协议可以知道,头部包含11个字节.
先实现一个客户端请求的实体类RequestInfo,该RequestInfo类必须实现接口 IRequestInfo,该接口只有一个名为"Key"的字符串类型的属性。
SuperSocket设计了两个RequestInfo类:StringRequestInfo 和BinaryRequestInfo,这里我们自定义一个来GDProtocolRequestInfo实现:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SuperSocket.SocketBase.Protocol;
namespace GDServer
{
public class GDProtocolRequestInfo : IRequestInfo
{
///
/// [不使用]
///
public string Key { get; set; }
///
/// 设备逻辑地址
///
public string DeviceLogicalCode { get; set; }
///
/// 命令序列号
///
public string Seq { get; set; }
///
/// 控制码
///
public string ControlCode { get; set; }
///
/// 数据长度
///
public string Length { get; set; }
///
/// 数据域
///
public string Data { get; set; }
///
/// CS校验
///
public string Cs { get; set; }
///
/// 当前完整帧
///
//public string EntireFrame { get; set; }
}
}
设计基于类FixedHeaderReceiveFilter实现自己的接收过滤器GDProtocolReceiveFilterV2,主要实现GetBodyLengthFromHeader和ResolveRequestInfo方法,实现如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SuperSocket.SocketBase.Protocol;
using SuperSocket.Facility.Protocol;//
using SuperSocket.Common;//
namespace GDServer
{
///
/// 广东规约过滤器V2,(帧格式为GDProtocolRequestInfo)
///
public class GDProtocolReceiveFilterV2 : FixedHeaderReceiveFilter
{
public GDProtocolReceiveFilterV2()
: base(11)
{
}
///
/// 获取数据域和结尾字节长度
///
///
///
///
///
protected override int GetBodyLengthFromHeader(byte[] header, int offset, int length)
{
//length为头部(包含两字节的length)长度
//获取高位
byte high = header[offset + length - 1];
//获取低位
byte low = header[offset + length - 2];
int len = (int)high * 256 + low;
return len + 2;//结尾有2个字节
}
///
/// 实现帧内容解析
///
///
///
///
///
///
protected override GDProtocolRequestInfo ResolveRequestInfo(ArraySegment header, byte[] bodyBuffer, int offset, int length)
{
GDProtocolRequestInfo res = new GDProtocolRequestInfo();
string entireFrame = BytesToHexStr(header.Array) + BytesToHexStr(bodyBuffer.CloneRange(offset, length));
//res.EntireFrame = entireFrame;
res.DeviceLogicalCode = entireFrame.Substring(2, 8);
res.Seq = entireFrame.Substring(10, 4);
res.ControlCode = entireFrame.Substring(16, 2);
res.Length = entireFrame.Substring(18, 4);
int dataLen = int.Parse(HEXtoDEC(ReverseHexString(res.Length)));
res.Data = entireFrame.Substring(22, dataLen * 2);
res.Cs = entireFrame.Substring(22 + dataLen * 2, 2);
return res;
}
///
/// 高低对调
///
///
///
string ReverseHexString(string str)
{
char[] buff = new char[str.Length];
for (int i = 0; i < str.Length; i += 2)
{
buff[i] = str[str.Length - i - 2];
buff[i + 1] = str[str.Length - 1 - i];
}
string s = new string(buff);
return s;
}
///
/// 16进制转10进制
///
///
///
string HEXtoDEC(string HEX)
{
return Convert.ToInt64(HEX, 16).ToString();
}
///
/// 转化bytes成16进制的字符
///
///
///
string BytesToHexStr(byte[] bytes)
{
string returnStr = "";
if (bytes != null)
{
for (int i = 0; i < bytes.Length; i++)
{
returnStr += bytes[i].ToString("X2");
}
}
return returnStr;
}
}
}
先创建新的AppSession,GDProtocolSessionV2,新的AppServer将使用它。
using SuperSocket.SocketBase;
using SuperSocket.SocketBase.Protocol;
using System;
namespace GDServer
{
public class GDProtocolSessionV2 : AppSession
{
protected override void HandleException(Exception e)
{
}
}
}
使用该协议的方法是使用接收或者自己定义的接收过滤器工厂来在 SuperSocket 中启用该协议
using SuperSocket.SocketBase;
using SuperSocket.SocketBase.Protocol;
namespace GDServer
{
public class GDProtocolServerV2 : AppServer
{
public GDProtocolServerV2()
: base(new DefaultReceiveFilterFactory()) //使用默认的接受过滤器工厂 (DefaultReceiveFilterFactory)
{
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using GDServer;
namespace Test
{
class Program
{
static void Main(string[] args)
{
Console.ForegroundColor = ConsoleColor.Red;
var gdServer = new GDProtocolServerV2();
gdServer.Setup(2015);
gdServer.NewSessionConnected += gdServer_NewSessionConnected;
gdServer.NewRequestReceived += gdServer_NewRequestReceived;
gdServer.SessionClosed += gdServer_SessionClosed;
gdServer.Start();
Console.WriteLine("server is:" + gdServer.State.ToString());
while (true)
{
if (Console.ReadKey().KeyChar == 'q')
{
gdServer.Stop();
gdServer.Dispose();
return;
}
}
}
static void gdServer_SessionClosed(GDProtocolSessionV2 session, SuperSocket.SocketBase.CloseReason value)
{
Console.WriteLine(session.RemoteEndPoint.ToString() + " closed. reason:" + value);
}
static void gdServer_NewRequestReceived(GDProtocolSessionV2 session, GDProtocolRequestInfo requestInfo)
{
var info = requestInfo;
Console.WriteLine("receive from: " + session.RemoteEndPoint.ToString());
Console.WriteLine("DeviceLogicalCode:" + info.DeviceLogicalCode);
Console.WriteLine("Seq:" + info.Seq);
Console.WriteLine("ControlCode:" + info.ControlCode);
Console.WriteLine("Length:" + info.Length);
Console.WriteLine("Data:" + info.Data);
Console.WriteLine("Cs:" + info.Cs);
Console.WriteLine("-------------------------------------------------------------");
}
static void gdServer_NewSessionConnected(GDProtocolSessionV2 session)
{
Console.WriteLine(session.RemoteEndPoint.ToString() + " connected.");
}
}
}
发送符合协议 ,切记! 协议稍复杂,不好测试。
分别发送符合该协议格式的帧(用TCP调试助手使用hex方式发送)
68 77 77 12 34 00 01 68 A1 03 00 11 11 11 DC 16
68 77 77 12 34 41 01 68 01 0C 00 01 00 00 00 00 00 00 00 30 80 10 80 94 16
68 77 77 12 34 41 01 68 88 08 00 00 00 30 80 00 10 80 00 16 16
68 77 77 12 34 41 01 68 95 23 00 00 0B 00 00 10 00 00 00 00 00 FF FF FF FF FF FF FF FF 00 00 5B 00 00 00 00 00 00 00 00 00 00 00 00 00 32 9E 16
打印结果如下:
server is:Running
127.0.0.1:34360 connected.
receive from: 127.0.0.1:34360
DeviceLogicalCode:77771234
Seq:0001
ControlCode:A1
Length:0300
Data:111111
Cs:DC
-------------------------------------------------------------
receive from: 127.0.0.1:34360
DeviceLogicalCode:77771234
Seq:4101
ControlCode:01
Length:0C00
Data:010000000000000030801080
Cs:94
-------------------------------------------------------------
receive from: 127.0.0.1:34360
DeviceLogicalCode:77771234
Seq:4101
ControlCode:88
Length:0800
Data:0000308000108000
Cs:16
-------------------------------------------------------------
receive from: 127.0.0.1:34360
DeviceLogicalCode:77771234
Seq:4101
ControlCode:95
Length:2300
Data:000B0000100000000000FFFFFFFFFFFFFFFF00005B0000000000000000000000000032
Cs:9E
-------------------------------------------------------------
以上代码请自行引入SuperSocket的dll和System.configuration.dll
关键字: TerminatorReceiveFilter, CountSpliterReceiveFilter, FixedSizeReceiveFilter, BeginEndMarkReceiveFilter, FixedHeaderReceiveFilter
阅读了前面一篇文档之后, 你可能会觉得用 SuperSocket 来实现你的自定义协议并不简单。 为了让这件事变得更容易一些, SuperSocket 提供了一些通用的协议解析工具, 你可以用他们简单而且快速的实现你自己的通信协议:
与命令行协议类似,一些协议用结束符来确定一个请求.
例如, 一个协议使用两个字符 "##" 作为结束符, 于是你可以使用类 "TerminatorReceiveFilterFactory":
///
/// TerminatorProtocolServer
/// Each request end with the terminator "##"
/// ECHO Your message##
///
public class TerminatorProtocolServer : AppServer
{
public TerminatorProtocolServer()
: base(new TerminatorReceiveFilterFactory("##"))
{
}
}
默认的请求类型是 StringRequestInfo, 你也可以创建自己的请求类型, 不过这样需要你做一点额外的工作:
基于TerminatorReceiveFilter实现你的接收过滤器(ReceiveFilter):
public class YourReceiveFilter : TerminatorReceiveFilter
{
//More code
}
实现你的接收过滤器工厂(ReceiveFilterFactory)用于创建接受过滤器实例:
public class YourReceiveFilterFactory : IReceiveFilterFactory
{
//More code
}
然后在你的 AppServer 中使用这个接收过滤器工厂(ReceiveFilterFactory).
有些协议定义了像这样格式的请求 "#part1#part2#part3#part4#part5#part6#part7#". 每个请求有7个由 '#' 分隔的部分. 这种协议的实现非常简单:
///
/// Your protocol likes like the format below:
/// #part1#part2#part3#part4#part5#part6#part7#
///
public class CountSpliterAppServer : AppServer
{
public CountSpliterAppServer()
: base(new CountSpliterReceiveFilterFactory((byte)'#', 8)) // 7 parts but 8 separators
{
}
}
你也可以使用下面的类更深入的定制这种协议:
CountSpliterReceiveFilter
CountSpliterReceiveFilterFactory
CountSpliterReceiveFilterFactory
在这种协议之中, 所有请求的大小都是相同的。如果你的每个请求都是有9个字符组成的字符串,如"KILL BILL", 你应该做的事就是想如下代码这样实现一个接收过滤器(ReceiveFilter):
class MyReceiveFilter : FixedSizeReceiveFilter
{
public MyReceiveFilter()
: base(9) //传入固定的请求大小
{
}
protected override StringRequestInfo ProcessMatchedRequest(byte[] buffer, int offset, int length, bool toBeCopied)
{
//TODO: 通过解析到的数据来构造请求实例,并返回
}
}
然后在你的 AppServer 类中使用这个接受过滤器 (ReceiveFilter):
public class MyAppServer : AppServer
{
public MyAppServer()
: base(new DefaultReceiveFilterFactory()) //使用默认的接受过滤器工厂 (DefaultReceiveFilterFactory)
{
}
}
在这类协议的每个请求之中 都有固定的开始和结束标记。例如, 我有个协议,它的所有消息都遵循这种格式 "!xxxxxxxxxxxxxx$"。因此,在这种情况下, "!" 是开始标记, "$" 是结束标记,于是你的接受过滤器可以定义成这样:
class MyReceiveFilter : BeginEndMarkReceiveFilter
{
//开始和结束标记也可以是两个或两个以上的字节
private readonly static byte[] BeginMark = new byte[] { (byte)'!' };
private readonly static byte[] EndMark = new byte[] { (byte)'$' };
public MyReceiveFilter()
: base(BeginMark, EndMark) //传入开始标记和结束标记
{
}
protected override StringRequestInfo ProcessMatchedRequest(byte[] readBuffer, int offset, int length)
{
//TODO: 通过解析到的数据来构造请求实例,并返回
}
}
然后在你的 AppServer 类中使用这个接受过滤器 (ReceiveFilter):
public class MyAppServer : AppServer
{
public MyAppServer()
: base(new DefaultReceiveFilterFactory()) //使用默认的接受过滤器工厂 (DefaultReceiveFilterFactory)
{
}
}
这种协议将一个请求定义为两大部分, 第一部分定义了包含第二部分长度等等基础信息. 我们通常称第一部分为头部.
例如, 我们有一个这样的协议: 头部包含 6 个字节, 前 4 个字节用于存储请求的名字, 后两个字节用于代表请求体的长度:
/// +-------+---+-------------------------------+
/// |request| l | |
/// | name | e | request body |
/// | (4) | n | |
/// | |(2)| |
/// +-------+---+-------------------------------+
使用 SuperSocket, 你可以非常方便的实现这种协议:
class MyReceiveFilter : FixedHeaderReceiveFilter
{
public MyReceiveFilter()
: base(6)
{
}
protected override int GetBodyLengthFromHeader(byte[] header, int offset, int length)
{
return (int)header[offset + 4] * 256 + (int)header[offset + 5];
}
protected override BinaryRequestInfo ResolveRequestInfo(ArraySegment header, byte[] bodyBuffer, int offset, int length)
{
return new BinaryRequestInfo(Encoding.UTF8.GetString(header.Array, header.Offset, 4), bodyBuffer.CloneRange(offset, length));
}
}
你需要基于类FixedHeaderReceiveFilter实现你自己的接收过滤器.
然后你就可以使用接收或者自己定义的接收过滤器工厂来在 SuperSocket 中启用该协议.
关键字: IRequestInfo, IReceiveFilter, 自定义协议, 请求, 接收过滤器
通信协议用于将接收到的二进制数据转化成您的应用程序可以理解的请求。 SuperSocket提供了一个内置的通信协议“命令行协议”定义每个请求都必须以回车换行"\r\n"结尾。
但是一些应用程序由于不同的原因无法使用命令行协议。 这种情况下,你需要使用下面的工具来实现你的自定义协议:
* RequestInfo
* ReceiveFilter
* ReceiveFilterFactory
* AppServer and AppSession
RequestInfo 是表示来自客户端请求的实体类。 每个来自客户端的请求都能应该被实例化为 RequestInfo 类型。 RequestInfo 类必须实现接口 IRequestInfo,该接口只有一个名为"Key"的字符串类型的属性:
public interface IRequestInfo
{
string Key { get; }
}
请求类型 StringRequestInfo 用在 SuperSocket 命令行协议中。
你也可以根据你的应用程序的需要来定义你自己的请求类型。 例如, 如果所有请求都包含 DeviceID 信息,你可以在RequestInfo类里为它定义一个属性:
public class MyRequestInfo : IRequestInfo
{
public string Key { get; set; }
public int DeviceId { get; set; }
/*
// Other properties
*/
}
SuperSocket 还提供了另外一个请求类 "BinaryRequestInfo" 用于二进制协议:
public class BinaryRequestInfo
{
public string Key { get; }
public byte[] Body { get; }
}
你可以直接使用此类型 BinaryRequestInfo, 如果他能满足你的需求的话。当然要用。
接收过滤器(ReceiveFilter)用于将接收到的二进制数据转化成请求实例(RequestInfo)。
实现一个接收过滤器(ReceiveFilter), 你需要实现接口 IReceiveFilter:
public interface IReceiveFilter
where TRequestInfo : IRequestInfo
{
///
/// Filters received data of the specific session into request info.
///
/// The read buffer.
/// The offset of the current received data in this read buffer.
/// The length of the current received data.
/// if set to true [to be copied].
/// The rest, the length of the data which hasn't been parsed.
///
TRequestInfo Filter(byte[] readBuffer, int offset, int length, bool toBeCopied, out int rest);
///
/// Gets the size of the left buffer.
///
///
/// The size of the left buffer.
///
int LeftBufferSize { get; }
///
/// Gets the next receive filter.
///
IReceiveFilter NextReceiveFilter { get; }
///
/// Resets this instance to initial state.
///
void Reset();
}
Filter(....): 该方法将会在 SuperSocket 收到一块二进制数据时被执行,接收到的数据在 readBuffer 中从 offset 开始, 长度为 length 的部分。
TRequestInfo Filter(byte[] readBuffer, int offset, int length, bool toBeCopied, out int rest);
这儿有很多种情况需要你处理:
接收过滤器工厂(ReceiveFilterFactory)用于为每个会话创建接收过滤器. 定义一个过滤器工厂(ReceiveFilterFactory)类型, 你必须实现接口 IReceiveFilterFactory. 类型参数 "TRequestInfo" 是你要在整个程序中使用的请求类型
///
/// Receive filter factory interface
///
/// The type of the request info.
public interface IReceiveFilterFactory : IReceiveFilterFactory
where TRequestInfo : IRequestInfo
{
///
/// Creates the receive filter.
///
/// The app server.
/// The app session.
/// The remote end point.
///
/// the new created request filer assosiated with this socketSession
///
IReceiveFilter CreateFilter(IAppServer appServer, IAppSession appSession, IPEndPoint remoteEndPoint);
}
你也可以直接使用默认的过滤器工厂(ReceiveFilterFactory)
DefaultReceiveFilterFactory
,当工厂的CreateFilter方法被调用时,它将会调用TReceiveFilter类型的无参构造方法来创建并返回TReceiveFilter.
现在, 你已经有了 RequestInfo, ReceiveFilter 和 ReceiveFilterFactory, 但是你还没有正式使用它们. 如果你想让他们在你的程序里面可用, 你需要定义你们的 AppSession 和 AppServer 来使用他们.
为 AppSession 设置 RequestInfo
public class YourSession : AppSession
{
//More code...
}
为 AppServer 设置 RequestInfo 和 ReceiveFilterFactory
public class YourAppServer : AppServer
{
public YourAppServer()
: base(new YourReceiveFilterFactory())
{
}
}
完成上面两件事情,你的自定义协议就应该可以工作了。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using SuperSocket.SocketBase;
using SuperSocket.SocketBase.Protocol;
using System.Threading;
using SuperSocket.SocketBase.Config;
using Newtonsoft.Json;
using System.Collections.Concurrent;
using SuperSocket.Facility.Protocol;
using SuperSocket.Common;
using System.Net;
using System.Text.RegularExpressions;
using Quartz.Util;
using System.Xml.Linq;
using FrameworkHostPC.Models;
using FrameworkHostPC.Util;
using FrameworkHostPC.Base;
using static FrameworkHostPC.Models.DataCollection;
using static log4net.Appender.RollingFileAppender;
using SuperSocket.SocketEngine;
namespace FrameworkHostPC.Views
{
///
/// SocketServerControl.xaml 的交互逻辑
///
public partial class SocketServerControl : UserControl
{
///
/// The synchronize content
///
public SynchronizationContext syncContent = null;
public ConcurrentQueue QueueMesDatas = new ConcurrentQueue();
public static event EventHandler UpdateUI; //
///
/// 客户端计数器
///
static int clientCount = 0;
public static bool StartSucess = false;
///
/// 数据
///
// public static ConcurrentQueue<> QueueData = new ConcurrentQueue< >();
public SocketServerControl()
{
InitializeComponent();
//获取当前上下文
syncContent = SynchronizationContext.Current;
//建立socketServer,与CCD进行通信
Task.Run(async () =>
{
await SocketServer();
MonitiorQueue();
});
}
public async Task SocketServer()
{
BinaryReceiveFilterFactory filterFactory = new BinaryReceiveFilterFactory();
BinaryServer appServer = new BinaryServer(filterFactory);
读取socket udp server 配置
//string socketServerConn = SimpleLog.ReadWriteConfigSettingXml("Device.Properties.Settings", "SocketServer", "GetValue", "userSettings");
//int port = Convert.ToInt32(socketServerConn.Split(',')[1]);
//在这设置服务器模式,更多属性请参阅官方文档
//配置服务器,使用TCP模式
if (!appServer.Setup(new ServerConfig
{
// Name = "FrameworkHostPC",
Ip = "Any",
//Ip = "192.168.250.10",
Port = 15000,
Mode = SocketMode.Tcp,
MaxRequestLength = 1024 * 16,
ReceiveBufferSize = 1024 * 16,//9216
// MaxConnectionNumber = 100
}))
{
syncContent.Post(SetTextBoxText2, "ERROR:配置socket server失败...\n\r");
return;
}
StartSucess = appServer.Start();
if (!StartSucess)
{
syncContent.Post(SetTextBoxText2, "ERROR:SockerServer再次启动...\n\r");
//return;
}
//连接时
appServer.NewSessionConnected += Server_NewSessionConnected;
//接收信息时
appServer.NewRequestReceived += Server_NewRequestReceived;
//关闭服务时
appServer.SessionClosed += Server_SessionClosed;
syncContent.Post(SetTextBoxText2, $"INFO:SocketServer启动成功,Port:{appServer.Config.Port}...\n\r");
await Task.Delay(100);
}
///
/// 新会话
///
///
private void Server_NewSessionConnected(BinarySession session)
{
clientCount++;
syncContent.Post(SetTextBoxText2, $"INFO:socket服务端得到来自socket客户端的连接成功,sessionIP是{session.RemoteEndPoint.Address}:{session.RemoteEndPoint.Port}\n\r");
session.Send("Hello,you are the " + clientCount + "th connected client!");
}
///
/// 数据接收
///
///
///
private void Server_NewRequestReceived(BinarySession session, BinaryRequestInfo requestInfo)
{
string json = string.Empty;
try
{
StringBuilder sb = new StringBuilder();
Array.ForEach(requestInfo.Body, b => sb.Append($"{b} "));
Console.WriteLine($"接收到客户端 {session.Config.Ip}:{session.Config.Port} 的数据:");
Console.WriteLine($"字节数组形式:{sb.ToString()}");
Console.WriteLine($"ASCII码转换:{Encoding.ASCII.GetString(requestInfo.Body)}");
//接收信息中以空格区分,需要以换行符结束\r\n
json = Encoding.Default.GetString(requestInfo.Body).ToString();
syncContent.Post(SetTextBoxText2, @"INFO:服务器端接收消息内容是:" + json + "\n\r");
}
catch (Exception ex)
{
Util.Log.WriteLog("CCDDataCollection", json);
syncContent.Post(SetTextBoxText2, @"ERROR:服务器端处理CCD数据异常:" + ex.Message + "\n\r");
session.Send("Unknow Error");
}
}
///
/// 会话结束
///
///
///
private void Server_SessionClosed(BinarySession session, CloseReason value)
{
syncContent.Post(SetTextBoxText2, $"INFO:服务端失去来自客户端的连接+{session.SessionID}+{value}\n\r");
}
private void MonitiorQueue()
{
Task.Run(async delegate
{
while (true)
{
try
{
if (QueueMesDatas.Count != 0)
{
await UploadMesDatas();
}
}
catch (Exception ex)
{
Util.Log.WriteLog("Exception", "异常:" + ex.ToString());
syncContent.Post(SetTextBoxText2, $"INFO:设备过程数据采集异常:{ex.Message}\n\r");
}
finally
{
await Task.Delay(3000);
}
}
});
}
private async Task UploadMesDatas()
{
try
{
string result = string.Empty;
QueueMesDatas.TryPeek(out var mESData);
if (mESData == null)
{
QueueMesDatas.TryDequeue(out var _);
return result;
}
var response = APIService.DataCollection(Common.MO, Common.BATCH, Common.BATTERYTYPE, mESData.lstDATAITEMS, mESData.lstNGITEMS);
if (response.Result == 1)
{
QueueMesDatas.TryDequeue(out var _);
syncContent.Post(SetTextBoxText2, $"INFO:设备过程数据采集 安驰mes成功响应:{response.MSG}。\n\r");
}
else
{
QueueMesDatas.TryDequeue(out var _);
syncContent.Post(SetTextBoxText2, $"WARNNING:设备过程数据采集 安驰mes响应失败:{response.MSG}。\n\r");
}
await Task.Delay(100);
return "OK";
}
catch (Exception ex)
{
Util.Log.WriteLog("Exception", "异常:" + ex.ToString());
return "ERROR:设备过程数据采集 接口调用发生异常,错误描述:" + ex.ToString();
}
}
///
/// 日志显示自动滚动与清除事件
///
///
///
private void AutoScrollViewer_ScrollChanged2(object sender, ScrollChangedEventArgs e)
{
var sc = sender as ScrollViewer;
int rows = 50;
//if (rows < 500)
//{
// rows = 500;
//}
if (TBTESTINGLOG2.Inlines.Count > rows)
{
TBTESTINGLOG2.Inlines.Remove(TBTESTINGLOG2.Inlines.FirstInline);
}
if (e.ExtentHeightChange > 0)
{
sc.ScrollToEnd();
}
}
///
/// Sets the text box text.
///
/// The information.
public void SetTextBoxText2(object info)
{
try
{
string strFlag = info.ToString().Split(':')[0];
switch (strFlag)
{
case "INFO":
TBTESTINGLOG2.Inlines.Add(new Run($"[{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}] {info.ToString().Replace("INFO:", "")}") { Foreground = Brushes.LightGreen });
break;
case "WARNNING":
TBTESTINGLOG2.Inlines.Add(new Run($"[{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}] {info.ToString().Replace("WARNNING:", "")}") { Foreground = Brushes.LightYellow });
break;
case "ERROR":
TBTESTINGLOG2.Inlines.Add(new Run($"[{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}] {info.ToString().Replace("ERROR:", "")}") { Foreground = Brushes.MediumVioletRed });
break;
}
}
catch
{
TBTESTINGLOG2.Inlines.Add(new Run($"[{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}] {info.ToString().Split(':')[1]}") { Foreground = Brushes.White });
}
}
}
#region SuperSocket 自定义协议-结束符协议
public class BinaryServer : AppServer
{
public BinaryServer(IReceiveFilterFactory protocol) : base(protocol)
{
//AppServer
}
}
public class BinarySession : AppSession
{
}
public class BinaryReceiveFilter : IReceiveFilter
{
public int LeftBufferSize { get; set; }
public IReceiveFilter NextReceiveFilter { get; set; }
public FilterState State { get; set; }
public BinaryRequestInfo Filter(byte[] readBuffer, int offset, int length, bool toBeCopied, out int rest)
{
byte[] value = new byte[length];
Array.Copy(readBuffer, offset, value, 0, length);
BinaryRequestInfo binaryRequestInfo = new BinaryRequestInfo("key", value);
rest = length - value.Length;
return binaryRequestInfo;
}
public void Reset()
{
}
}
public class BinaryReceiveFilterFactory : IReceiveFilterFactory
{
public IReceiveFilter CreateFilter(IAppServer appServer, IAppSession appSession, IPEndPoint remoteEndPoint)
{
BinaryReceiveFilter binaryReceiveFilter = new BinaryReceiveFilter();
return binaryReceiveFilter;
}
}
#endregion
}
构建一个网络应用程序, 仅仅使用 TCP 还是 UDP 是远远不够的。 因为TCP 和 UDP 是传输层协议。定义了传输层协议是不能让网络的两端进行通信的。需要定义应用层通信协议把接收到的二进制数据转化成你程序能理解的请求。
Socket里面的协议解析是Socket通讯程序设计中最复杂的地方,如果你的应用层协议设计或实现不佳,Socket通讯中常见的粘包和分包就难以避免,而用SuperSocket提供的通信协议就完美的避免了上述情况的发生。
CommandLineProtocol---命令行协议(默认内置)
TerminatorReceiveFilter (SuperSocket.SocketBase.Protocol.TerminatorReceiveFilter, SuperSocket.SocketBase) ---结束符协议
CountSpliterReceiveFilter (SuperSocket.Facility.Protocol.CountSpliterReceiveFilter, SuperSocket.Facility)---固定数量分隔符协议
FixedSizeReceiveFilter (SuperSocket.Facility.Protocol.FixedSizeReceiveFilter, SuperSocket.Facility)---固定请求大小协议
BeginEndMarkReceiveFilter (SuperSocket.Facility.Protocol.BeginEndMarkReceiveFilter, SuperSocket.Facility)---带起止符协议
FixedHeaderReceiveFilter (SuperSocket.Facility.Protocol.FixedHeaderReceiveFilter, SuperSocket.Facility)---头部格式固定并包含内容长度协议
TerminatorReceiveFilter ,结束符协议(SuperSocket.SocketBase.Protocol.TerminatorReceiveFilter, SuperSocket.SocketBase)
CountSpliterReceiveFilter ,固定数量分隔符协议(SuperSocket.Facility.Protocol.CountSpliterReceiveFilter, SuperSocket.Facility)
FixedSizeReceiveFilter, 固定请求大小的协议 (SuperSocket.Facility.Protocol.FixedSizeReceiveFilter, SuperSocket.Facility)
BeginEndMarkReceiveFilter,带起止符的协议 (SuperSocket.Facility.Protocol.BeginEndMarkReceiveFilter, SuperSocket.Facility)
FixedHeaderReceiveFilter ,头部格式固定并且包含内容长度的协议(SuperSocket.Facility.Protocol.FixedHeaderReceiveFilter, SuperSocket.Facility)
什么是默认内置?当你不定义应用层通信协议,直接用AppSession.Send()方法发送字符串进行服务器和客户端双向通信,SuperSocket就默认启用命令行协议CommandLineProtocol。
下图的代码,你不定义应用层通信协议,直接用AppSession.Send()方法发送字符串进行服务器和客户端双向通信,SuperSocket就默认启用命令行格式的协议CommandLineProtocol进行通信。
static void appServer_NewSessionConnected(AppSession session)
{
session.Send("Welcome to SuperSocket Telnet Server!");
}
命令行协议是一种被广泛应用的协议。一些成熟的协议如 Telnet, SMTP, POP3 和 FTP 都是基于命令行协议的。
在SuperSocket 中,如果我们没有自定义应用层协议的话,SuperSocket默认的协议就是命令行协议 ,这样就极大的简化了基于此类协议的开发。
1、SuperSocket命令行协议定义了每个请求必须以回车换行符( "\r\n")结尾。
2、SuperSocket命令行协议将接收到的数据翻译成StringRequestInfo 实例。
SuperSocket设计了两个RequestInfo类:StringRequestInfo 和BinaryRequestInfo。其中StringRequestInfo用于字符串实例,BinaryRequestInfo用于二进制实例。如果我们在SuperSocket中使用命令行协议,所有接收到的数据都会翻译成 StringRequestInfo实例(字符串实例)。我们来看一下StringRequestInfo的定义:
public class StringRequestInfo
{
public string Key { get; }//Key是这个命令行协议的命令名,用于关联Command的字符串;
public string Body { get; }//Body是一个命令的参数部分
public string[] Parameters { get; }//Parameters是一个命令的参数列表
/* Other properties and methods */
}
3、SuperSocket 内置的命令行协议用空格来分割请求的Key和参数部分。
SuperSocket内置的命令行协议用空格来分割请求的Key和参数部分,因此当客户端发送如下数据到服务器端时
"LOGIN kerry 123456" + 回车换行符
服务器将会收到一个StringRequestInfo 实例,这个实例的属性为:
Key: "LOGIN"
Body: "kerry 123456";
Parameters: ["kerry", "123456"]
同时定义 "LOGIN" 的命令,这个命令的 ExecuteCommand 方法将会被执行,服务器所接收到的StringRequestInfo实例也将作为参数传给这个方法。
public class LOGIN : CommandBase
{
public override void ExecuteCommand(AppSession session, StringRequestInfo requestInfo)
{
}
}
4、SuperSocket 内置的命令行协议的参数部分用空格来分割。
SuperSocket内置的命令行协议的参数部分用空格来分割,因此当客户端发送如下数据到服务器端时:
"LOGIN kerry 123 456 789" + 回车换行符
服务器将会收到一个StringRequestInfo 实例,这个实例的属性为:
Key: "LOGIN"
Body: "kerry 123 456 789";
Parameters: ["kerry", "123", "456", "789"]
其中:Parameters[0]="kerry",Parameters[1]="123",Parameters[2]="456",Parameters[3]="789"
AppSession 代表一个和客户端的逻辑连接,基于连接的操作应该放在该类之中。你可以用该类的实例发送数据到客户端,接收客户端发送的数据或者关闭连接。
使用方法:创建自定义类SocketSession,继承AppSession类并重写AppSession类的方法(注意:一个AppSession对象对应一个连接)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SuperSocket.Common;
using SuperSocket.SocketBase;
using SuperSocket.SocketBase.Protocol;
using SuperSocket.SocketBase.Config;
using SuperSocket.SocketEngine;
namespace SuperSocketCommandLineProtocol
{
///
/// 自定义连接类SocketSession,继承AppSession,并传入到AppSession
///
public class SocketSession : AppSession
{
public override void Send(string message)
{
Console.WriteLine("发送消息:" + message);
base.Send(message);
}
protected override void OnSessionStarted()
{
//输出客户端IP地址
Console.WriteLine(this.LocalEndPoint.Address.ToString());
this.Send("Hello User,Welcome to SuperSocket Telnet Server!");
}
///
/// 连接关闭
///
///
protected override void OnSessionClosed(CloseReason reason)
{
base.OnSessionClosed(reason);
}
protected override void HandleUnknownRequest(StringRequestInfo requestInfo)
{
Console.WriteLine($"遇到未知的请求 Key:" + requestInfo.Key + $" Body:" + requestInfo.Body);
base.HandleUnknownRequest(requestInfo);
}
///
/// 捕捉异常并输出
///
///
protected override void HandleException(Exception e)
{
this.Send("error: {0}", e.Message);
}
}
}
AppServer 代表了监听客户端连接,承载TCP连接的服务器实例。理想情况下,我们可以通过AppServer实例获取任何你想要的客户端连接,服务器级别的操作和逻辑应该定义在此类之中。使用方法:创建自定义类SocketServer,继承AppServer类并重写AppServer类的方法
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SuperSocket.Common;
using SuperSocket.SocketBase;
using SuperSocket.SocketBase.Protocol;
using SuperSocket.SocketBase.Config;
using SuperSocket.SocketEngine;
namespace SuperSocketCommandLineProtocol.Config
{
public class SocketServer : AppServer
{
protected override bool Setup(IRootConfig rootConfig, IServerConfig config)
{
Console.WriteLine("正在准备配置文件");
return base.Setup(rootConfig, config);
}
protected override void OnStarted()
{
Console.WriteLine("服务已开始");
base.OnStarted();
}
protected override void OnStopped()
{
Console.WriteLine("服务已停止");
base.OnStopped();
}
///
/// 输出新连接信息
///
///
protected override void OnNewSessionConnected(SocketSession session)
{
base.OnNewSessionConnected(session);
//输出客户端IP地址
Console.Write("\r\n" + session.LocalEndPoint.Address.ToString() + ":连接");
}
///
/// 输出断开连接信息
///
///
///
protected override void OnSessionClosed(SocketSession session, CloseReason reason)
{
base.OnSessionClosed(session, reason);
Console.Write("\r\n" + session.LocalEndPoint.Address.ToString() + ":断开连接");
}
}
}
在SuperSocket中的Command让我们进行扩展,使用方法也极其简单。只需要继承一个CommandBase
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SuperSocket.Common;
using SuperSocket.SocketBase;
using SuperSocket.SocketBase.Protocol;
using SuperSocket.SocketBase.Command;
namespace SuperSocketCommandLineProtocol
{
public class LOGIN : CommandBase
{
///
/// 自定义执行命令方法,注意传入的变量session类型为MySession
///
/// 会话
/// 请求数据信息
public override void ExecuteCommand(SocketSession session, StringRequestInfo requestInfo)
{
session.Send(string.Format("LOGIN {0}:{1} {2}", session.Config.Ip, session.Config.Port, requestInfo.Body));
}
}
}
在program.cs类中改为使用App.config文件配置,用BootStrap启动服务器。警告:要想使用BootStrap启动服务器,必须引用 using SuperSocket.SocketEngine;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SuperSocket.Common;
using SuperSocket.SocketBase;
using SuperSocket.SocketBase.Protocol;
using SuperSocket.SocketEngine;
namespace SuperSocketCommandLineProtocol
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("请按任何键进行启动SuperSocket服务!");
Console.ReadKey();
Console.WriteLine();
var bootstrap = BootstrapFactory.CreateBootstrap();
if (!bootstrap.Initialize())
{
Console.WriteLine("初始化失败!");
Console.ReadKey();
return;
}
var result = bootstrap.Start();
Console.WriteLine("服务正在启动: {0}!", result);
if (result == StartResult.Failed)
{
Console.WriteLine("服务启动失败!");
Console.ReadKey();
return;
}
Console.WriteLine("服务启动成功,请按'q'停止服务!");
while (Console.ReadKey().KeyChar != 'q')
{
Console.WriteLine();
continue;
}
//停止服务
bootstrap.Stop();
Console.WriteLine("服务已停止!");
Console.ReadKey();
}
}
}
SuperSocket使用.NET自带的配置技术,SuperSocket有一个专门的配置Section,使用配置启动SuperSocket可以灵活配置选项。
name: 实例名称(就是:工程项目的解决方案名称:SuperSocketCommandLineProtocol)serverType: 实例运行的AppServer类型(就是之前我们创建的自定义AppServer类【SocketServer】,它创建在Config文件夹下,因此的完整路径为:SuperSocketCommandLineProtocol.Config.SocketServer)
ip: 侦听ip
port: 侦听端口
客户端发送字符串“LOGIN kerry 123 456 789”+回车换行符
1、SuperSocket命令行协议定义了每个请求必须以回车换行符( "\r\n")结尾。
2、SuperSocket命令行协议将接收到的数据翻译成StringRequestInfo 实例。
3、SuperSocket 内置的命令行协议用空格来分割请求的Key和参数部分。
4、SuperSocket 内置的命令行协议的参数部分用空格来分割。
在某些场景中:命令和参数,参数和参数之间的分隔符不是用空格,而是如下分隔符,例如:
"LOGIN:kerry,123,456,789" + 回车换行符
即: (1)、冒号:来分割请求的Key和参数部分。
(2)、逗号,来分割参数部分。
在这种情况我们就需要重新设置CommandLineProtocol的CommandParser。SuperSocket内建的BasicCommandParser可以直接设置命令名和参数,参数与参数之间的分隔符。重置SuperSocket内置的命令行协议的分隔符就称之为自定义SuperSocket内置的命令行协议。
namespace SuperSocketCustomCommandLineProtocol
{
public class SocketServer : AppServer
{
public SocketServer()
: base(new CommandLineReceiveFilterFactory(Encoding.Default, new BasicRequestInfoParser(":", ",")))
{
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SuperSocket.Common;
using SuperSocket.SocketBase;
using SuperSocket.SocketBase.Protocol;
using SuperSocket.SocketBase.Config;
using SuperSocket.SocketEngine;
namespace SuperSocketCustomCommandLineProtocol
{ ///
/// 自定义连接类SocketSession,继承AppSession,并传入到AppSession
///
public class SocketSession : AppSession
{
public override void Send(string message)
{
Console.WriteLine("发送消息:" + message);
base.Send(message);
}
protected override void OnSessionStarted()
{
//输出客户端IP地址
Console.WriteLine(this.LocalEndPoint.Address.ToString());
this.Send("Hello User,Welcome to SuperSocket Telnet Server!");
}
///
/// 连接关闭
///
///
protected override void OnSessionClosed(CloseReason reason)
{
base.OnSessionClosed(reason);
}
protected override void HandleUnknownRequest(StringRequestInfo requestInfo)
{
Console.WriteLine($"遇到未知的请求 Key:" + requestInfo.Key + $" Body:" + requestInfo.Body);
base.HandleUnknownRequest(requestInfo);
}
///
/// 捕捉异常并输出
///
///
protected override void HandleException(Exception e)
{
this.Send("error: {0}", e.Message);
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SuperSocket.Common;
using SuperSocket.SocketBase;
using SuperSocket.SocketBase.Protocol;
using SuperSocket.SocketBase.Config;
using SuperSocket.SocketEngine;
namespace SuperSocketCustomCommandLineProtocol
{
public class SocketServer : AppServer
{
public SocketServer()
: base(new CommandLineReceiveFilterFactory(Encoding.Default, new BasicRequestInfoParser(":", ",")))
{
// SocketServer类添加自定义命令行协议的分隔符(冒号和逗号)
}
protected override bool Setup(IRootConfig rootConfig, IServerConfig config)
{
Console.WriteLine("正在准备配置文件");
return base.Setup(rootConfig, config);
}
protected override void OnStarted()
{
Console.WriteLine("服务已开始");
base.OnStarted();
}
protected override void OnStopped()
{
Console.WriteLine("服务已停止");
base.OnStopped();
}
///
/// 输出新连接信息
///
///
protected override void OnNewSessionConnected(SocketSession session)
{
base.OnNewSessionConnected(session);
//输出客户端IP地址
Console.Write("\r\n" + session.LocalEndPoint.Address.ToString() + ":连接");
}
///
/// 输出断开连接信息
///
///
///
protected override void OnSessionClosed(SocketSession session, CloseReason reason)
{
base.OnSessionClosed(session, reason);
Console.Write("\r\n" + session.LocalEndPoint.Address.ToString() + ":断开连接");
}
}
}
在SuperSocket中的Command让我们进行扩展,使用方法也极其简单。只需要继承一个CommandBase
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SuperSocket.Common;
using SuperSocket.SocketBase;
using SuperSocket.SocketBase.Protocol;
using SuperSocket.SocketBase.Command;
namespace SuperSocketCustomCommandLineProtocol
{
public class LOGIN : CommandBase
{
///
/// 自定义执行命令方法,注意传入的变量session类型为MySession
///
/// 会话
/// 请求数据信息
public override void ExecuteCommand(SocketSession session, StringRequestInfo requestInfo)
{
session.Send(string.Format("LOGIN {0}:{1} {2}", session.Config.Ip, session.Config.Port, requestInfo.Body));
}
}
}
在program.cs类中改为使用App.config文件配置,用BootStrap启动服务器。
警告:要想使用BootStrap启动服务器,必须引用 using SuperSocket.SocketEngine;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SuperSocket.Common;
using SuperSocket.SocketBase;
using SuperSocket.SocketBase.Protocol;
using SuperSocket.SocketEngine;
namespace SuperSocketCustomCommandLineProtocol
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("请按任何键进行启动SuperSocket服务!");
Console.ReadKey();
Console.WriteLine();
var bootstrap = BootstrapFactory.CreateBootstrap();
if (!bootstrap.Initialize())
{
Console.WriteLine("初始化失败!");
Console.ReadKey();
return;
}
var result = bootstrap.Start();
Console.WriteLine("服务正在启动: {0}!", result);
if (result == StartResult.Failed)
{
Console.WriteLine("服务启动失败!");
Console.ReadKey();
return;
}
Console.WriteLine("服务启动成功,请按'q'停止服务!");
while (Console.ReadKey().KeyChar != 'q')
{
Console.WriteLine();
continue;
}
//停止服务
bootstrap.Stop();
Console.WriteLine("服务已停止!");
Console.ReadKey();
}
}
}
客户端发送字符串“LOGIN:kerry,123,456,789”+回车换行符
TerminatorReceiveFilter (SuperSocket.SocketBase.Protocol.TerminatorReceiveFilter, SuperSocket.SocketBase)
CountSpliterReceiveFilter (SuperSocket.Facility.Protocol.CountSpliterReceiveFilter, SuperSocket.Facility)
FixedSizeReceiveFilter (SuperSocket.Facility.Protocol.FixedSizeReceiveFilter, SuperSocket.Facility)
BeginEndMarkReceiveFilter (SuperSocket.Facility.Protocol.BeginEndMarkReceiveFilter, SuperSocket.Facility)
FixedHeaderReceiveFilter (SuperSocket.Facility.Protocol.FixedHeaderReceiveFilter, SuperSocket.Facility)
这一篇文章我们实现TerminatorReceiveFilter - 结束符协议,使用两个字符 "##" 作为结束符。(注:SuperSocket内置的默认命令行协议CommandLineProtocol使用回车换行符"\r\n"作为结束符)。 实现TerminatorReceiveFilter - 结束符协议,我们只需要在自定义AppServer类中实现如下代码即可。
///
/// TerminatorProtocolServer
/// Each request end with the terminator "##"
/// ECHO Your message##
///
namespace SuperSocketTerminatorReceiveFilter
{
public class TerminatorProtocolServer : AppServer
{
public TerminatorProtocolServer()
: base(new TerminatorReceiveFilterFactory("##"))
{
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SuperSocket.Common;
using SuperSocket.SocketBase;
using SuperSocket.SocketBase.Protocol;
using SuperSocket.SocketBase.Config;
using SuperSocket.SocketEngine;
namespace SuperSocketTerminatorReceiveFilter
{
public class TerminatorProtocolSession : AppSession
{
public override void Send(string message)
{
Console.WriteLine("发送消息:" + message);
base.Send(message);
}
protected override void OnSessionStarted()
{
//输出客户端IP地址
Console.WriteLine(this.LocalEndPoint.Address.ToString());
this.Send("Hello User,Welcome to SuperSocket Telnet Server!");
}
///
/// 连接关闭
///
///
protected override void OnSessionClosed(CloseReason reason)
{
base.OnSessionClosed(reason);
}
protected override void HandleUnknownRequest(StringRequestInfo requestInfo)
{
Console.WriteLine($"遇到未知的请求 Key:" + requestInfo.Key + $" Body:" + requestInfo.Body);
base.HandleUnknownRequest(requestInfo);
}
///
/// 捕捉异常并输出
///
///
protected override void HandleException(Exception e)
{
this.Send("error: {0}", e.Message);
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SuperSocket.Common;
using SuperSocket.SocketBase;
using SuperSocket.SocketBase.Protocol;
using SuperSocket.SocketBase.Config;
using SuperSocket.SocketEngine;
namespace SuperSocketTerminatorReceiveFilter
{
public class TerminatorProtocolServer : AppServer
{
public TerminatorProtocolServer()
: base(new TerminatorReceiveFilterFactory("##"))
{
}
protected override bool Setup(IRootConfig rootConfig, IServerConfig config)
{
Console.WriteLine("正在准备配置文件");
return base.Setup(rootConfig, config);
}
protected override void OnStarted()
{
Console.WriteLine("服务已开始");
base.OnStarted();
}
protected override void OnStopped()
{
Console.WriteLine("服务已停止");
base.OnStopped();
}
///
/// 输出新连接信息
///
///
protected override void OnNewSessionConnected(TerminatorProtocolSession session)
{
base.OnNewSessionConnected(session);
//输出客户端IP地址
Console.Write("\r\n" + session.LocalEndPoint.Address.ToString() + ":连接");
}
///
/// 输出断开连接信息
///
///
///
protected override void OnSessionClosed(TerminatorProtocolSession session, CloseReason reason)
{
base.OnSessionClosed(session, reason);
Console.Write("\r\n" + session.LocalEndPoint.Address.ToString() + ":断开连接");
}
}
}
在自定义AppServer类(TerminatorProtocolServer类)中实现如下代码即可实现TerminatorReceiveFilter - 结束符协议。
namespace SuperSocketTerminatorReceiveFilter
{
public class TerminatorProtocolServer : AppServer
{
public TerminatorProtocolServer()
: base(new TerminatorReceiveFilterFactory("##"))
{
}
警告: 自定义类TerminatorProtocolServer创建在根目录下,因此完整的路径是: SuperSocketTerminatorReceiveFilter.TerminatorProtocolServer
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SuperSocket.Common;
using SuperSocket.SocketBase;
using SuperSocket.SocketBase.Protocol;
using SuperSocket.SocketBase.Command;
namespace SuperSocketTerminatorReceiveFilter
{
public class LOGIN : CommandBase
{
///
/// 自定义执行命令方法,注意传入的变量session类型为MySession
///
/// 会话
/// 请求数据信息
public override void ExecuteCommand(TerminatorProtocolSession session, StringRequestInfo requestInfo)
{
session.Send(string.Format("LOGIN {0}:{1} {2}", session.Config.Ip, session.Config.Port, requestInfo.Body));
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SuperSocket.Common;
using SuperSocket.SocketBase;
using SuperSocket.SocketBase.Protocol;
using SuperSocket.SocketEngine;
namespace SuperSocketTerminatorReceiveFilter
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("请按任何键进行启动SuperSocket服务!");
Console.ReadKey();
Console.WriteLine();
var bootstrap = BootstrapFactory.CreateBootstrap();
if (!bootstrap.Initialize())
{
Console.WriteLine("初始化失败!");
Console.ReadKey();
return;
}
var result = bootstrap.Start();
Console.WriteLine("服务正在启动: {0}!", result);
if (result == StartResult.Failed)
{
Console.WriteLine("服务启动失败!");
Console.ReadKey();
return;
}
Console.WriteLine("服务启动成功,请按'q'停止服务!");
while (Console.ReadKey().KeyChar != 'q')
{
Console.WriteLine();
continue;
}
//停止服务
bootstrap.Stop();
Console.WriteLine("服务已停止!");
Console.ReadKey();
}
}
}
客户端发送字符串“LOGIN kerry 123 456 789”+"##"
我们要实现自己定义的Server类和Session类,来重写SuperSocket框架原生的Server类或Session类的方法,通过添加自己所需的属性,来实现自己的业务逻辑,并且也不在使用事件来绑定接收,连接,或关闭事件,全部交给Bootstrap来执行,(这个Bootstrap并不是指前端框架的Bootstrap,而是指的SuperSocket框架的一个引导程序或说是辅助程序),就是这里我们使用Bootstrap 来配置启动SuperSocket服务器程序。
不需要修改任何源程序就可以完成服务器的启动呢?答案就是使用APP.config配置文件启动服务器。我们可以在配置文件中方便的修改服务器的端口号和IP地址,而不需要修改源程序。
AppSession 代表一个和客户端的逻辑连接,基于连接的操作应该放在该类之中。你可以用该类的实例发送数据到客户端,接收客户端发送的数据或者关闭连接。
使用方法:创建自定义类SocketSession,继承AppSession类并重写AppSession类的方法(注意:一个AppSession对象对应一个连接)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SuperSocket.Common;
using SuperSocket.SocketBase;
using SuperSocket.SocketBase.Protocol;
using SuperSocket.SocketBase.Config;
using SuperSocket.SocketEngine;
namespace SuperSocketClass.Config
{
///
/// 自定义连接类SocketSession,继承AppSession,并传入到AppSession
///
public class SocketSession : AppSession
{
public override void Send(string message)
{
Console.WriteLine("发送消息:" + message);
base.Send(message);
}
protected override void OnSessionStarted()
{
//输出客户端IP地址
Console.WriteLine(this.LocalEndPoint.Address.ToString());
this.Send("Hello User,Welcome to SuperSocket Telnet Server!");
}
///
/// 连接关闭
///
///
protected override void OnSessionClosed(CloseReason reason)
{
base.OnSessionClosed(reason);
}
protected override void HandleUnknownRequest(StringRequestInfo requestInfo)
{
Console.WriteLine($"遇到未知的请求 Key:" + requestInfo.Key + $" Body:" + requestInfo.Body);
base.HandleUnknownRequest(requestInfo);
}
///
/// 捕捉异常并输出
///
///
protected override void HandleException(Exception e)
{
this.Send("error: {0}", e.Message);
}
}
}
AppServer 代表了监听客户端连接,承载TCP连接的服务器实例。理想情况下,我们可以通过AppServer实例获取任何你想要的客户端连接,服务器级别的操作和逻辑应该定义在此类之中。
使用方法:创建自定义类SocketServer,继承AppServer类并重写AppServer类的方法
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SuperSocket.Common;
using SuperSocket.SocketBase;
using SuperSocket.SocketBase.Protocol;
using SuperSocket.SocketBase.Config;
using SuperSocket.SocketEngine;
namespace SuperSocketClass.Config
{
public class SocketServer : AppServer
{
protected override bool Setup(IRootConfig rootConfig, IServerConfig config)
{
Console.WriteLine("正在准备配置文件");
return base.Setup(rootConfig, config);
}
protected override void OnStarted()
{
Console.WriteLine("服务已开始");
base.OnStarted();
}
protected override void OnStopped()
{
Console.WriteLine("服务已停止");
base.OnStopped();
}
///
/// 输出新连接信息
///
///
protected override void OnNewSessionConnected(SocketSession session)
{
base.OnNewSessionConnected(session);
//输出客户端IP地址
Console.Write("\r\n" + session.LocalEndPoint.Address.ToString() + ":连接");
}
///
/// 输出断开连接信息
///
///
///
protected override void OnSessionClosed(SocketSession session, CloseReason reason)
{
base.OnSessionClosed(session, reason);
Console.Write("\r\n" + session.LocalEndPoint.Address.ToString() + ":断开连接");
}
}
}
在SuperSocket中的Command让我们进行扩展,使用方法也极其简单。只需要继承一个CommandBase
//appServer.NewRequestReceived += new RequestHandler(appServer_NewRequestReceived);
using SuperSocket.SocketBase.Protocol;
namespace SuperSocket.SocketBase.Command
{
//
// 摘要:
// Command base class
//
// 类型参数:
// TAppSession:
// The type of the app session.
//
// TRequestInfo:
// The type of the request info.
public abstract class CommandBase : ICommand, ICommand
where TAppSession : IAppSession, IAppSession, new()
where TRequestInfo : IRequestInfo
{
protected CommandBase();
//
// 摘要:
// Gets the name.
public virtual string Name { get; }
//
// 摘要:
// Executes the command.
//
// 参数:
// session:
// The session.
//
// requestInfo:
// The request info.
public abstract void ExecuteCommand(TAppSession session, TRequestInfo requestInfo);
//
// 摘要:
// Returns a System.String that represents this instance.
//
// 返回结果:
// A System.String that represents this instance.
public override string ToString();
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SuperSocket.Common;
using SuperSocket.SocketBase;
using SuperSocket.SocketBase.Protocol;
using SuperSocket.SocketBase.Command;
namespace SuperSocketClass.Config
{
public class Hello : CommandBase
{
///
/// 自定义执行命令方法,注意传入的变量session类型为MySession
///
/// 会话
/// 请求数据信息
public override void ExecuteCommand(SocketSession session, StringRequestInfo requestInfo)
{
session.Send(string.Format("Hello {0}:{1} {2}", session.Config.Ip, session.Config.Port, requestInfo.Body));
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SuperSocket.Common;
using SuperSocket.SocketBase;
using SuperSocket.SocketBase.Protocol;
using SuperSocket.SocketBase.Command;
namespace SuperSocketClass.Config
{
public class ADD : CommandBase
{
public override void ExecuteCommand(SocketSession session, StringRequestInfo requestInfo)
{
session.Send(requestInfo.Parameters.Select(p => Convert.ToInt32(p)).Sum().ToString());
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SuperSocket.Common;
using SuperSocket.SocketBase;
using SuperSocket.SocketBase.Protocol;
using SuperSocket.SocketBase.Command;
namespace SuperSocketClass.Config
{
public class MULT : CommandBase
{
public override void ExecuteCommand(SocketSession session, StringRequestInfo requestInfo)
{
var result = 1;
foreach (var factor in requestInfo.Parameters.Select(p => Convert.ToInt32(p)))
{
result *= factor;
}
session.Send(result.ToString());
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SuperSocket.Common;
using SuperSocket.SocketBase;
using SuperSocket.SocketBase.Protocol;
using SuperSocket.SocketBase.Command;
namespace SuperSocketClass.Config
{
public class Echo : CommandBase
{
public override void ExecuteCommand(SocketSession session, StringRequestInfo requestInfo)
{
session.Send(requestInfo.Body);
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SuperSocket.Common;
using SuperSocket.SocketBase;
using SuperSocket.SocketBase.Protocol;
using SuperSocket.SocketEngine;
/****************************************************************
* 描述说明:自定义连接类SocketSession,继承AppSession,并传入到AppSession
*****************************************************************/
namespace SuperSocketClass
{
class Program
{
///
/// SuperSocket服务启动或停止
///
///
static void Main(string[] args)
{
//var appServer = new AppServer();
//启动应用服务端口
//if (!appServer.Setup(2017)) //启动时监听端口2017
//{
//Console.WriteLine("服务端口启动失败!");
//Console.ReadKey();
//return;
//}
//Console.WriteLine();
//尝试启动应用服务
//if (!appServer.Start())
//{
//Console.WriteLine("服务启动失败!");
//Console.ReadKey();
//return;
//}
//Console.WriteLine("服务启动成功,请按'q'停止服务!");
//appServer.NewSessionConnected += appServer_NewSessionConnected;
//appServer.NewRequestReceived += appServer_NewRequestReceived;
//while (Console.ReadKey().KeyChar != 'q')
//{
//Console.WriteLine();
//continue;
//}
//停止服务
//appServer.Stop();
//Console.WriteLine("服务已停止!");
//Console.ReadKey();
// }
//static void appServer_NewSessionConnected(AppSession session)
//{
//session.Send("Welcome to SuperSocket Telnet Server!");
//}
///
///客户端请求处理
///
/// 会话
/// 请求信息
//static void appServer_NewRequestReceived(AppSession session, StringRequestInfo requestInfo)
//{
//switch (requestInfo.Key.ToUpper())
//{
// case ("ECHO"):
//session.Send(requestInfo.Body);
//break;
// case ("ADD"):
//session.Send(requestInfo.Parameters.Select(p => Convert.ToInt32(p)).Sum().ToString());
//break;
// case ("MULT"):
//var result = 1;
//foreach (var factor in requestInfo.Parameters.Select(p => Convert.ToInt32(p)))
//{
//result *= factor;
//}
//session.Send(result.ToString());
//break;
//}
Console.WriteLine("请按任何键进行启动SuperSocket服务!");
Console.ReadKey();
Console.WriteLine();
var bootstrap = BootstrapFactory.CreateBootstrap();
if (!bootstrap.Initialize())
{
Console.WriteLine("初始化失败!");
Console.ReadKey();
return;
}
var result = bootstrap.Start();
Console.WriteLine("服务正在启动: {0}!", result);
if (result == StartResult.Failed)
{
Console.WriteLine("服务启动失败!");
Console.ReadKey();
return;
}
Console.WriteLine("服务启动成功,请按'q'停止服务!");
while (Console.ReadKey().KeyChar != 'q')
{
Console.WriteLine();
continue;
}
//停止服务
// appServer.Stop();
bootstrap.Stop();
Console.WriteLine("服务已停止!");
Console.ReadKey();
}
}
}
从下图可以观察到,实例运行的AppServer类型为:SocketServer,它的全称为:SuperSocketClass.Config.SocketServer。(因为自定义类SocketServer创建在Config文件夹下,因此它的全称为SuperSocketClass.Config.SocketServer)
客户端输入字符串"Hello C# SuperSocket Very Good!!!"。
客户端输入字符串"ADD 2 3 5"。
客户端输入字符串"MULT 2 3 5"。
客户端输入字符串"Echo hello world!"。
警告:在客户端输入字符串,一定要按下回车键后才能点【发送数据】按钮,因为SuperSocket规定客户端给服务器发送的报文必须以"\r\n"为结束符,否则服务器会认为是非法报文不预处理。
(1)、SocketServer、自定义命令和ScoketSession的访问权限必须设置为public
(2)、 SocketServerr父类为AppServer
(3)、 SocketSession父类为AppSession
(4)、Hello父类为CommandBase
(5)、多服务器中需注意AppSession、AppServer、自定义命令中的AppSession不要搞错
App.config中添加代码如下:
这里有个坑,要注意,就是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 事件,会在服务端接收到完整的数据后促发。
我们开启一个TCP的客户端发送数据。
我们首先要看的是事件里面的:
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需要继承IRequestInfo:
///
/// 简单的将过来的数据进行格式化
///
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就需要关联一个RequestInfo,我们就关联刚刚自定义的SimpleRequestInfo。
public class SimpleSession : AppSession
{
///
/// 异常处理
///
///
protected override void HandleException(Exception e)
{
this.Send("Application error: {0}", e.Message);
}
///
/// 有新的命令执行的时候触发;
/// 只有服务不去订阅NewRequestReceived事件的时候,才会触发这个函数
///
///
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之后,我们就可以自定义服务了:
///
/// 服务
///
public class MySvr : AppServer
{
}
这才是完整的写法。
我们可以通过服务的构造函数装配过滤器。
///
/// 服务
///
public class MySvr : AppServer
: 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
{
public MyFixedHeaderFilter()
: base(6)
{
}
///
/// 获取数据长度部分
///
///
///
///
///
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;
}
///
/// 加过滤器的好处是,会将没有用的信息自动跳出去
/// 就体现在下面这段代码了!!!!
///
///
///
///
///
///
protected override SimpleRequestInfo ResolveRequestInfo(ArraySegment 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。
///
/// 固定头协议服务
///
public class FixedHeaderSvr : AppServer
{
public FixedHeaderSvr()
: base(new DefaultReceiveFilterFactory()) //使用默认的接受过滤器工厂 (DefaultReceiveFilterFactory)
{
}
}
现象,数据过大时,服务端接收不到数据,客户端被踢下线。
通过调试:在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().Publish("客户端断开原因: " + reason.ToString());
}
最后定位到参数:maxRequestLength
随后修改配置文件,加上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证书的信息
写了一个Terminator的服务,修改了结束符,一改发现中文就乱码了!我在配置中配置了服务默认为UTF8:
发送也是按UTF8发送的,但是接收就乱码了,后来发现,TerminatorReceiveFilterFactory有个重载函数,可以指定编码!改了就没乱码了!
public TerminatorSvr()
: base(new TerminatorReceiveFilterFactory("##",Encoding.UTF8))
{
}
默认情况下:TerminatorReceiveFilterFactory是ASCII
Socket通讯不管怎么封装,其核心必然是Listen、Accept、Receive、Send等关键,因此从最底层开始往上抽丝剥茧,解读SuperSocket的逻辑。
项目SuperSocket.SocketEngine.Net45 实现对Socket链接、收发数据的封装。
项目SuperSocket.SocketBase.Net45实现对AppServer AppSession等业务逻辑的抽象封装。
整个SuperSocket是基于.net SocketAsyncEventArgs实现的
1、SocketServerBase:一个SocketServer可以监听多个端口
public SocketServerBase(IAppServer appServer, ListenerInfo[] listeners)
{....} //ListenerInfo[]表示多个端口
public virtual bool Start()
{
......
//创建多个监听
for (var i = 0; i < ListenerInfos.Length; i++)
{
var listener = CreateListener(ListenerInfos[i]); //创建一个Socket监听
listener.Error += new ErrorHandler(OnListenerError);
listener.Stopped += new EventHandler(OnListenerStopped);
listener.NewClientAccepted += new NewClientAcceptHandler(OnNewClientAccepted); //OnNewClientAccepted是抽象方法,在AsyncSocketServer中实现;
if (listener.Start(AppServer.Config))
{
Listeners.Add(listener);
........
}
}
......
}
//子类TcpSocketServerBase实现抽象方法创建监听
protected override ISocketListener CreateListener(ListenerInfo listenerInfo)
{
return new TcpAsyncSocketListener(listenerInfo);
}
2、SocketListenerBase,抽象类,实现接口ISocketListener,定义基础的start stop error等事件;TcpAsyncSocketListener ,SocketListenerBase子类,封装了socket监听:
public override bool Start(IServerConfig config)
{
m_ListenSocket = new Socket(this.Info.EndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); //创建socket;
......
ProcessAccept(acceptEventArg);//处理accept
}
void ProcessAccept(SocketAsyncEventArgs e)
{
......
if (socket != null)
OnNewClientAccepted(socket, null);//触发OnNewClientAccepted,通过这个事件,又回到了AsyncSocketServer.OnNewClientAccepted
........
}
3. AsyncSocketServer , TcpSocketServerBase的子类,是获取新连接,监听收数据的关键
public override bool Start()
{
m_BufferManager = new BufferManager(bufferSize * AppServer.Config.MaxConnectionNumber, bufferSize); //预生成了一块缓冲区,根据ServerConfig的最大链接数预生成
//预创建MaxConnectionNumber个SocketAsyncEventArgsProxy
var socketArgsProxyList = new List(AppServer.Config.MaxConnectionNumber);
for (int i = 0; i < AppServer.Config.MaxConnectionNumber; i++)
{
//Pre-allocate a set of reusable SocketAsyncEventArgs
socketEventArg = new SocketAsyncEventArgs();
m_BufferManager.SetBuffer(socketEventArg);
socketArgsProxyList.Add(new SocketAsyncEventArgsProxy(socketEventArg));
}
//创建了读写时的队列,基于ConcurrentStack,是.net 并行框架的一个库,是无锁的,提高效率;参考资料:http://www.cnblogs.com/zw369/p/3990908.html
m_ReadWritePool = new ConcurrentStack(socketArgsProxyList);
......
}
//接着分析NewClientAccepted事件
protected override void OnNewClientAccepted(ISocketListener listener, Socket client, object state)
{
........
ProcessNewClient(client, listener.Info.Security);
}
//处理新的Seesion接入(新的客户端Socket接入)
private IAppSession ProcessNewClient(Socket client, SslProtocols security)
{
if (!m_ReadWritePool.TryPop(out socketEventArgsProxy)).....
//AsyncSocketSession封装了处理某一个client Socket数据收发的类
socketSession = new AsyncSocketSession(client, socketEventArgsProxy);
//创建了一个AppSession对象,CreateSession在TcpSocketServerBase中实现;主要是对clientSocket进行设置缓冲区大小,超时时间等;
AppSession代表一个和客户端的逻辑连接,官方说明:http://docs.supersocket.net/v1-6/zh-CN/Implement-your-AppServer-and-AppSession
通过AppSession,将AsyncSocketServer 与AppServer关联起来了;
最终是调用了AppServerBase.CreateAppSession,并执行了ExecuteConnectionFilters,ConnectionFilter的目的是对客户端的请求进行验证,更多ConnectionFilter参考官网说明:http://docs.supersocket.net/v1-6/zh-CN/Connection-Filter;
返回的AppSession是什么类型?后面继续讲
var session = CreateSession(client, socketSession);
......
//AsyncStreamSocketSession继承了INegotiateSocketSession,用于处理SSL加密;
var negotiateSession = socketSession as INegotiateSocketSession;
if (negotiateSession == null) //一般情况是AsyncSocketSession
{
//AppServer注册了session的Closed事件并绑到了SessionClosed事件上,同时触发了NewSessionConnected事件;
if (RegisterSession(session))
{
AppServer.AsyncRun(() => socketSession.Start());//异步启动接收数据;这里是最重点的部分;
}
return session;
}
negotiateSession.NegotiateCompleted += OnSocketSessionNegotiateCompleted;
negotiateSession.Negotiate();
}
从上面部分代码的解读,可以清楚了解到SuperSocket是如何监听一个端口的,如何响应一个客户端Socket的链接请求的(NewSessionConnected),以及如何响应客户端Socket的关闭事件的(SessionClosed)。
4、接下来解读如何处理收数据、解析数据
AsyncSocketSession:
//开始接收数据
public override void Start()
{
StartReceive(SocketAsyncProxy.SocketEventArgs);
if (!m_IsReset)
StartSession();
}
private void StartReceive(SocketAsyncEventArgs e, int offsetDelta)
{......
// the connection is closing or closed
if (!OnReceiveStarted())
return;
willRaiseEvent = Client.ReceiveAsync(e);
......
ProcessReceive(e)
}
public void ProcessReceive(SocketAsyncEventArgs e)
{
......
//由AppSession进行进一步的数据处理;
offsetDelta = this.AppSession.ProcessRequest(e.Buffer, e.Offset, e.BytesTransferred, true);
......
//read the next block of data sent from the client
StartReceive(e, offsetDelta);
}
AppSession :
int IAppSession.ProcessRequest(byte[] readBuffer, int offset, int length, bool toBeCopied)
{
int rest, offsetDelta;
while (true)
{
//解析数据,FilterRequest的解读在下一段;
var requestInfo = FilterRequest(readBuffer, offset, length, toBeCopied, out rest, out offsetDelta);
if (requestInfo != null)
{
try
{
//执行指令,将解析出来的数据进一步进行业务逻辑处理;解读在后面...
AppServer.ExecuteCommand(this, requestInfo);
}
........
}
if (rest <= 0)
{
return offsetDelta;
}
//Still have data has not been processed
offset = offset + length - rest;
length = rest;
}//循环进行数据解析,直到缓冲区的数据全部解析完,目的是为了防止有粘包...
}
解析数据
TRequestInfo FilterRequest(byte[] readBuffer, int offset, int length, bool toBeCopied, out int rest, out int offsetDelta)
{
//触发AppServer的RawDataReceived事件;从这里可以看出来,RawDataReceived是原始的数据包,如果返回false则不会继续往下执行。
if (!AppServer.OnRawDataReceived(this, readBuffer, offset, length))
{
rest = 0;
offsetDelta = 0;
return null;
}
var currentRequestLength = m_ReceiveFilter.LeftBufferSize;
//调用ReceiveFilter处理接收到的数据,ReceiveFilter的官网说明:http://docs.supersocket.net/v1-6/zh-CN/Implement-Your-Own-Communication-Protocol-with-IRequestInfo,-IReceiveFilter-and-etc
官方内置了一些常用的Filter,http://docs.supersocket.net/v1-6/zh-CN/The-Built-in-Common-Format-Protocol-Implementation-Templates
利用自定义的Filter可以实现自己业务数据的解析;
var requestInfo = m_ReceiveFilter.Filter(readBuffer, offset, length, toBeCopied, out rest);
if (m_ReceiveFilter.State == FilterState.Error)
{
rest = 0;
offsetDelta = 0;
Close(CloseReason.ProtocolError);
return null;
}
var offsetAdapter = m_ReceiveFilter as IOffsetAdapter;
offsetDelta = offsetAdapter != null ? offsetAdapter.OffsetDelta : 0;
.......
return requestInfo;
}
//执行指令。AppServerBase类中。
protected virtual void ExecuteCommand(TAppSession session, TRequestInfo requestInfo)
{
if (m_RequestHandler == null)
{
//从命令加载器获取对应命令,参考官网说明:http://docs.supersocket.net/v1-6/zh-CN/Command-and-Command-Loader
var commandProxy = GetCommandByName(requestInfo.Key);
if (commandProxy != null)
{
var command = commandProxy.Command;
var commandFilters = commandProxy.Filters;
session.CurrentCommand = requestInfo.Key;
var cancelled = false;
if (commandFilters == null)
{
//执行命令
command.ExecuteCommand(session, requestInfo);
}
else
{
//如果有命令过滤器,参考官网说明:http://docs.supersocket.net/v1-6/zh-CN/Command-Filter,命令过滤器的作用是可以在执行前,执行后进行进一步的业务处理,用Attribute实现的,类似一种AOP的作用;
var commandContext = new CommandExecutingContext();
commandContext.Initialize(session, requestInfo, command);
for (var i = 0; i < commandFilters.Length; i++)
{
var filter = commandFilters[i];
//命令执行前
filter.OnCommandExecuting(commandContext);
if (commandContext.Cancel)
{
cancelled = true;
if(Logger.IsInfoEnabled)
Logger.Info(session, string.Format("The executing of the command {0} was cancelled by the command filter {1}.", command.Name, filter.GetType().ToString()));
break;
}
}
if (!cancelled)
{
try
{
//执行命令
command.ExecuteCommand(session, requestInfo);
}
catch (Exception exc)
{
commandContext.Exception = exc;
}
for (var i = 0; i < commandFilters.Length; i++)
{
var filter = commandFilters[i];
//命令执行后
filter.OnCommandExecuted(commandContext);
}
if (commandContext.Exception != null && !commandContext.ExceptionHandled)
{
try
{
session.InternalHandleExcetion(commandContext.Exception);
}
catch
{
}
}
}
}
if(!cancelled)
{
session.PrevCommand = requestInfo.Key;
if (Config.LogCommand && Logger.IsInfoEnabled)
Logger.Info(session, string.Format("Command - {0}", requestInfo.Key));
}
}
else
{
session.InternalHandleUnknownRequest(requestInfo);
}
session.LastActiveTime = DateTime.Now;
}
else
{
session.CurrentCommand = requestInfo.Key;
try
{
//触发NewRequestReceived事件,由事件响应处理指令;
m_RequestHandler(session, requestInfo);
}
catch (Exception e)
{
session.InternalHandleExcetion(e);
}
......
}
.......
}
关键字: 主动推送, 推送数据, 客户端推送, 获取Session, 发送数据, 回话快照
前面已经说过,AppSession 代表了一个逻辑的 socket 连接,基于连接的操作都应该定义在此类之中。 这个AppSession 类也封装了通过 socket 发送数据的方法。 你可以使用 AppSession 的方法 "Send(...)" 来发送数据到客户端:
session.Send(data, 0, data.Length);
or
session.Send("Welcome to use SuperSocket!");
前面提到过,如果你获取了连接的 Session 实例,你就可以通过 "Send()" 方法向客户端发送数据。但是在某些情况下,你无法直接获取 Session 实例。
SuperSocket 提供了一个 API 让你从 AppServer 的 Session 容器中通过 SessionID 获取 Session
var session = appServer.GetSessionByID(sessionID);
if(session != null)
session.Send(data, 0, data.Length);
SessionID是什么?
SessionID 是 AppSession 类的一个属性,用于唯一标识一个 Session 实例。 在一个 SuperSocket TCP 服务器中,当 Session 一创建, SessionID 就会被赋值为一个 GUID 字符串。 如果你不在 SuperSocket UDP 服务器中使用 UdpRequestInfo,SessionID 就会有客户端的IP和端口组成。 如果你使用UdpRequestInfo,SessionID将会从客户端传过来。
你也可以从 AppServer 实例获取所有连接上的 session 然后推送数据到所有客户端:
foreach(var session in appServer.GetAllSessions())
{
session.Send(data, 0, data.Length);
}
如果你启用了 Session 快照, 这些从 AppServer.GetAllSessions() 获取的 sessions 将不是实时更新的。 他们是在上次获取快照时所有连接到服务器的 Session。 快照相关配置,请参考配置文档。
如果你有一个自定义的属性 "CompanyId" 在你的 AppSession 类之中,如果你想要获取这个属性等于某值的 的所有 Session, 你可以使用 AppServer 的方法 GetSessions(...):
var sessions = appServer.GetSessions(s => s.CompanyId == companyId);
foreach(var s in sessions)
{
s.Send(data, 0, data.Length);
}
和方法 "GetAllSessions(...)" 一样, 如果你启用了 Session 快照,这些返回的 session 也一样也来自于快照之中。
通过上述代码解读后,对SuperSocket的结构有了一个清晰的概念,对照官网这张结构图,会更直观:
但是从代码中可以看出来,在Command执行期间,如果存在大运算或CPU密集型计算,仍然有可能造成阻塞,因为指令执行这一部分并没有用到线程池,所以在处理业务逻辑时要着重注意!
跟物联网设备常用的通信协议有TCP,MQTT.今天我们介绍的是TCP连接,TCP连接程序的组件有Supersocket,do.NETty.Supersocket相信搞过.net的朋友应该都知道,dotnetty是有微软Azure从JAVA平台下移植过来的一个高性能、异步事件驱动的 NIO 框架,Kafka和RocketMQ等消息中间件、ElasticSearch开源搜索引擎、大数据处理Hadoop的RPC框架Avro、分布式通信框架Dubbo,都使用了Netty,Netty的资料很多,有兴趣的可以搜一下,dotnetty和Netty语法基本一致,所以资料可以互相参考,今天我们介绍的是.net下supersocket的使用以及源码分析。
SuperSocket 可以和 ASP.NET Core 网站一起同时运行。你需要做的是将 SuperSocket 注册到 ASP.NET Core 网站的host builder中去, 同时将服务器的选项放到配置文件中或者通过代码定义。
//don't forget the usings
using SuperSocket;
using SuperSocket.ProtoBase;
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup();
})
.AsSuperSocketHostBuilder()
.UsePackageHandler(async (s, p) =>
{
// echo message back to client
await s.SendAsync(Encoding.UTF8.GetBytes(p.Text + "rn"));
});
同时将服务器的配置选项放到配置文件 "Appsettings.json" 中去:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"serverOptions": {
"name": "TestServer",
"listeners": [
{
"ip": "Any",
"port": 4040
}
]
},
"AllowedHosts": "*"
}
上面代码就可以实现supersocket和asp.net core (.net core3,.net5,.net6)的集成,可以支持最新的.NET6,当然DotNetty也支持.Net 6
今天我们分析源码的版本是supersocket V2.0的版本,跟之前.net freamwork下有很大的区别
主流程图
1分析的入口就从SuperSocketService这个类开始,他集成自IHostedService, IServer,因此这个类的StartAsync就是执行的入口
async Task IServer.StartAsync()
{
await StartAsync(CancellationToken.None);
return true;
}
2.通过channelCreatorFactory创建监听器,监听代码如下
public bool Start()
{
var options = Options;
try
{
if (options.Security != SslProtocols.None && options.CertificateOptions != null)
{
options.CertificateOptions.EnsureCertificate();
}
var listenEndpoint = options.GetListenEndPoint();
var listenSocket = _listenSocket = new Socket(listenEndpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
listenSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
listenSocket.LingerState = new LingerOption(false, 0);
if (options.NoDelay)
listenSocket.NoDelay = true;
listenSocket.Bind(listenEndpoint);
listenSocket.Listen(options.BackLog);
IsRunning = true;
_cancellationTokenSource = new CancellationTokenSource();
KeepAccept(listenSocket).DoNotAwait();
return true;
}
catch (Exception e)
{
_logger.LogError(e, $"The listener[{this.ToString()}] failed to start.");
return false;
}
}
3.开启异步接收accept线程
private async Task KeepAccept(Socket listenSocket)
{
while (!_cancellationTokenSource.IsCancellationRequested)
{
try
{
var client = await listenSocket.AcceptAsync().ConfigureAwait(false);
OnNewClientAccept(client);
}
catch (Exception e)
{
if (e is ObjectDisposedException || e is NullReferenceException)
break;
if (e is SocketException se)
{
var errorCode = se.ErrorCode;
//The listen socket was closed
if (errorCode == 125 || errorCode == 89 || errorCode == 995 || errorCode == 10004 || errorCode == 10038)
{
break;
}
}
_logger.LogError(e, $"Listener[{this.ToString()}] failed to do AcceptAsync");
continue;
}
}
_stopTaskCompletionSource.TrySetResult(true);
}
4 当有连接进来后,执行执行注册的事件
listener.NewClientAccepted += OnNewClientAccept;
5.accepted后就创建新的Channel,session
private void AcceptNewChannel(IChannel channel)
{
var session = _sessionFactory.Create() as AppSession;
HandleSession(session, channel).DoNotAwait();
}
6 channel.Start()
public override void Start()
{
_readsTask = ProcessReads();
_sendsTask = ProcessSends();
WaitHandleClosing();
}
7.将从内核里读的socket数据异步写到 Pipe中
protected virtual async Task ProcessReads()
{
var pipe = In;
Task writing = FillPipeAsync(pipe.Writer);
Task reading = ReadPipeAsync(pipe.Reader);
await Task.WhenAll(reading, writing);
}
8当Pipe有数据写入后,通知Pipe读线程去解析数据,这里通知用的方法是
ManualResetValueTaskSourceCore,写线程写入数据后执行 _taskSourceCore.SetResult(target);就会触发读线程去读,读的时候会根据你设置的协议模版去解析,这个过程会去处理粘包和拆包的过程,因为Pipe是可以定向的从流中取部分数据的
内置的协议模板,参照上文。
9 读取到的数据解析成packageInfo 后继续往下执行
await foreach (var p in packageChannel.RunAsync())
{
if(_packageHandlingContextAccessor!=null)
{
_packageHandlingContextAccessor.PackageHandlingContext = new PackageHandlingContext(session, p);
}
await packageHandlingScheduler.HandlePackage(session, p);
}
10再执行到我们定义的command即可
ValueTask IPackageHandler.Handle(IAppSession session, TNetPackageInfo package)
{
return HandlePackage(session, PackageMapper.Map(package));
}
11 执行我们预制的command代码
[Command("add")]
public class ADD : IAsyncCommand
{
public async ValueTask ExecuteAsync(IAppSession session, StringPackageInfo package)
{
var result = package.Parameters
.Select(p => int.Parse(p))
.Sum();
await session.SendAsync(Encoding.UTF8.GetBytes(result.ToString() + "rn"));
}
}