概述

  对于稍微熟悉这两个优秀的项目来说,每个内容单独介绍都不为过,本文只是简介并探讨如何将两部分内容合并起来,使其在某些场景下更适合、更高效。

  NetMQ:ZeroMQ的.Net版本,ZeroMQ简单来说就是局域网内的消息中间件(与MSMQ类似),包括了进程间通讯、点对点通讯、订阅模式通讯等等,底层用更“完美”的Socket实现,ZeroMQ实现了多语言、跨平台、高效率等诸多优势。详细介绍请参考ZeroMQ和NetMQ官方文档:http://zguide.zeromq.org/page:all#Chapter-Sockets-and-Patterns,http://netmq.readthedocs.org/en/latest/introduction/

  Protocol Buffer:源自与Google内部的开源项目,作为高效的RPC消息协议,相比较Json、XML协议的消息格式,Protobuf在序列化以及数据大小上都具有十分明显的优势,跨平台,协议可读性也接近于Json等等。这里也推荐一篇文章:http://www.ibm.com/developerworks/cn/linux/l-cn-gpb/

定义Protobuf协议

  Protocol Buffer(简称Protobuf)是以.proto的脚本形式实现的通用语义形式,类似于Json格式:

message  WeatherMessage
{
enum CommandType
{
Debug=;
Weather=;
Other=;
} required CommandType Command= [default=Weather];
optional string Content=; message Loaction
{
required int32 East=;
required int32 North=;
} repeated Loaction UserLocation=;
}

  这里的Message、required(必选属性)、optional(可有可无属性)、repeated(内部嵌套的类型属性)等都是proto的关键字,具体意义以及为关键字的功能大家可以查看官方文档,这里只介绍如何应用,或者Stephen Liu的文章也不错。

  当然,光定义脚本是不能实现应用的,还需要根据特定的编码语言进行描述,这里利用Protobuf-Net来实现.Net平台的协议实现。

  首先,下载软件包:https://code.google.com/p/protobuf-net/(肯能需要FQ)

  然后,解压并将刚才的.proto文件复制到文件夹ProtoGen下。

  最后,启动CMD并cd到ProtoGen文件夹目录下,运行命令:

  protogen -i: PBWeatherMessage.proto -0: PBWeatherMessage.cs -ns:ProtobufNameSpace

(-i指定了输入,-o指定了输出,-ns指定了生成代码的namespace)

  如果,正确的话(当然了,我给出的脚本是不会错的),就会生成一个PBWeatherMessage.cs文件,这样的话就可以将.cs文件加入到项目中当做一个纯粹的类来使用了。

代码中使用,就是类似于二进制序列化一样,只是这回序列化的是Protobuf专用的序列化方式而已。

  序列化:

                        #region Protobuf
var weatherMsg = new NetMQAndProtocolBuffer.PBProtocol.WeatherMessage()
{
Command = PBProtocol.WeatherMessage.CommandType.Weather,
Content = string.Format("{0} {1} {2}", zipcode, temperature, relhumidity),
}; using (var sm = new MemoryStream())
{
ProtoBuf.Serializer.Serialize<NetMQAndProtocolBuffer.PBProtocol.WeatherMessage>(sm, weatherMsg);
publisher.Send(sm.ToArray());
}
#endregion

  反序列化:

                      var weatherMsg = new NetMQAndProtocolBuffer.PBProtocol.WeatherMessage();
var receivedBytes = subscriber.Receive();
using (var sm = new MemoryStream(receivedBytes))
{
weatherMsg = ProtoBuf.Serializer.Deserialize<NetMQAndProtocolBuffer.PBProtocol.WeatherMessage>(sm);
}

  这里就简单介绍完了protobuf协议的使用,下面介绍一下NetMQ+Protobuf的使用。

NetMQ+Protobuf

  接下来我们来改造下NetMQ Sample中的Publisher-Subscriber模式:

  首先下载从GitHub上下载NetMQ Sample: https://github.com/zeromq/netmq

或者下载我的示例代码,其中包含了一个No Protobuf的工程,这个是直接摘自原作者的示例代码。

  服务端Publisher:

            using (var context = NetMQContext.Create())// NetMQ全局维护的Content上下文,建议只有一个并且使用完毕后及时回收。
using (var publisher = context.CreatePublisherSocket())// 从Content上下文中创建CreatePublisherSocket,这里如果用其他四种模式之一需要Create其他类型。
{
publisher.Bind("tcp://127.0.0.1:5556");// Bind到指定的IP及端口。 var rng = new Random(); while (!stopRequested)
{
int zipcode = rng.Next(, );// 这里模拟一个随机命令编号(如果非10001,客户端直接丢弃此Publisher发布的消息,实现消息过滤)
int temperature = rng.Next(-, );
int relhumidity = rng.Next(, ); publisher.Send(string.Format("{0} {1} {2}", zipcode, temperature, relhumidity));// 直接Send,干净整洁。
}
}

  客户端Subscriber:

 using (var context = NetMQContext.Create())// 创建全局NetMQ句柄,建议唯一,使用完毕及时回收。
