最近看了一下ZeroMQ,把一些只是记录下来,以供后面复习。
当然最好的学习文档还是官方的手册zguide
1,请求应答模式
请求应答模式是一种常用的通信模式,每次请求和应答都是有顺序要求的,每个请求都要有一个应答。
如果一次发送两条请求,那么就会返回-1.
代码如下:
服务端.cs
static void Main(string[] args)
{
if(args == null || args.Length < 1)
{
args = new string[] { "World" };
}
string name = args[0];
// Create
using (var context = new ZContext())
using (var responder = new ZSocket(context, ZSocketType.REP))
{
// Bind
responder.Bind("tcp://*:5555");
while(true)
{
// Receive
using (ZFrame request = responder.ReceiveFrame())
{
Console.WriteLine("Received {0}",request.ReadString());
// Do some work
Thread.Sleep(1);
// Send
responder.Send(new ZFrame(name));
}
}
}
}
客户端.cs
static void Main(string[] args)
{
if(args == null || args.Length < 1)
{
args = new string[] { "tcp://127.0.0.1:5555" };
}
string endpoint = args[0];
// Create
using (var context = new ZContext())
using (var requester = new ZSocket(context, ZSocketType.REQ))
{
// Connect
requester.Connect(endpoint);
for(int n = 0; n < 10; ++n)
{
string requestText = "Hello";
Console.Write("Sending {0}", requestText);
// Send
requester.Send(new ZFrame(requestText));
// Receive
using (ZFrame reply = requester.ReceiveFrame())
{
Console.WriteLine(" Receive: {0} {1} !", requestText, reply.ReadString());
}
}
}
Console.ReadKey();
}
几点说明
- 代码比较简单,只是说清楚请求应答模式,客户端你可以增长到成百上千个也能稳定的执行。
ZeroMQ是一个工具,通过它你可以来打造一艘航空母舰。 - 只是这样不是一个可靠的连接,如果中间重启服务端,那客户端并不能正确的接收数据。
- 对于传输的数据,ZeroMQ出了长度,其他的并不知道。这就需要你自己定义协议来保证数据的解析。
- 对于字符串,ZeroMQ不会带有字符串结束标志。
2,发布订阅模式
上图可以很明白的理解发布订阅模式,这种模式是异步的。这种情况下,不知道接收端何时开始接收到消息的,即便先启动订阅端,因为订阅端连接到发布端也是需要时间的(这段时间,发布端已经发送了消息)。
发布端.cs
static void Main(string[] args)
{
// Create
using (var context = new ZContext())
using (var pubScoket = new ZSocket(context, ZSocketType.PUB))
{
// 绑定当个地址
string address = "tcp://*:5566";
Console.WriteLine("Publisher.Bind'ing on {0}", address);
pubScoket.Bind(address);
// 绑定到多个地址
// ……
while (true)
{
string data = "发送内容";
// 发送内容
using (var updateFrame = new ZFrame(data))
{
pubScoket.Send(updateFrame);
}
}
}
}
订阅端.cs
static void Main(string[] args)
{
// 初始化Socket
using (var context = new ZContext())
using (var subScoket = new ZSocket(context, ZSocketType.SUB))
{
// 连接到发布端
string address = "tcp://*:5556";
subScoket.Connect(address);
*// 发送订阅请求
string subCode = "subCode";
subScoket.Subscribe(subCode);
// 接收发布端发来的消息
for (int i = 0; i < 20; i++)
{
using (var replyFrame = subScoket.ReceiveFrame())
{
string reply = replyFrame.ReadString();
// 处理消息
}
}
}
}
几点说明
- 订阅端可以同时连接多个服务端,交叉接收服务端的消息(公平排队)
- 如果没有订阅者,则优雅的丢掉消息。
- 从ZeroMQ v3.x开始,使用连接协议(tcp:// 或者 ipc:// )则在发布端进行过滤;使用epgm:// 协议则在订阅端进行过滤。在ZeroMQ v2.x 中,所有过滤都在订阅端进行。
3,并行管道模式
从图中可以看出任务是并行在执行,并且工作节点是可以动态增加的。任务发布节点和统计节点相对固定的。
任务发布.cs
static void Main(string[] args)
{
// Task Ventilator
// Sends batch of tasks to workers via that socket
using(var context = new ZContext())
using (var sender = new ZSocket(context, ZSocketType.PUSH))
using (var sink = new ZSocket(context, ZSocketType.PUSH))
{
sender.Bind("tcp://*:5557");
sink.Connect("tcp://127.0.0.1:5558");
*//wait
Console.WriteLine("Press Enter when the workers are ready……");
Console.ReadKey(true);
Console.WriteLine("Sending tasks to workers……");
// The first message is "0" and signals start of batch
sink.Send(new byte[] { 0x00 }, 0, 1);
// Initialize random number generator
var rnd = new Random();
// Send 100 tasks
int i = 0;
long total_msec = 0; //Total expected cost in msecs
for (; i < 100; i++)
{
// Random workload from 1 to 100msecs
int workload = rnd.Next(100) + 1;
total_msec += workload;
byte[] action = BitConverter.GetBytes(workload);
Console.WriteLine("{0}", workload);
sender.Send(action, 0, action.Length);
}
Console.WriteLine("Total expected cost: {0} ms", total_msec);
}
}
工作节点.cs
static void Main(string[] args)
{
//
// Task worker
// Connects PULL socket to tcp://127.0.0.1:5557
// Collects workloads from ventilator via that socket
// Connects PUSH socket to tcp://127.0.0.1:5558
// Sends results to sink via that socket
//
using (var context = new ZContext())
using (var pull = new ZSocket(context, ZSocketType.PULL))
using (var sink = new ZSocket(context, ZSocketType.PUSH))
{
pull.Connect("tcp://127.0.0.1:5557");
sink.Connect("tcp://127.0.0.1:5558");
// Process tasks forever
while (true)
{
// Receive one Task
var replyBytes = new byte[4];
pull.ReceiveBytes(replyBytes, 0, replyBytes.Length);
// Show progress
int workload = BitConverter.ToInt32(replyBytes, 0);
Console.WriteLine("{0}.", workload);
// Do the work
Thread.Sleep(workload);
// Send results to sink
sink.Send(new byte[0], 0, 0);
}
}
}
统计节点.cs
static void Main(string[] args)
{
// Task sink
// Binds PULL socket to tcp://127.0.0.1:5558
// Collects results from workers via that socket
//
using (var context = new ZContext())
using (var sink = new ZSocket(context, ZSocketType.PULL))
{
sink.Bind("tcp://127.0.0.1:5558");
// wait for start of batch
sink.ReceiveFrame();
// start our clock now
var stopwatch = new Stopwatch();
stopwatch.Start();
// process 100 confirmations
for (int i = 0; i < 100; i++)
{
sink.ReceiveFrame();
if ((i / 10) * 10 == i)
Console.Write(":");
else
{
Console.Write(".");
}
}
// Calculate and report duration of batch
stopwatch.Stop();
Console.WriteLine("Total elapsed time: {0} ms", stopwatch.ElapsedMilliseconds);
}
}
总结,这个只是入门,并不能应用到实际的项目中,ZeroMQ是一组API,使用它可以构建一个复杂的系统,可扩展性强,在多线程应用中也会更为复杂。
可以发现,ZeroMQ应用程序总是从创建 ZContext 开始,然后创建套接字。ZContext是单个进程中所有套接字的容器,使得消息能够在线程间以最快的速度传输。