SuperSocket 动态协议服务端开发全解析

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 中,核心概念有:

  1. TPackageInfo :代表每个完整消息包的对象,框架通过 PipelineFilter 将字节流解码成 TPackageInfo
  2. PipelineFilter:字节流到消息包的解码器,支持多种协议类型(Terminator、FixedHeader、LengthField 等)。
  3. IPipelineFilterFactory:工厂接口,用于创建不同类型的 PipelineFilter。
  4. 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 线程安全
}

注意点

  1. 不要用 RunAsync() ,因为它会阻塞线程。WPF 里用 StartAsync() 更安全。
  2. 停止服务
csharp 复制代码
await host.StopAsync();
host = null;

6. 超级实用技巧

  1. 动态协议切换

    • 使用 IPipelineFilterFactory + DI,可以在外部配置文件里控制协议类型和结束符,无需修改代码。
  2. 日志和调试

    • SuperSocket 内置对 Microsoft.Extensions.Logging 支持,方便在开发和生产环境打印调试信息。
  3. 大包支持

    • MaxPackageLength 可以设置为 1GB 或更多,支持大数据传输。
  4. 命令行或 WPF 混合使用

    • SuperSocket 既可以在 Console,也可以在 WPF 窗口中运行。
    • 异步模式保证 UI 不被阻塞。

7. 配置文件示例(appsettings.json)

json 复制代码
{
  "Protocol": {
    "Type": "Terminator",
    "Terminator": "$$"
  },
  "Server": {
    "Port": 8051
  }
}
  • WPF 启动时读取配置文件即可动态选择协议和结束符。
  • 支持快速切换到 FixedHeader 协议。

8. 总结

通过本文方法,你可以:

  1. 灵活支持多种协议类型(Terminator / FixedHeader)。
  2. 动态配置结束符,无需修改代码。
  3. 集成到 WPF 或其他 .NET 应用中。
  4. 利用 SuperSocket DI 与 Logging 做可扩展、可维护的高性能服务端。

SuperSocket 结合 DI + Factory 模式,是实现可扩展 TCP 服务的最佳实践。

相关推荐
hy.z_7771 天前
【JavaEE】网络编程套接字2: TCP流 套接字编程
网络·java-ee·tcp
DogDaoDao4 天前
深入解析quiche开源项目:从QUIC协议到云原生实践
音视频·实时音视频·tcp·quic·视频直播·流媒体协议·quiche
DebugKitty11 天前
网络编程1-基本概念、函数接口
运维·服务器·网络·网络协议·socket·tcp
敲上瘾16 天前
Linux I/O 多路复用实战:Select/Poll 编程指南
linux·服务器·c语言·c++·select·tcp·poll
Doris_LMS1 个月前
一篇文章入门TCP与UDP(保姆级别)
网络·udp·tcp
cupid85051 个月前
LWIP TCP滑动窗口为TCP ZeroWindow的解决方法
tcp·lwip
是阿建吖!1 个月前
【Linux | 网络】传输层(UDP和TCP)
linux·网络·udp·tcp
学编程的董1 个月前
网络原理 - TCP/IP(一)
网络·网络协议·udp·ip·tcp
是阿建吖!1 个月前
【Linux | 网络】传输层(UDP和TCP) - 两万字详细讲解!!
linux·网络·udp·tcp