ZeroMQ是一种基于消息队列的多线程网络库.提供跨越多种传输协议(TCP:传输控制协议,当传输出现错误时,能自动予以纠正;UDP:用户数据包协议,当传输出现错误时会将错误信息丢弃;)的套接字,ZeroMQ是一个可伸缩层,可并行运行,分散在分布式系统间.
zeroMQ在设计上主要采用了以下几个高能性的特征:
1.无锁的队列模型
2.批量处理的算法
3.多核下的线程绑定,无须CPU切换
ZMQ提供了一组单播传输协议(inporc, ipc, tcp),和两个广播协议(epgm, pgm)。
TCP套接字和ZMQ套接字之间在传输数据方面的区别:
1.Request+Reply模式
问答模式:又请求端发起请求,然后等待回应端应答.一个请求必须对于一个回应,从请求端的角度来看是发-收配对,从回应端的角度是收-发对,请求端可以是1~N个,该模型主要用于远程调用及任务分配等.
使用REQ-REP套接字发送和接收消息是需要遵循一定规律的,客户端首先使用zmq_send()发送消息,在使用zmq_recv()接收,如此循环,如果打乱了这个顺序(如连续发送两次)则会报错.类似地,服务端必须先进行接收,后进行发送.
package zmq;
import org.zeromq.ZMQ;
import java.util.Scanner;
/**
* 模仿zeromq,问答模式中客户端
*/
public class ZeroMqREQ {
public static void main(String[] args) {
//创建zmq实例
ZMQ.Context context = ZMQ.context(1);
//zmq的套接字模式
ZMQ.Socket req = context.socket(ZMQ.REQ);
//连接ip和端口
req.connect("tcp://localhost:5555");
//循环发送,接收数据
while(Thread.currentThread().isInterrupted()){
//发送数据给客服端
//创建控制台连接
Scanner sc = new Scanner(System.in);
//获取控制台的内容
String next = sc.next();
//发送
req.send("hello"+next);
//接收
byte[] re = req.recv();
System.out.println(new String(re));
}
//关闭req
req.close();
//关闭context
context.term();
}
}
package zmq;
import org.zeromq.ZMQ;
/**
* 模仿zeromq问答模式中回应端
*/
public class ZeroMqREP {
public static void main(String[] args) throws InterruptedException {
//创建zmq实例
ZMQ.Context context = ZMQ.context(1);
//连接模式
ZMQ.Socket rep = context.socket(ZMQ.REP);
//绑定端口
rep.bind("tcp://localhost:5555");
//开始交互
while(true){
//先接受数据
String s = rep.recvStr();
//睡眠200ms
Thread.sleep(200);
//回答问题
rep.send("hello,zeromq");
}
}
}
2.Pub-Sub模式
发布订阅模式,发布端单向分发数据,且不关心是否把全部信息发送给订阅端,如果发布端开始发布信息时,订阅端尚未连接上来,则这些信息会被直接丢弃.订阅端未连接导致信息丢失的问题,可以通过与请求回应模式组合来解决.订阅端只负责接收,而不能反馈,且在订阅端消费速度慢于发布端的情况下,会在订阅端堆积数据.该模型主要用于数据分发.天气预报,微博明星粉丝可以应用这种经典模型.
在使用SUB套接字时,必须使用subscribe()方法来设置订阅的内容,如果你不设置订阅内容,那将什么消息都收不到,订阅信息可以是任何字符串,可以设置多次.只要消息满足其中一条订阅信息.SUB套接字就会接收到.
PUB-SUB套接字组合是异步的,客户端在一个循环中使用zmq_recv()接收消息.如果向SUB套接字发送消息则会报错;类似地,服务端可以不断使用zmq_send()发送消息,但不能在PUB套接字上使用zmq_recv()
关于PUB-SUB套接字,还有一点需要注意,你无法得知SUB是何时开始接收消息的,就算你先打开了SUB套接字,后打开PUB发送消息,这时SUB还是会丢失一些消息的,因为建立连接是需要一些时间,很少,但并不是零.
关于发布-订阅模式的几点说明:
package zmq;
import org.zeromq.ZMQ;
//模仿zermq中订阅模式sub(客户端--订阅者)
public class Sub {
public static void main(String[] args) {
//zmq实例
ZMQ.Context context = ZMQ.context(1);
//模式
ZMQ.Socket sub = context.socket(ZMQ.SUB);
//连接ip和port
sub.connect("tcp://localhost:9000");
//设置接受形式(不设置,接收不到参数)
sub.subscribe("");
//开始接受数据
while(!Thread.currentThread().isInterrupted()){
byte[] recv = sub.recv();
System.out.println(new String(recv));
}
//释放资源
sub.close();
context.term();
}
}
package zmq;
import org.zeromq.ZMQ;
//模仿zmq中订阅模式中pub(服务端--发布者)
public class Pub {
public static void main(String[] args) throws InterruptedException {
//context
ZMQ.Context context = ZMQ.context(1);
//建立模式
ZMQ.Socket pub = context.socket(ZMQ.PUB);
//绑定ip和port
pub.bind("tcp://localhost:9000");
//发送数据
int i = 0;
while(true){
//发送数据
Thread.sleep(100);
System.out.println(i);
//只能发送,不能接收
pub.send("第"+i+"次都对你说,hello");
i++;
}
}
}
3.Push-Pull模式
Server端作为Push端,而Client端作为Pull端,如果有多个Client端同时连接到Server端,则Server端会在内部做一个负载均衡,采用平均分配的算法,将所有消息均衡发布到Client端上。如果有多个Server端同时连接到Client端,这里Push与Pull之间的对应关系是多个Push角色对应一个Pull角色,在ZeroMQ中,给这种结构取的名叫做公平队列,这里也就是说将Pull角色理解为一个队列,各个Push角色不断的向这个队列中发送数据。与发布订阅模型相比,推拉模型在没有消费者的情况下,发布的消息不会被消耗掉;在消费者能力不够的情况下,能够提供多消费者并行消费解决方案。该模型主要用于多任务并行处理。
package zmq;
import org.zeromq.ZMQ;
//模仿zmq中推拉模式的pull(客户端)
public class ZeroMQPull {
public static void main(String[] args) {
ZMQ.Context context = ZMQ.context(1);
ZMQ.Socket pull = context.socket(ZMQ.PULL);
pull.connect("tcp://localhost:5556");
while(!Thread.currentThread().isInterrupted()){
byte[] recv = pull.recv();
System.out.println(new String(recv));
}
pull.close();
context.term();
}
}
package zmq;
import org.zeromq.ZMQ;
//模仿zmq中推拉模式push(服务端)
public class ZeroMQPUSH {
public static void main(String[] args) throws InterruptedException {
ZMQ.Context context = ZMQ.context(1);
ZMQ.Socket push = context.socket(ZMQ.PUSH);
push.bind("tcp://localhost:5556");
int i = 0;
while (true){
push.send("hello"+i);
System.out.println(i);
i++;
Thread.sleep(200);
}
}
}
代理:
package zmq;
import org.zeromq.ZMQ;
//模仿代理模式
public class ZMQProxy {
public static void main(String[] args) {
ZMQ.Context context = ZMQ.context(1);
//创建pull
ZMQ.Socket pull = context.socket(ZMQ.PULL);
pull.connect("tcp://localhost:5555");
//创建push
ZMQ.Socket push = context.socket(ZMQ.PUSH);
push.bind("tcp://localhost:5556");
//启动代理
ZMQ.proxy(pull,push,null);
//释放资源
pull.close();
push.close();
context.term();
}
}