using (var subscriber = context.CreateSubscriberSocket())// 创建Publisher-Subscriber模式的客户端监听。
{
subscriber.Connect("tcp://127.0.0.1:5556");// 连接到指定Socket
subscriber.Subscribe(zipToSubscribeTo.ToString(CultureInfo.InvariantCulture));// 这里创建消息内容的过滤,如果不包含“zipToSubscribeTo”值则不接收消息。 for (int i = ; i < iterations; i++)
{
string results = subscriber.ReceiveString(); // 如果消息以“zipToSubscribeTo”开头,则会返回整条信息。
Console.Write("."); // "zip temp relh" ... "10001 84 23" -> ["10001", "84", "23"]
string[] split = results.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);// 按照固定模式解码。 int zip = int.Parse(split[]);
if (zip != zipToSubscribeTo)
{
throw new Exception(string.Format("Received message for unexpected zipcode: {0} (expected {1})", zip, zipToSubscribeTo));
} totalTemp += int.Parse(split[]);
totalHumidity += int.Parse(split[]);
}
}

  这就是四种模式之一的发布者模式,使用起来很方便,但是这仅仅传递的是基于String的字符串,还不是一个可以序列化的对象,下一步我们将把消息字符串用Protobuf进行序列化与反序列化,来优化我们的消息格式。

请参考,我的示例代码中的Publisher Pattern工程:

  服务端Publisher:

                using (var context = NetMQContext.Create())
using (var publisher = context.CreatePublisherSocket())
{
publisher.Bind("tcp://127.0.0.1:5556");
var rng = new Random();
while (!stopRequested)
{
int zipcode = rng.Next(,); //Relpace: rng.Next(0, 99999);
int temperature = rng.Next(-, );
int relhumidity = rng.Next(, ); #region Protobuf
var weatherMsg = new NetMQAndProtocolBuffer.PBProtocol.WeatherMessage()
{
Command = PBProtocol.WeatherMessage.CommandType.Weather,
Content = string.Format("{0} {1} {2}", zipcode, temperature, relhumidity),
}; using (var sm = new MemoryStream())
{
ProtoBuf.Serializer.Serialize<NetMQAndProtocolBuffer.PBProtocol.WeatherMessage>(sm, weatherMsg);
publisher.Send(sm.ToArray());
}
#endregion // publisher.Send(string.Format("{0} {1} {2}", zipcode, temperature, relhumidity)); WriteLine(string.Format("Publisher send message: {0} {1} {2}", zipcode, temperature, relhumidity));
System.Threading.Thread.Sleep();
}
}

  客户端Subscriber:

 using (var context = NetMQContext.Create())
