网络库提供了数据包编码器架构,用于实现各种数据封包,灵活解决粘包问题。常见SRMP标准封包、固定长度封包、字符分隔封包等,具体应用有MQTT和RocketMQ协议实现。

Nuget包:NewLife.Core

源码地址:https://github.com/NewLifeX/X/blob/master/NewLife.Core/Model/IHandler.cs

Get Started

新建NET7控制台项目,并从Nuget引入 NewLife.Core,写入以下代码:

using NewLife;
using NewLife.Data;
using NewLife.Log;
using NewLife.Net;
using NewLife.Net.Handlers;
using NewLife.Serialization;

XTrace.UseConsole();

var server = new NetServer
{
    Port = 12345,
    Log = XTrace.Log,
    SessionLog = XTrace.Log,
    //SocketLog = XTrace.Log,
    //LogSend = true,
    //LogReceive = true
};
server.Add<LengthFieldCodec>();
server.Received += (s, e) =>
{
    XTrace.WriteLine("原始:{0}", e.Packet.ToHex(32, "-"));

    if (e.Message is Packet pk)
        XTrace.WriteLine("收到:{0}", pk.ToStr());
};
server.Start();

var uri = new NetUri("tcp://127.0.0.1:12345");
var client = uri.CreateRemote();
client.Log = XTrace.Log;
client.Add<LengthFieldCodec>();
client.Open();

var str = "Stone";
var pk = new Packet(str.GetBytes());

client.SendMessage(pk);

var info = new LoginInfo { UserName = "Stone", Password = "NewLife" };
pk = new Packet(info.ToJson().GetBytes());
client.SendMessage(pk);

Console.ReadLine();

class LoginInfo
{
    public String UserName { get; set; }
    public String Password { get; set; }
}

执行结果

这是一个添加了长度封包编码器 LengthFieldCodec 的网络通信例程,客户端发送了两次消息,SendMessge内部经过编码器对消息进行编码,服务端的编码器对消息进行解码。

    • 发送字符串Stone,本应只有5个字节,服务端收到7个字节,开头多了 05-00,即后续数据长度5字节。
    • 发送一段Json字符串,服务端收到数据头部多了 29-00(0x0029的小端字节序),即后续数据长度41字节

由此可见,数据封包编码器,本质上就是在消息体外部再包装一层,增加一些标识以便于接收方判断消息体如何拆分,处理粘包问题。

长度封包编码器 LengthFieldCodec

常见协议使用指定字段表示负载数据长度。如上述例程。

可用属性项如下:

    • Offset。长度所在位置,一般为0
    • Size。长度占据字节数,1/2/4个字节,0表示压缩编码整数,默认2
    • Expire。过期时间,超过该时间后按废弃数据处理,默认500ms

长度是2字节时,可传输最大字节数限制在65535,因此有些协议使用4字节。硬件通信时更多使用1个字节。

标准封包编码器StandardCodec

功能

代码

using NewLife;
using NewLife.Data;
using NewLife.Log;
using NewLife.Net;
using NewLife.Net.Handlers;
using NewLife.Serialization;

XTrace.UseConsole();

var server = new NetServer
{
    Port = 12345,
    Log = XTrace.Log,
    SessionLog = XTrace.Log,
};
server.Add<StandardCodec>();
server.Received += (s, e) =>
{
    XTrace.WriteLine("原始:{0}", e.Packet.ToHex(32, "-"));

    if (e.Message is Packet pk)
        XTrace.WriteLine("收到:{0}", pk.ToStr());
};
server.Start();

var uri = new NetUri("tcp://127.0.0.1:12345");
var client = uri.CreateRemote();
client.Log = XTrace.Log;
client.Add<StandardCodec>();
client.Open();

var str = "Stone";
var pk = new Packet(str.GetBytes());

client.SendMessage(pk);

var info = new LoginInfo { UserName = "Stone", Password = "NewLife" };
pk = new Packet(info.ToJson().GetBytes());
client.SendMessage(pk);

Console.ReadLine();

class LoginInfo
{
    public String UserName { get; set; }
    public String Password { get; set; }
}

执行结果

自定义编码器

代码