• 参考资料说明

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协议的更多相关文章

  1. SuperSocket与Netty之实现protobuf协议,包括服务端和客户端

    今天准备给大家介绍一个c#服务器框架(SuperSocket)和一个c#客户端框架(SuperSocket.ClientEngine).这两个框架的作者是园区里面的江大渔. 首先感谢他的无私开源贡献. ...

  2. SuperSocket入门(五)-常用协议实现模版及FixedSizeReceiveFilter示例

             Socket里面的协议解析是Socket通讯程序设计中最复杂的地方,如果你的应用层协议设计或实现不佳,Socket通讯中常见的粘包,分包就难以避免.SuperSocket内置了命令行 ...

  3. SuperSocket内置的命令行协议

    内置的命令行协议(接受自定义,分隔符为“:”,“,”): 命令行协议定义了每个请求必须以回车换行结尾 "\r\n". 由于 SuperSocket 中内置的命令行协议用空格来分割请 ...

  4. Google的Protobuf协议分析

    protobuf和thrift类似,也是一个序列化的协议实现,简称PB(下文出现的PB代表protobuf). Github:https://github.com/google/protobuf 上图 ...

  5. netty 对 protobuf 协议的解码与包装探究(2)

    netty 默认支持protobuf 的封装与解码,如果通信双方都使用netty则没有什么障碍,但如果客户端是其它语言(C#)则需要自己仿写与netty一致的方式(解码+封装),提前是必须很了解net ...

  6. 使用Go语言+Protobuf协议完成一个多人聊天室

    软件环境:Goland Github地址 一.目的 之前用纯逻辑垒完了一个可登入登出的在线多人聊天室(代码仓库地址),这次学习了Protobuf协议,于是想试着更新下聊天室的版本. 主要目的是为了掌握 ...

  7. 自定义兼容多种Protobuf协议的编解码器

    <从零开始搭建游戏服务器>自定义兼容多种Protobuf协议的编解码器 直接在protobuf序列化数据的前面,加上一个自定义的协议头,协议头里包含序列数据的长度和对应的数据类型,在数据解 ...

  8. Protobuf 协议语言指南

    l  定义一个消息(message)类型 l  标量值类型 l  Optional 的字段及默认值 l  枚举 l  使用其他消息类型 l  嵌套类型 l  更新一个消息类型 l  扩展 l  包(p ...

  9. Protobuf协议的Java应用例子

    Protobuf协议,全称:Protocol Buffer 它跟JSON,XML一样,是一个规定好的数据传播格式.不过,它的序列化和反序列化的效率太变态了…… 来看看几张图你就知道它有多变态. Pro ...

随机推荐

  1. [P5665][CSP2019D2T2] 划分

    先说说部分分做法吧 1.\(n \leq 10\) 指数级瞎草都可以2333 2.\(n \leq 50\) 好像并没有什么做法-也许给剪枝的人部分分吧 3.\(n \leq 400\) 这个复杂度是 ...

  2. PAT (Basic Level) Practice (中文)1047 编程团体赛 (20 分)

    编程团体赛的规则为:每个参赛队由若干队员组成:所有队员独立比赛:参赛队的成绩为所有队员的成绩和:成绩最高的队获胜. 现给定所有队员的比赛成绩,请你编写程序找出冠军队. 输入格式: 输入第一行给出一个正 ...

  3. PAT - A1073

    1073 Scientific Notation (20point(s)) 小数点移动的边界条件 小数点左移 超过数据左边界 小数点右移 未超过数据右边界 超过数据右边界 +0.1E+2 --> ...

  4. Android 判断APP前台,后台运行

    public void checkAppState() { ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVI ...

  5. mysql 行锁 表锁

    MySQL数据库 - 引擎: - innodb - 支持事务 - 锁 - 行锁 - 表锁 - 示例: - 终端: begin; select xx from xx for update; commit ...

  6. 假期学习【六】Python网络爬虫2020.2.4

    今天通过Python网络爬虫视频复习了一下以前初学的网络爬虫,了解了网络爬虫的相关规范. 案例:京东的Robots协议 https://www.jd.com/robots.txt 说明可以爬虫的范围 ...

  7. Hadoop的安装(2)---Hadoop配置

    一:安装JDK hadoop2.x最低jdk版本要求是:jdk1.7(不过推荐用最新的:jdk1.8,因为jdk是兼容旧版本的,而且我们使用的其他软件可能要求的jdk版本较高) 下载地址:https: ...

  8. usim卡介绍

  9. 树莓派中安装ubuntu及相关设置

    一.下载并烧录系统 首先准备好我们要烧录的ubuntu_meta系统,可以在树莓派官网中下载https://www.raspberrypi.org/downloads/ 这里我们选择 Raspberr ...

  10. 连接数据库报错:ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/lib/mysql/mysql.sock' (2)

    报错: ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/lib/mysql/mysql.soc ...