using (var subscriber = context.CreateSubscriberSocket())
{
subscriber.Connect("tcp://127.0.0.1:5556");
subscriber.SubscribeToAnyTopic(); // No Command Filter, warn if not set thie method SubscribeToAnyTopic, it will receive nothing. while (true)
{
if (curIndex > iterations) break; var weatherMsg = new NetMQAndProtocolBuffer.PBProtocol.WeatherMessage();
var receivedBytes = subscriber.Receive();
using (var sm = new MemoryStream(receivedBytes))
{
weatherMsg = ProtoBuf.Serializer.Deserialize<NetMQAndProtocolBuffer.PBProtocol.WeatherMessage>(sm);
} // "zip temp relh" ... "10001 84 23" -> ["10001", "84", "23"]
string[] split = weatherMsg.Content.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
int cmdId = int.Parse(split[]);
if (weatherMsg.Command == PBProtocol.WeatherMessage.CommandType.Weather)
{
if (cmdId == zipToSubscribeTo)
{
curIndex++;
WriteLine(string.Format("Subscriber receive message: {0}", weatherMsg.Content));
totalTemp += int.Parse(split[]);
totalHumidity += int.Parse(split[]);
}
}
}

  好了,其实单独来看,这两部分内容并为涉及的很深入,只是作为一个技术实践、技术储备,希望其中有问题或者有更好的应用场景,还请各位留言,不胜感谢!

  我的示例代码下载

冷静下来

这里补充一些不足:

  1. NetMQ中的过滤:默认NetMQ支持过滤,可是当我们摒弃String类型传递而转向Protobuf格式的时候NetMQ通道是无法解析其内容的,所以我们需要先解析内容,然后手写一些过滤代码,放弃了原生的支持。subscriber.SubscribeToAnyTopic()监听所有非过滤模式。
  2. NetMQ消息持久化:基于ZMQ的NetMQ设计理念中均不支持数据持久化(相比MSMQ而言,NetMQ不能接收当客户端不在线情况下的消息),所以如果需要持久化还需要做其他工作或者转战其他MQ家族。

引用

ZMQ:http://zguide.zeromq.org/page:all#Chapter-Sockets-and-Patterns

NetMQ:http://netmq.readthedocs.org/en/latest/introduction/

Protocol Buffer:http://www.ibm.com/developerworks/cn/linux/l-cn-gpb/

Stephen Liu:http://www.cnblogs.com/stephen-liu74/archive/2013/01/02/2841485.html

Protobuf-Net:https://code.google.com/p/protobuf-net/

消息中间件NetMQ结合Protobuf简介的更多相关文章

  1. Protobuf(一)——Protobuf简介

    Protobuf简介 ​ 什么是 Google Protocol Buffer? 假如您在网上搜索,应该会得到类似这样的文字介绍: ​ Google Protocol Buffer( 简称 Proto ...

  2. protobuf简介和使用

    1.Protocol Buffers简介 Protocol Buffers (ProtocolBuffer/ protobuf )是Google公司开发的一种数据描述语言,类似于XML能够将结构化数据 ...

  3. protobuf简介

    #1,简介 把某种数据结构的信息,以某种格式保存起来: 主要用于数据存储,传输协议格式. #2,优点 性能好 反观XML的缺点:解析的开销惊人,不适用于事件性能敏感的场合:为了有较好的可读性,引入一些 ...

  4. Google 的开源技术protobuf 简介与例子

    本文来自CSDN博客:http://blog.csdn.NET/program_think/archive/2009/05/31/4229773.aspx 今天来介绍一下"Protocol  ...

  5. java消息中间件的使用与简介

    一.为什么要使用消息中间件 消息中间件就是可以省去繁琐的步骤,直达目的,怎么讲呢,就是比如你想很多人,知道你的动态,而知道的人可能手机没电,可能手机信号不好,可能手机不在服务区,或者看的人比较忙,看的 ...

  6. [转]Google 的开源技术protobuf 简介与例子

    本文来自CSDN博客:http://blog.csdn.NET/program_think/archive/2009/05/31/4229773.aspx 今天来介绍一下“Protocol Buffe ...

  7. Protobuf 简介及简单应用

    Protobuf 是 protocol buffers 的缩写. 根据官网的说法, protocol buffers 与平台无关, 与语言无关, 实现数据序列化的一种手段. 正如名字一样, proto ...

  8. 【转】深入 ProtoBuf - 简介

    之前在网络通信和通用数据交换等应用场景中经常使用的技术是 JSON 或 XML,而在最近的开发中接触到了 Google 的 ProtoBuf. 在查阅相关资料学习 ProtoBuf 以及研读其源码之后 ...

  9. google protobuf安装与使用

    google protobuf是一个灵活的.高效的用于序列化数据的协议.相比较XML和JSON格式,protobuf更小.更快.更便捷.google protobuf是跨语言的,并且自带了一个编译器( ...

随机推荐

  1. mysql force index() 强制索引的使用

    mysql force index() 强制索引的使用 之前跑了一个SQL,由于其中一个表的数据量比较大,而在条件中有破坏索引或使用了很多其他索引,就会使得sql跑的非常慢... 那我们怎么解决呢? ...

  2. Ubuntu进阶学习,指令迅速查询,Bug迅速查询(Ctrl+F)

    There is some notes while I am learning Ubuntu Operate System! (Ask Ubuntu) 1-- Hard link : ln comma ...

  3. C++中对象初始化

    在C++中对象要在使用前初始化,永远在使用对象之前先将它初始化. 1.对于无任何成员的内置类型,必须手工完成此事. 例如: int x=0; double d; std::cin>>d; ...

  4. NoSql之旅--Cassandra安装篇(一)

    有点迷茫了,头脑中只想起来一句话,"那就去学习吧". 我负责的项目中有一部分用到了Cassandra,当时也看过点,但是并没有太深入的了解,既然"学习劲头"正足 ...

  5. [嵌入式开发板]iTOP-4412以模块的方式编译驱动

    本文转自迅为:http://www.topeetboard.com 大家好,本章节我们将向大家讲解如何在 linux 下实现以模块的方式加载内核驱动.我们以内核里面蜂鸣器的 驱动为例来讲解. 1)首先 ...

  6. DW Basic Knowledge2

    DW的元数据是指除去数据本身之外的所有信息. 围绕DBMS方面的元数据可以描述为表定义,分区设置,索引视图定义,以及DBMS级安全方面的特权 与授权等内容. 在任何场合下,ODS要么是一个处在OLTP ...

  7. NOIP2012普及组 (四年后的)解题报告 -SilverN

    本章施工仍未完成 现在的时间是3.17 0:28,我困得要死 本来今天(昨天?)晚上的计划是把整个四道题的题解写出来,但是到现在还没写完T4的高效算法,简直悲伤. 尝试了用floyd写T4,终于大功告 ...

  8. HDU 4460 Friend Chains --BFS

    题意:问给定的一张图中,相距最远的两个点的距离为多少.解法:跟求树的直径差不多,从1 开始bfs,得到一个最远的点,然后再从该点bfs一遍,得到的最长距离即为答案. 代码: #include < ...

  9. UESTC 923 稳住GCD DP + GCD

    定义:dp[i][j] 表示 在前i个数中,使整个gcd值为j时最少取的数个数. 则有方程: gg = gcd(a[i],j) gg == j : 添加这个数gcd不变,不添加,  dp[i][j] ...

  10. 定制你的Unity编辑器

    Unity的编辑器可以通过写脚本进行界面定制,包括添加功能菜单,今天写游戏Demo用到了记录一下. 为Unity添加子菜单 示例程序 [AddComponentMenu("Defend Ho ...