Beetle在Tcp通讯中使用Protobuf
Protobuf是google制定的一种对象序列化格式,而在.net下的实现有protobuf-net.而protobuf-net在序列化方面有着出色的性能,效率是.net二进制序列化几倍,而序列化后所占的空间也少于.net二进制序列化;除了以上两个优势外Protobuf有着一个更大的优势就是和其他平台交互的兼容性,在现有大部分流行的语言平台中基本都有Protobuf的实现.因此采用protobuf进行对象序列化是个不错的选择.接下来详细讲解Beetle实现对protobuf-net支持.
定义协议格式
为了保证TCP数据流正确处理,首先要做的事情还是要制定一个处理协议,来保证数据处理的有效性.
数据包同样也是两部分组件,头描述消息总长度,消息体是主内容.由于Protobuf-net序列化的数据交不包括消息类型,所以在协议中必须包括类型名称用于提供给对方实始化对应的类型过行反序列化操作.
实现具体的分析器和消息适配器
协议制定后就可以进行分析器的实现,由于采用头4字节描述大小,所以分析器从HeadSizeOfPackage基础类派生下载重写相关方法即可;完整实现代码如下:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144using
System;
using
System.Collections.Generic;
using
System.Text;
using
Beetle;
namespace
Beetle.Packages
{
public
class
ProtobufPackage : Beetle.HeadSizeOfPackage
{
public
ProtobufPackage()
{
}
public
ProtobufPackage(Beetle.TcpChannel channel)
:
base
(channel)
{
}
protected
override
void
WriteMessageType(IMessage msg, BufferWriter writer)
{
}
public
override
object
WriteCast(
object
message)
{
MessageAdapter ma =
new
MessageAdapter();
ma.Message = message;
return
ma;
}
public
override
object
ReadCast(
object
message)
{
return
((MessageAdapter)message).Message;
}
protected
override
IMessage ReadMessageByType(BufferReader reader,
out
object
typeTag)
{
typeTag =
"ProtobufAdapter"
;
return
new
MessageAdapter();
}
static
Dictionary<
string
, Type> mTypes =
new
Dictionary<
string
, Type>(256);
static
Dictionary<Type,
string
> mNames =
new
Dictionary<Type,
string
>(256);
public
static
void
LoadAssembly()
{
try
{
string
path = AppDomain.CurrentDomain.DynamicDirectory;
LoadAssembly(path);
path = AppDomain.CurrentDomain.BaseDirectory;
LoadAssembly(path);
}
catch
{
}
}
public
static
void
LoadAssembly(
string
path)
{
if
(!
string
.IsNullOrEmpty(path))
{
foreach
(
string
item
in
System.IO.Directory.GetFiles(path,
"*.dll"
))
{
try
{
LoadAssembly(System.Reflection.Assembly.LoadFile(item));
}
catch
{
}
}
}
}
public
static
void
LoadAssembly(System.Reflection.Assembly assembly)
{
foreach
(Type t
in
assembly.GetTypes())
{
ProtoBuf.ProtoContractAttribute[] pc = Smark.Core.Functions.GetTypeAttributes<ProtoBuf.ProtoContractAttribute>(t,
false
);
if
(pc.Length > 0)
{
string
name = t.Name;
if
(!
string
.IsNullOrEmpty(pc[0].Name))
name = pc[0].Name;
mTypes.Add(name, t);
mNames.Add(t, name);
}
}
}
class
MessageAdapter : Beetle.IMessage
{
public
object
Message
{
get
;
set
;
}
public
void
Load(Beetle.BufferReader reader)
{
string
type = reader.ReadString();
Beetle.ByteArraySegment segment = ByteArraySegment;
reader.ReadByteArray(segment);
using
(System.IO.Stream stream =
new
System.IO.MemoryStream(segment.Array, 0, segment.Count))
{
Message = ProtoBuf.Meta.RuntimeTypeModel.Default.Deserialize(stream,
null
, mTypes[type]);
}
}
public
void
Save(Beetle.BufferWriter writer)
{
writer.Write(mNames[Message.GetType()]);
Beetle.ByteArraySegment segment = ByteArraySegment;
using
(System.IO.Stream stream =
new
System.IO.MemoryStream(segment.Array))
{
ProtoBuf.Meta.RuntimeTypeModel.Default.Serialize(stream, Message);
segment.SetInfo(0, (
int
)stream.Position);
}
writer.Write(segment);
}
[ThreadStatic]
private
static
ByteArraySegment mByteArraySegment =
null
;
public
ByteArraySegment ByteArraySegment
{
get
{
if
(mByteArraySegment ==
null
)
mByteArraySegment =
new
ByteArraySegment(TcpUtils.DataPacketMaxLength);
return
mByteArraySegment;
}
}
}
}
}
分析主要重写了ReadCast,WriteCast,而两个Cast方法主要是把消息进行一个适配器包装到一个IMessage对象中提供给组件处理.通过适配器MessageAdapter来实现终于对象的序列化和反序列化操作,并整合的流中.为了方便处理消息对应称名称还添加了分析程序类型来加载对应的类型和名称关系映射.接下来订制一个简单的注册对象
12345678910[ProtoContract]
public
class
Register
{
[ProtoMember(1)]
public
string
UserName {
get
;
set
; }
[ProtoMember(2)]
public
string
EMail {
get
;
set
; }
[ProtoMember(3)]
public
DateTime ResponseTime {
get
;
set
; }
}
实现相应的TCP服务
协议分析器扩展完成后就通过它来实现一个基于protobuf对象处理的TCP交互服务.
12345678910111213141516171819202122232425262728293031323334class
Program : Beetle.ServerBase<Beetle.Packages.ProtobufPackage>
{
static
void
Main(
string
[] args)
{
Beetle.Packages.ProtobufPackage.LoadAssembly(
typeof
(Program).Assembly);
Beetle.TcpUtils.Setup(
"beetle"
);
Program server =
new
Program();
server.Open(9034);
Console.WriteLine(
"server start @9034"
);
Console.Read();
}
protected
override
void
OnConnected(
object
sender, Beetle.ChannelEventArgs e)
{
base
.OnConnected(sender, e);
Console.WriteLine(
"{0} connected"
, e.Channel.EndPoint);
}
protected
override
void
OnDisposed(
object
sender, Beetle.ChannelDisposedEventArgs e)
{
base
.OnDisposed(sender, e);
Console.WriteLine(
"{0} disposed"
, e.Channel.EndPoint);
}
protected
override
void
OnError(
object
sender, Beetle.ChannelErrorEventArgs e)
{
base
.OnError(sender, e);
Console.WriteLine(
"{0} error {1}"
, e.Channel.EndPoint, e.Exception.Message);
}
protected
override
void
OnMessageReceive(Beetle.PacketRecieveMessagerArgs e)
{
Messages.Register register = (Messages.Register)e.Message;
register.ResponseTime = DateTime.Now;
e.Channel.Send(register);
}
}
TCP服务的实现和原来的实现方式一致,只是继承的ServerBase的泛型参是基于protobuf的协议分析器类型.
连接到服务进行对象通讯
同样接入服务端的代码只是改变一下泛型参类型即可
123456789101112131415161718192021222324252627282930313233343536373839404142private
void
cmdConnect_Click(
object
sender, EventArgs e)
{
try
{
channel = Beetle.TcpServer.CreateClient<Beetle.Packages.ProtobufPackage>(txtIPAddress.Text, 9034, OnReceive);
channel.ChannelDisposed += OnDisposed;
channel.ChannelError += OnError;
channel.BeginReceive();
cmdRegister.Enabled =
true
;
cmdConnect.Enabled =
false
;
}
catch
(Exception e_)
{
MessageBox.Show(e_.Message);
}
}
private
void
OnReceive(Beetle.PacketRecieveMessagerArgs e)
{
Register reg = (Register)e.Message;
Invoke(
new
Action<Register>(r =>
{
txtREMail.Text = r.EMail;
txtRName.Text = r.UserName;
txtResponseTime.Text = r.ResponseTime.ToString();
}), reg);
}
private
void
OnDisposed(
object
sender, Beetle.ChannelEventArgs e)
{
Invoke(
new
Action<Beetle.ChannelEventArgs>(s =>
{
txtStatus.Text =
"disconnect!"
;
cmdRegister.Enabled =
false
;
cmdConnect.Enabled =
true
;
}), e);
}
private
void
OnError(
object
sender, Beetle.ChannelErrorEventArgs e)
{
Invoke(
new
Action<Beetle.ChannelErrorEventArgs>(r =>
{
txtStatus.Text = r.Exception.Message;
}), e);
}
把对象发送给服务端
1234Register register =
new
Register();
register.EMail = txtEMail.Text;
register.UserName = txtName.Text;
channel.Send(register);
运行效果
下载代码:Code
总结
由于Protobuf制定的协议是开放的,所以很多平台下都有相关实现包括:c++,java,php等.通过整合protobuf作为协议载体可以方便地和其他平台进行TCP数据交互整合.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?