SuperSocket与SuperSocket.ClientEngine实现Protobuf协议
- 参考资料说明
SuperSocket文档 http://docs.supersocket.net/
Protobuf语言参考 https://developers.google.com/protocol-buffers/docs/proto
单消息多类型解决方案 https://developers.google.com/protocol-buffers/docs/techniques#
主要资料(非常感谢) http://www.cnblogs.com/caipeiyu/p/5559112.html
使用的ProtocolBuffers http://code.google.com/p/protobuf-csharp-port
关于MsgPack的协议 https://my.oschina.net/caipeiyu/blog/512437
- Proto
message CallMessage
{
optional string content = ;
} message BackMessage
{
optional string content = ;
} message PersonMessage
{
required int32 id = ;
required string name = ;
enum Sex
{
Male = ;
Female = ;
}
required Sex sex = [default = Male];
required uint32 age = ;
required string phone = ;
} import "BackMessage.proto";
import "CallMessage.proto";
import "PersonMessage.proto"; message DefeatMessage
{
enum Type
{
CallMessage = ;
BackMessage = ;
PersonMessage = ;
}
required Type type = ;
optional CallMessage callMessage = ;
optional BackMessage backMessage = ;
optional PersonMessage personMessage = ;
}
- 生成C#代码
protoc --descriptor_set_out=DefeatMessage.protobin --proto_path=./ --include_imports DefeatMessage.proto
protogen DefeatMessage.protobin
- Server
namespace SuperSocketProtoServer.Protocol
{
public class ProtobufRequestInfo: IRequestInfo
{
public string Key { get; }
public DefeatMessage.Types.Type Type { get; } public DefeatMessage Body { get; } public ProtobufRequestInfo(DefeatMessage.Types.Type type, DefeatMessage body)
{
Type = type;
Key = type.ToString();
Body = body;
}
}
} namespace SuperSocketProtoServer.Protocol
{
public class ProtobufReceiveFilter: IReceiveFilter<ProtobufRequestInfo>, IOffsetAdapter, IReceiveFilterInitializer
{
private int _origOffset;
private int _offsetDelta;
private int _leftBufferSize; public void Initialize(IAppServer appServer, IAppSession session)
{
_origOffset = session.SocketSession.OrigReceiveOffset;
} public int OffsetDelta
{
get { return _offsetDelta; }
} /// <summary>
/// 数据包解析
/// </summary>
/// <param name="readBuffer">接收缓冲区</param>
/// <param name="offset">接收到的数据在缓冲区的起始位置</param>
/// <param name="length">本轮接收到的数据长度</param>
/// <param name="toBeCopied">为接收到的数据重新创建一个备份而不是直接使用接收缓冲区</param>
/// <param name="rest">接收缓冲区未被解析的数据</param>
/// <returns></returns>
public ProtobufRequestInfo Filter(byte[] readBuffer, int offset, int length, bool toBeCopied, out int rest)
{
rest = ;
// 重新计算缓冲区的起始位置,前一次解析还有剩下没有解析的数据就需要把起始位置移到之前最后要解析的那个位置
var readOffset = offset - _offsetDelta;
// 由google.protocolbuffers提供
CodedInputStream cis = CodedInputStream.CreateInstance(readBuffer, readOffset, length);
// 计算数据包的长度,不包含Length本身
int varint32 = (int) cis.ReadRawVarint32();
if (varint32 <= ) return null; // 计算协议里面Length占用字节
int headLen = (int) (cis.Position - readOffset);
// 本轮解析完缓冲后剩余没有解析的数据大小
rest = length - varint32 - headLen + _leftBufferSize; // 缓冲里面的数据足够本轮解析
if (rest >= )
{
byte[] body = cis.ReadRawBytes(varint32);
DefeatMessage message = DefeatMessage.ParseFrom(body);
ProtobufRequestInfo requestInfo = new ProtobufRequestInfo(message.Type, message);
_offsetDelta = ;
_leftBufferSize = ; return requestInfo;
}
// 缓冲里面的数据不够本次解析[tcp分包传送]
else
{
_leftBufferSize += length;
_offsetDelta = _leftBufferSize;
rest = ; var expectedOffset = offset + length;
var newOffset = _origOffset + _offsetDelta;
if (newOffset < expectedOffset) Buffer.BlockCopy(readBuffer, offset - _leftBufferSize + length, readBuffer, _origOffset, _leftBufferSize);
} return null;
} public void Reset()
{
_offsetDelta = ;
_leftBufferSize = ;
} public int LeftBufferSize
{
get { return _leftBufferSize; }
} public IReceiveFilter<ProtobufRequestInfo> NextReceiveFilter { get; } public FilterState State { get; }
}
} namespace SuperSocketProtoServer.Protocol
{
public class ProtobufAppSession:AppSession<ProtobufAppSession, ProtobufRequestInfo>
{
public ProtobufAppSession() { }
}
} namespace SuperSocketProtoServer.Protocol
{
public class ProtobufAppServer: AppServer<ProtobufAppSession, ProtobufRequestInfo>
{
public ProtobufAppServer()
: base(new DefaultReceiveFilterFactory<ProtobufReceiveFilter, ProtobufRequestInfo>())
{ }
}
}
- Server Command
namespace SuperSocketProtoServer.Protocol.Command
{
public class BackMessage : CommandBase<ProtobufAppSession, ProtobufRequestInfo>
{
public override void ExecuteCommand(ProtobufAppSession session, ProtobufRequestInfo requestInfo)
{
Console.WriteLine("BackMessage:{0}", requestInfo.Body.BackMessage.Content);
}
}
} namespace SuperSocketProtoServer.Protocol.Command
{
public class CallMessage : CommandBase<ProtobufAppSession, ProtobufRequestInfo>
{
public override void ExecuteCommand(ProtobufAppSession session, ProtobufRequestInfo requestInfo)
{
Console.WriteLine("CallMessage:{0}", requestInfo.Body.CallMessage.Content);
var backMessage = global::BackMessage.CreateBuilder().SetContent("Hello I am from C# server by SuperSocket")
.Build();
var message = DefeatMessage.CreateBuilder().SetType(DefeatMessage.Types.Type.BackMessage)
.SetBackMessage(backMessage).Build();
using (var stream = new MemoryStream())
{
CodedOutputStream cos = CodedOutputStream.CreateInstance(stream);
cos.WriteMessageNoTag(message);
cos.Flush();
byte[] data = stream.ToArray();
session.Send(new ArraySegment<byte>(data));
}
}
}
} namespace SuperSocketProtoServer.Protocol.Command
{
public class PersonMessage:CommandBase<ProtobufAppSession, ProtobufRequestInfo>
{
public override void ExecuteCommand(ProtobufAppSession session, ProtobufRequestInfo requestInfo)
{
Console.WriteLine("Recv Person Message From Client.");
Console.WriteLine("person's id = {0}, person's name = {1}, person's sex = {2}, person's phone = {3}",
requestInfo.Body.PersonMessage.Id,
requestInfo.Body.PersonMessage.Name,
requestInfo.Body.PersonMessage.Sex,
requestInfo.Body.PersonMessage.Phone);
}
}
}
- Client
namespace SuperSocketProtoClient.Protocol
{
public class ProtobufPackageInfo: IPackageInfo
{
public string Key { get; }
public DefeatMessage.Types.Type Type { get; }
public DefeatMessage Body { get; } public ProtobufPackageInfo(DefeatMessage.Types.Type type, DefeatMessage body)
{
Type = type;
Key = type.ToString();
Body = body;
}
}
} namespace SuperSocketProtoClient.Protocol
{
public class ProtobufReceiveFilter: IReceiveFilter<ProtobufPackageInfo>
{
/// <summary>
/// 数据解析
/// BufferList已经实现了分包处理
/// </summary>
/// <param name="data">数据缓冲区</param>
/// <param name="rest">缓冲区剩余数据</param>
public ProtobufPackageInfo Filter(BufferList data, out int rest)
{
rest = ;
var buffStream = new BufferStream();
buffStream.Initialize(data); var stream = CodedInputStream.CreateInstance(buffStream);
var varint32 = (int)stream.ReadRawVarint32();
if (varint32 <= ) return default(ProtobufPackageInfo); var total = data.Total;
var packageLen = varint32 + (int)stream.Position; if (total >= packageLen)
{
rest = total - packageLen;
var body = stream.ReadRawBytes(varint32);
var message = DefeatMessage.ParseFrom(body);
var requestInfo = new ProtobufPackageInfo(message.Type, message);
return requestInfo;
} return default(ProtobufPackageInfo);
} public void Reset()
{
NextReceiveFilter = null;
State = FilterState.Normal;
} public IReceiveFilter<ProtobufPackageInfo> NextReceiveFilter { get; protected set; }
public FilterState State { get; protected set; }
}
}
- Server Entrance
namespace SuperSocketProtoServer
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Press any key to start the server!"); Console.ReadKey();
Console.WriteLine(); var appServer = new ProtobufAppServer();
//appServer.NewRequestReceived += AppServerOnNewRequestReceived; try
{
//Setup the appServer
if (!appServer.Setup()) //Setup with listening port
{
Console.WriteLine("Failed to setup!");
Console.ReadKey();
return;
}
}catch(Exception e) { Console.WriteLine(e);} Console.WriteLine(); //Try to start the appServer
if (!appServer.Start())
{
Console.WriteLine("Failed to start!");
Console.ReadKey();
return;
} Console.WriteLine("The server started successfully, press key 'q' to stop it!"); while (Console.ReadKey().KeyChar != 'q')
{
Console.WriteLine();
continue;
} //Stop the appServer
appServer.Stop(); Console.WriteLine("The server was stopped!");
Console.ReadKey();
} private static void AppServerOnNewRequestReceived(ProtobufAppSession session, ProtobufRequestInfo requestinfo)
{
switch (requestinfo.Type)
{
case DefeatMessage.Types.Type.BackMessage:
Console.WriteLine("BackMessage:{0}", requestinfo.Body.BackMessage.Content);
break;
case DefeatMessage.Types.Type.CallMessage:
Console.WriteLine("CallMessage:{0}", requestinfo.Body.CallMessage.Content);
var backMessage = BackMessage.CreateBuilder().SetContent("Hello I am from C# server by SuperSocket")
.Build();
var message = DefeatMessage.CreateBuilder().SetType(DefeatMessage.Types.Type.BackMessage)
.SetBackMessage(backMessage).Build();
using (var stream = new MemoryStream())
{
CodedOutputStream cos = CodedOutputStream.CreateInstance(stream);
cos.WriteMessageNoTag(message);
cos.Flush();
byte[] data = stream.ToArray();
session.Send(new ArraySegment<byte>(data));
}
break;
}
}
}
}
- Client Entrance
namespace SuperSocketProtoClient
{
class Program
{
static void Main(string[] args)
{
EasyClient client = new EasyClient();
client.Initialize(new ProtobufReceiveFilter(), packageInfo =>
{
switch (packageInfo.Type)
{
case DefeatMessage.Types.Type.BackMessage:
Console.WriteLine("BackMessage:{0}", packageInfo.Body.BackMessage.Content);
break;
case DefeatMessage.Types.Type.CallMessage:
Console.WriteLine("CallMessage:{0}", packageInfo.Body.CallMessage.Content);
break; }
});
var flag = client.ConnectAsync(new DnsEndPoint("127.0.0.1", ));
if (flag.Result)
{
var callMessage = CallMessage.CreateBuilder()
.SetContent("Hello I am form C# client by SuperSocket ClientEngine").Build();
var message = DefeatMessage.CreateBuilder()
.SetType(DefeatMessage.Types.Type.CallMessage)
.SetCallMessage(callMessage).Build(); using (var stream = new MemoryStream())
{ CodedOutputStream os = CodedOutputStream.CreateInstance(stream); os.WriteMessageNoTag(message); os.Flush(); byte[] data = stream.ToArray();
client.Send(new ArraySegment<byte>(data)); } Thread.Sleep(); // 发送PersonMessage
var personMessage = PersonMessage.CreateBuilder()
.SetId().SetAge().SetSex(PersonMessage.Types.Sex.Male).SetName("zstudio").SetPhone("").Build();
message = DefeatMessage.CreateBuilder().SetType(DefeatMessage.Types.Type.PersonMessage)
.SetPersonMessage(personMessage).Build();
using (var stream = new MemoryStream())
{
CodedOutputStream cos = CodedOutputStream.CreateInstance(stream);
cos.WriteMessageNoTag(message);
cos.Flush();
byte[] data = stream.ToArray();
client.Send(new ArraySegment<byte>(data));
} }
Console.ReadKey();
}
}
}
- 执行结果
- 工程、资源、资料打包:http://pan.baidu.com/s/1qXB9aEg
- 更多项目相关细节和详情参考博客:http://www.cnblogs.com/caipeiyu/p/5559112.html, 在此也表示对博主由衷的感谢!!!
SuperSocket与SuperSocket.ClientEngine实现Protobuf协议的更多相关文章
- SuperSocket与Netty之实现protobuf协议,包括服务端和客户端
今天准备给大家介绍一个c#服务器框架(SuperSocket)和一个c#客户端框架(SuperSocket.ClientEngine).这两个框架的作者是园区里面的江大渔. 首先感谢他的无私开源贡献. ...
- SuperSocket入门(五)-常用协议实现模版及FixedSizeReceiveFilter示例
Socket里面的协议解析是Socket通讯程序设计中最复杂的地方,如果你的应用层协议设计或实现不佳,Socket通讯中常见的粘包,分包就难以避免.SuperSocket内置了命令行 ...
- SuperSocket内置的命令行协议
内置的命令行协议(接受自定义,分隔符为“:”,“,”): 命令行协议定义了每个请求必须以回车换行结尾 "\r\n". 由于 SuperSocket 中内置的命令行协议用空格来分割请 ...
- Google的Protobuf协议分析
protobuf和thrift类似,也是一个序列化的协议实现,简称PB(下文出现的PB代表protobuf). Github:https://github.com/google/protobuf 上图 ...
- netty 对 protobuf 协议的解码与包装探究(2)
netty 默认支持protobuf 的封装与解码,如果通信双方都使用netty则没有什么障碍,但如果客户端是其它语言(C#)则需要自己仿写与netty一致的方式(解码+封装),提前是必须很了解net ...
- 使用Go语言+Protobuf协议完成一个多人聊天室
软件环境:Goland Github地址 一.目的 之前用纯逻辑垒完了一个可登入登出的在线多人聊天室(代码仓库地址),这次学习了Protobuf协议,于是想试着更新下聊天室的版本. 主要目的是为了掌握 ...
- 自定义兼容多种Protobuf协议的编解码器
<从零开始搭建游戏服务器>自定义兼容多种Protobuf协议的编解码器 直接在protobuf序列化数据的前面,加上一个自定义的协议头,协议头里包含序列数据的长度和对应的数据类型,在数据解 ...
- Protobuf 协议语言指南
l 定义一个消息(message)类型 l 标量值类型 l Optional 的字段及默认值 l 枚举 l 使用其他消息类型 l 嵌套类型 l 更新一个消息类型 l 扩展 l 包(p ...
- Protobuf协议的Java应用例子
Protobuf协议,全称:Protocol Buffer 它跟JSON,XML一样,是一个规定好的数据传播格式.不过,它的序列化和反序列化的效率太变态了…… 来看看几张图你就知道它有多变态. Pro ...
随机推荐
- mysql查询中字符串转换成数字
在操作mysql时,经常需要将字符转换成数字,这一步虽然简单,但不常用的话也很容易忘记,现将在网上找到的方法记录如下: 1.将字符的数字转成数字,比如'0'转成0可以直接用加法来实现例如:将pony表 ...
- 第一个12PB的项目--2017年
今年第一个上PB级的项目终于落地.准备了几年,期盼了几年的项目,终于有了市场.内心还是有成就感的. 可是年终沟通,就像是一瓢冷水,从头往下倒.拔凉拔凉的.什么是心寒.什么是希望.想了很多. 我还本想着 ...
- 【Debian学徒记事】记一次解决Debian开机1min30s
记一次解决Debian开机1min30s 打开我亲爱的Debian 欸,好像有点慢 [* * * * ] A start job is running for....(*/1min30s) [TIME ...
- linux commands - 一次性解压多个tar.gz文件
tar -zxvf list所有tar.gz文件,然后利用xargs将其作为参数传给tar命令.-n 1表示每次传一个参数. xargs: https://www.cnblogs.com/wangqi ...
- 代数式转换为c语言表达式(很简单)
- 3、const与constexpr
初遇到constexpr真的是有点懵比,看了很多博客也没看懂,不知道是我太笨,还是别人写的太深奥?总之经过一番折腾算是入门了.一下是我个人总结,有不对的地方望指出. 一.学习const与constex ...
- CodeForces 1144C
链接 https://vjudge.net/problem/CodeForces-1144C #include<bits/stdc++.h> using namespace std; in ...
- Codeforces Round #617 (Div. 3) 题解
又是隔了一年才来补题的我 A.B水题就不用说了 C - Yet Another Walking Robot C题我居然卡了一会,最后决定用map水,结果出来看了看题解,居然真的是map...没想到会出 ...
- Nginx配置服务器宕机策略
Nginx解决服务器宕机问题,Nginx配置服务器宕机策略,如果服务器宕机,会找下一台机器进行访问 配置nginx.cfg配置文件,在映射拦截地址中加入代理地址响应方案 location ...
- 5G将至,4G降速:是谣言还是真相?
畅用移动智能终端设备,早已成为大众日常生活中的一部分.卫报专栏作家伯克曼提到,"据估计,70%的人会抱着手机或iPad刷资讯入睡."当移动智能终端变得如此重要时,与之息息相关的网络 ...