• 参考资料说明

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. 数据结构(集合)学习之List

    集合 框架关系图: Collection接口下面有三个子接口:List.Set.Queue.此篇是关于List<E>的简单学习总结. 补充:HashTable父类是Dictionary,不 ...

  2. TC SRM556 OldBridges

    题意 有一个包含\(n\)个点的图,点的编号分别为\(0\)到\(n-1\).有若干双向边连接两个点,有些边可以经过无限次,有些边最多只能经过(双向)两次.Alice计划从\(a1\)到\(a2\)进 ...

  3. Vue中echarts的使用

    1.安装 npm install echarts --save 2. 导入并挂载 <template>   <!-- 1. 为ECharts准备一个具备大小(宽高)的Dom --&g ...

  4. 【笔记】机器学习 - 李宏毅 - 3 - Bias & Variance

    A more complex model does not always lead to better performance on testing data. Because error due t ...

  5. C#调用Crypto++库AES ECB CBC加解密

    本文章使用上一篇<C#调用C++类库例子>的项目代码作为Demo.本文中,C#将调用C++的Crypto++库,实现AES的ECB和CBC加解密. 一.下载Crypto 1.进入Crypt ...

  6. Ansible Tower 3.5.1 平台部署和破解

    原创 Ansible Tower 3.5.1 平台部署和破解 Ansible Tower (以前叫’AWX’)是能够帮助任何IT团队更容易使用Ansible的解决方案.该方案基于web. Tower允 ...

  7. GitKraken 快速配置 SSH Key

    快速使用 GitKraken 配置SSH keys git是现在最流行的版本管理工具,应用范围非常广泛,推荐一款git的可视化工具,这款 工具特别方便 它的官方如下https://www.gitkra ...

  8. mysql 零基础 开始过程

    2016-11-07 算是差不多是两个月的学习,我也马上要结课了.今天才勉强把mysql装上,之前还有因为用户权限的问题,以及用户为创建的问题.应该早点就把该准备的开发平台准备好,而不是在最后快考试了 ...

  9. 预防XSs和sql注入常见分析

    SQL注入简介SQL 注入漏洞(SQL Injection)是 Web 开发中最常见的一种安全漏洞.可以用它来从数据库获取敏感信息,或者利用数据库的特性执行添加用户,导出文件等一系列恶意操作,甚至有可 ...

  10. Codeforces Round #624 (Div. 3) F. Moving Points 题解

    第一次写博客 ,请多指教! 翻了翻前面的题解发现都是用树状数组来做,这里更新一个 线段树+离散化的做法: 其实这道题是没有必要用线段树的,树状数组就能够解决.但是个人感觉把线段树用熟了会比树状数组更有 ...