SuperSocket:动态协议服务端开发全解析
在开发 TCP/Socket 服务时,我们经常会遇到 不同协议类型共存 的需求,例如一部分客户端用 分隔符协议 (Terminator),另一部分客户端用 固定长度头协议 (FixedHeader)。本文通过 WPF 框架结合 SuperSocket,详细讲解如何实现一个灵活可配置的服务端。
1. SuperSocket 简介
SuperSocket 是一款基于 .NET 的高性能、可扩展 TCP Socket 框架。
它的核心特点包括:
- 高性能 I/O :基于
System.IO.Pipelines
,支持异步流处理。 - 协议可扩展 :通过
PipelineFilter
可以定义各种自定义协议。 - DI 与 Logging 支持:完美集成 .NET Core 的依赖注入与日志体系。
- 支持多种主机类型:可在 Console、Windows 服务、WPF 或 ASP.NET 环境中运行。
在 SuperSocket 中,核心概念有:
- TPackageInfo :代表每个完整消息包的对象,框架通过
PipelineFilter
将字节流解码成TPackageInfo
。 - PipelineFilter:字节流到消息包的解码器,支持多种协议类型(Terminator、FixedHeader、LengthField 等)。
- IPipelineFilterFactory:工厂接口,用于创建不同类型的 PipelineFilter。
- AppSession:每个客户端连接的会话对象,通过它发送或接收消息。
2. 定义消息包
在本例中,我们定义一个简单的 StringPackageInfo
:
csharp
public class StringPackageInfo
{
public string Key { get; set; }
public string[] Parameters { get; set; } = Array.Empty<string>();
public string Body { get; set; }
}
Key
:命令名,例如ADD
。Parameters
:命令参数,例如"1 2"
。Body
:原始消息文本。
3. 自定义 PipelineFilter
3.1 Terminator 协议
csharp
public class MyTerminatorFilter : TerminatorPipelineFilter<StringPackageInfo>
{
public MyTerminatorFilter(byte[] terminator) : base(terminator) { }
protected override StringPackageInfo DecodePackage(ref ReadOnlySequence<byte> buffer)
{
var text = buffer.GetString(Encoding.UTF8)?.Trim();
if (string.IsNullOrEmpty(text)) return new StringPackageInfo();
var parts = text.Split(' ', StringSplitOptions.RemoveEmptyEntries);
return new StringPackageInfo
{
Key = parts[0],
Parameters = parts.Skip(1).ToArray(),
Body = text
};
}
}
- 通过
TerminatorPipelineFilter
,我们可以指定任意分隔符(如\r\n
、$$
等)。 DecodePackage
将字节流解码成StringPackageInfo
。
3.2 FixedHeader 协议
csharp
using SuperSocket.ProtoBase;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TcpSvrTest.Filter;
public class MyFixedFilter : FixedHeaderPipelineFilter<StringPackageInfo>
{
//数据格式:
// -------+----------+-------------------------------------------+
// 0001 | 00000010 | 4C36 3150 2D43 4D2B 4C30 3643 5055 2D43 4D2B 4C4A |
// 固定头 | 数据长度 | 数据 |
// 2byte | 4byte | |
// -------+----------+---------------------------------+
private const int HeaderSize = 6; //Header总长度
public MyFixedFilter()
: base(HeaderSize)
{
}
string cmdValue = "";
protected override int GetBodyLengthFromHeader(ref ReadOnlySequence<byte> buffer)
{
var reader = new SequenceReader<byte>(buffer);
// ✅ 1. 检查是否有足够的头部数据
if (buffer.Length < 6)
{
// 数据不足,等待更多数据到来(返回 -1)
return -1;//这会将客户端被踢掉
}
//先读头
reader.TryReadLittleEndian(out short fix);
if (!Enum.IsDefined(typeof(CommandId), (UInt16)fix))
{
//Console.WriteLine("不是有效的固定头");
return -1;//这会将客户端被踢掉
}
//头转换为枚举字符串,变成Key,这样就可以路由了
cmdValue = ((CommandId)fix).ToString(); ;
//他真的,我哭死(太贴心了)
reader.TryReadLittleEndian(out int len); //以小端方式读取short类型数据
return len;
}
protected override StringPackageInfo DecodePackage(ref ReadOnlySequence<byte> buffer)
{
StringPackageInfo info = new StringPackageInfo();
// ✅ 3. 枚举变Key
string key = cmdValue; // 直接得到 "LOGIN", "SEND"...
//跳过前面6个字节
var new_buffer = buffer.Slice(HeaderSize).ToArray();
//合成数据
info.Key = key; // 基于StringPackageInfo的Key可以实现路由~~~~~,这样固定头也可以路由,就可以有意义了
info.Body = Encoding.UTF8.GetString(new_buffer);
return info;
}
}
- FixedHeader 协议通过读取固定长度头部来获取消息体长度。
- 非常适合二进制协议或者自定义长度包。
4. 配置工厂实现动态协议
为了支持 不同类型客户端协议动态选择 ,我们实现了 ConfigurableFilterFactory
:
csharp
public class ConfigurableFilterFactory : IPipelineFilterFactory<StringPackageInfo>
{
private readonly string _protocolType;
private readonly string _terminator;
public ConfigurableFilterFactory(string protocolType, string terminator = "\r\n")
{
_protocolType = protocolType;
_terminator = terminator;
}
public IPipelineFilter<StringPackageInfo> Create(object client)
{
return _protocolType switch
{
"Terminator" => new MyTerminatorFilter(Encoding.UTF8.GetBytes(_terminator)),
"FixedHeader" => new MyFixedFilter(),
_ => throw new NotSupportedException($"Unknown protocol: {_protocolType}")
};
}
}
- 通过依赖注入,我们可以在 WPF 或 Console 应用中选择协议类型。
- Terminator 的结束符也可以动态配置。
5. 构建并启动 SuperSocket 服务
在 WPF 的异步方法中:
csharp
async Task StartTcp(string protocolType, string terminator)
{
host = SuperSocketHostBuilder
.Create<StringPackageInfo, CommandLinePipelineFilter>() // 占位 Filter
.ConfigureServices((ctx, services) =>
{
services.AddSingleton<ConfigurableFilterFactory>(_ =>
new ConfigurableFilterFactory(protocolType, terminator));
services.AddSingleton<IPipelineFilterFactory<StringPackageInfo>>(sp =>
sp.GetRequiredService<ConfigurableFilterFactory>());
})
.UsePipelineFilterFactory<ConfigurableFilterFactory>() // 泛型传类型
.UsePackageHandler(async (session, package) =>
{
// 简单示例:返回命令结果
await session.SendAsync(Encoding.UTF8.GetBytes($"Received: {package.Body}\r\n"));
})
.ConfigureSuperSocket(options =>
{
options.Name = "MyTcpServer";
options.Listeners = new List<ListenOptions>
{
new ListenOptions { Ip = "0.0.0.0", Port = 8051 }
};
})
.ConfigureLogging((ctx, logging) =>
{
logging.AddConsole();
logging.AddDebug();
})
.Build();
await host.StartAsync(); // WPF UI 线程安全
}
注意点
- 不要用
RunAsync()
,因为它会阻塞线程。WPF 里用StartAsync()
更安全。 - 停止服务:
csharp
await host.StopAsync();
host = null;
6. 超级实用技巧
-
动态协议切换
- 使用
IPipelineFilterFactory
+ DI,可以在外部配置文件里控制协议类型和结束符,无需修改代码。
- 使用
-
日志和调试
- SuperSocket 内置对
Microsoft.Extensions.Logging
支持,方便在开发和生产环境打印调试信息。
- SuperSocket 内置对
-
大包支持
MaxPackageLength
可以设置为 1GB 或更多,支持大数据传输。
-
命令行或 WPF 混合使用
- SuperSocket 既可以在 Console,也可以在 WPF 窗口中运行。
- 异步模式保证 UI 不被阻塞。
7. 配置文件示例(appsettings.json)
json
{
"Protocol": {
"Type": "Terminator",
"Terminator": "$$"
},
"Server": {
"Port": 8051
}
}
- WPF 启动时读取配置文件即可动态选择协议和结束符。
- 支持快速切换到 FixedHeader 协议。
8. 总结
通过本文方法,你可以:
- 灵活支持多种协议类型(Terminator / FixedHeader)。
- 动态配置结束符,无需修改代码。
- 集成到 WPF 或其他 .NET 应用中。
- 利用 SuperSocket DI 与 Logging 做可扩展、可维护的高性能服务端。
SuperSocket 结合 DI + Factory 模式,是实现可扩展 TCP 服务的最佳实践。