zmqclient简单demo(java)
- 1.pom.xml
<!-- ZeroMq-->
<dependency>
<groupId>org.zeromq</groupId>
<artifactId>jeromq</artifactId>
<version>0.5.1</version>
</dependency>
- 2.client代码
package com.skj.zmq.client;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.zeromq.SocketType;
import org.zeromq.ZContext;
import org.zeromq.ZMQ;
@Order(-1)
@Slf4j
@Component
public class ZmqClient3Test implements ApplicationRunner {
private int clientPort =5550;
private String clientIp ="127.0.0.1";
private String protocol = "tcp://";
/**
* 封装接收信息
* @param args
* @throws Exception
*/
@Override
public void run(ApplicationArguments args) throws Exception {
subscriber();
}
public void subscriber(){
try (ZContext context = new ZContext()) {
ZMQ.Socket subscriber = context.createSocket(SocketType.SUB); //subscribe类型
subscriber.connect(protocol + clientIp + ":" + clientPort);
subscriber.subscribe(""); //订阅内容
System.out.println("--->:"+protocol + clientIp + ":" + clientPort);
while (true) {
//接收到服务端的推送内容
String text = subscriber.recvStr();
System.out.println(text);
}
}catch (Exception e){
e.printStackTrace();
}
}
}
- 正常启动后会得到类似如下的控制台信息,具体看自己订阅的消息
--->:tcp://127.0.0.1:5550
N
{"addTime":null,"calendarModel":null,"createTime":1594366463302,"important":1,"newsId":"25547_100","newsTitle":"6月底,中国的社会融资总额超过了271.8万亿元","newsType":0,"noticeStatus":1,"releasedDate":1594366461000,"serverPushTime":1594366385185,"simDate":1594366385374,"simId":null,"simWebsite":null,"simWebsiteName":null,"slowSecond":null,"smallImg":null,"status":1,"tags":"1,31"}
V
- 上面的代码能够进行简单的接收但是不稳定,我说的是不稳定是指很有可能出现刚开始是能正常接收的,但是在运行一段时候后会突然出现接收不到的情况。让人头秃的是,出现问题的时候你看不到任何错误日志,让你无从下手,这个时候你需要想办法拿到一些信息去分析问题,查找资料后我发现zmq有提供事件监控,我们可以开启一个新的线程去监控zmq的状态变化。
zmqclient加上事件监控
package com.skj.zmq.client;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.zeromq.SocketType;
import org.zeromq.ZContext;
import org.zeromq.ZMQ;
@Order(-1)
@Slf4j
@Component
public class ZmqClient3Test implements ApplicationRunner {
private int clientPort =5550;
private String clientIp ="127.0.0.1";
private String protocol = "tcp://";
/**
* 封装接收信息
* @param args
* @throws Exception
*/
@Override
public void run(ApplicationArguments args) throws Exception {
subscriber();
}
public void subscriber(){
try (ZContext context = new ZContext()) {
ZMQ.Socket subscriber = context.createSocket(SocketType.SUB); //subscribe类型
subscriber.monitor("inproc://monitor.sub", ZMQ.EVENT_ALL);//这段代码会创建一个pair类型的socket,专门来接收当前socket发生的事件
final ZMQ.Socket monitor = context.createSocket(SocketType.PAIR);
monitor.connect("inproc://monitor.sub");//监听上面的subscriber
subscriber.connect(protocol + clientIp + ":" + clientPort);
subscriber.subscribe(""); //订阅内容
System.out.println("--->:"+protocol + clientIp + ":" + clientPort);
new Thread(new Runnable() {
@Override
public void run() {
while (true){
ZMQ.Event event = ZMQ.Event.recv(monitor);
log.info("zmq连接状态监控:{}",event.getEvent()+" "+event.getAddress()+" "+event.getValue());
}
}
}).start();
while (true) {
//接收到服务端的推送内容
String text = subscriber.recvStr();
System.out.println(text);
}
}catch (Exception e){
e.printStackTrace();
}
}
}
- 启动后我们可以从日志钟看到当前客户端sokcet的状态
--->:tcp://127.0.0.1:5550
2020-07-10 15:44:37.065 INFO 3608 --- [ Thread-8] com.skj.zmq.client.ZmqClient3Test : zmq连接状态监控:2 tcp://127.0.0.1:5550 -1
2020-07-10 15:44:37.070 INFO 3608 --- [ Thread-8] com.skj.zmq.client.ZmqClient3Test : zmq连接状态监控:1 tcp://127.0.0.1:5550 null
2020-07-10 15:44:37.078 INFO 3608 --- [ Thread-8] com.skj.zmq.client.ZmqClient3Test : zmq连接状态监控:32768 tcp://127.0.0.1:5550 3
- 数字对应的具体状态可以查看源码
public static final int ZMQ_EVENT_CONNECTED = 1;
public static final int ZMQ_EVENT_CONNECT_DELAYED = 1 << 1;
public static final int ZMQ_EVENT_CONNECT_RETRIED = 1 << 2;
public static final int ZMQ_EVENT_LISTENING = 1 << 3;
public static final int ZMQ_EVENT_BIND_FAILED = 1 << 4;
public static final int ZMQ_EVENT_ACCEPTED = 1 << 5;
public static final int ZMQ_EVENT_ACCEPT_FAILED = 1 << 6;
public static final int ZMQ_EVENT_CLOSED = 1 << 7;
public static final int ZMQ_EVENT_CLOSE_FAILED = 1 << 8;
public static final int ZMQ_EVENT_DISCONNECTED = 1 << 9;
public static final int ZMQ_EVENT_MONITOR_STOPPED = 1 << 10;
public static final int ZMQ_EVENT_HANDSHAKE_PROTOCOL = 1 << 15;
public static final int ZMQ_EVENT_ALL = 0xffff;
- 所以上面启动时对应的状态分别时
- 2:ZMQ_EVENT_CONNECT_DELAYED ,
- 1:ZMQ_EVENT_CONNECTED
- 32768:ZMQ_EVENT_HANDSHAKE_PROTOCOL
- 好了现在我们总算能看到点信息了,是不是觉得自己要发现问题关键了呢,嗯。。。其实并没有,我试过了收不到信息的时候,控制台并没用发现socket状态的变化,你是不是觉得socket没问题怎么突然接收不到了,其实只是看着没问题,接收不到消息,状态却没有变化,这才是出大问题了。
- 要知道zmq本身是有实现重试的,但是zmq的重试是需要zmq客户端自己知道自己和服务断的通讯断开了,现在状态没变化,显然客户端还不知道自己和服务断的通讯断了,所以就没能按我们的期望zmq自己重连接,这种往往是断开的时候客户端和服务端没按正常流程走完握手协议,单想靠zmq自己去重连已经不可能。
- 这时候我们可能需要利用zmqclient之外的检测,嘻嘻木有错,是时候让TCPKeepAlive登场了,TCPKeepAlive用于检测通讯时和对接方是否断开了,它是由系统去发送探测包,如果发送的探测包在探测一定的时间和数目后未能正常的获得响应,就可以知道客户端和服务端的通讯其实已经端了,这时他会将通讯的标志复位,也就是将客户端和服务端之间的通讯状态设置为断开,这时zmq就知道自己已经和服务端断开通讯了,也就能促发重试了。以下是我在开发过程钟捕获到的zmq重连时的状态变化。
- 512:ZMQ_EVENT_DISCONNECTED
- 4:ZMQ_EVENT_CONNECT_RETRIED
- 2:ZMQ_EVENT_CONNECT_DELAYED ,
- 1:ZMQ_EVENT_CONNECTED
- 32768:ZMQ_EVENT_HANDSHAKE_PROTOCOL
- 可以看到状态是先关闭通讯,然后再重链接的,这之后重新连接上我确实又恢复了
- 下面介绍下如何设置 TCPKeepAlive
使用TCPKeepAlive
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.zeromq.SocketType;
import org.zeromq.ZContext;
import org.zeromq.ZMQ;
@Order(-1)
@Slf4j
@Component
public class ZmqClient3Test implements ApplicationRunner {
private int clientPort =5550;
private String clientIp ="127.0.0.1";
private String protocol = "tcp://";
/**
* 封装接收信息
* @param args
* @throws Exception
*/
@Override
public void run(ApplicationArguments args) throws Exception {
subscriber();
}
public void subscriber(){
try (ZContext context = new ZContext()) {
ZMQ.Socket subscriber = context.createSocket(SocketType.SUB); //subscribe类型
subscriber.setTCPKeepAlive(1);//开启保持TCP连接的活动性
subscriber.setTCPKeepAliveIdle(120L);//两分钟内检测连接是否可用
subscriber.setTCPKeepAliveInterval(10L);//探测包未响应10s后再次发送
subscriber.setTCPKeepAliveCount(3);//探测三次 三次未响应标记为不可用
subscriber.monitor("inproc://monitor.sub", ZMQ.EVENT_ALL);//这段代码会创建一个pair类型的socket,专门来接收当前socket发生的事件
final ZMQ.Socket monitor = context.createSocket(SocketType.PAIR);
monitor.connect("inproc://monitor.sub");//监听上面的subscriber
subscriber.connect(protocol + clientIp + ":" + clientPort);
subscriber.subscribe(""); //订阅内容
System.out.println("--->:"+protocol + clientIp + ":" + clientPort);
new Thread(new Runnable() {
@Override
public void run() {
while (true){
ZMQ.Event event = ZMQ.Event.recv(monitor);
log.info("zmq连接状态监控:{}",event.getEvent()+" "+event.getAddress()+" "+event.getValue());
}
}
}).start();
while (true) {
//接收到服务端的推送内容
String text = subscriber.recvStr();
System.out.println(text);
}
}catch (Exception e){
e.printStackTrace();
}
}
}
-
TCPKeepAlive系统本身是关闭的,所以要用的话需要自己设置成打开,开了以后会有一定的流量损耗,所以要自己考虑清楚哈。
-
TCPKeepAliveIdle :设置多长时间未接收到消息,就发送探测包,一般系统默认好像是2小时,具体可以自己查下
-
TCPKeepAliveInterval :用于设置探测包发送的时间间隔 ,若发送的探测包没有响应,间隔多少时间再次发送,默认好像是1s
-
TCPKeepAliveCount:发送重试的次数,默认好像是5次
-
注意这里的时间设置单位不是固定的, 和项目部署的环境有关 linux是秒, win好像是毫秒 具体自己查下哈
-
当然你也可以不用TCPKeepAlive,可以自己写个定时去监控消息的接收,如果超过时间就自己手动重连。
定时重连
思路
1.记录最近一次的接收时间recvTime
2.在定时中判断当前距离上次接收时间多久了,如果超过自己的预订时间就自己重连
package com.skj.zmq.client;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.zeromq.SocketType;
import org.zeromq.ZContext;
import org.zeromq.ZMQ;
@Order(-1)
@Slf4j
@Component
public class ZmqClient3Test implements ApplicationRunner {
private int clientPort =5550;
private String clientIp ="127.0.0.1";
private String protocol = "tcp://";
private ZMQ.Socket subscriber;
private ZContext context;
public static volatile long recvTime = 0L;
/**
* 封装接收信息
* @param args
* @throws Exception
*/
@Override
public void run(ApplicationArguments args) throws Exception {
subscriber();
}
public void subscriber(){
try {
context = new ZContext()
subscriber = context.createSocket(SocketType.SUB); //subscribe类型
subscriber.setTCPKeepAlive(1);//开启保持TCP连接的活动性
subscriber.setTCPKeepAliveIdle(120L);//两分钟内检测连接是否可用 linux单位 毫秒 win单位 秒
subscriber.setTCPKeepAliveInterval(10L);//探测包未响应10s后再次发送
subscriber.setTCPKeepAliveCount(3);//探测三次 三次未响应标记为不可用
subscriber.monitor("inproc://monitor.sub", ZMQ.EVENT_ALL);//这段代码会创建一个pair类型的socket,专门来接收当前socket发生的事件
final ZMQ.Socket monitor = context.createSocket(SocketType.PAIR);
monitor.connect("inproc://monitor.sub");//监听上面的subscriber
subscriber.connect(protocol + clientIp + ":" + clientPort);
subscriber.subscribe(""); //订阅内容
System.out.println("--->:"+protocol + clientIp + ":" + clientPort);
new Thread(new Runnable() {
@Override
public void run() {
while (true){
ZMQ.Event event = ZMQ.Event.recv(monitor);
log.info("zmq连接状态监控:{}",event.getEvent()+" "+event.getAddress()+" "+event.getValue());
}
}
}).start();
while (true) {
//接收到服务端的推送内容
String text = subscriber.recvStr();
recvTime = System.currentTimeMillis();
System.out.println(text);
}
}catch (Exception e){
e.printStackTrace();
}
}
public void retryConnect(){
subscriber.disconnect(protocol + clientIp + ":" + clientPort);
initSocket();
}
private void initSocket() {
subscriber.setTCPKeepAlive(1);//开启保持TCP连接的活动性
subscriber.setTCPKeepAliveIdle(120L);//两分钟内检测连接是否可用
subscriber.setTCPKeepAliveInterval(10L);//探测包未响应10s后再次发送
subscriber.setTCPKeepAliveCount(3);//探测三次 三次未响应标记为不可用
subscriber.connect(protocol + clientIp + ":" + clientPort);
subscriber.subscribe(""); //订阅内容
}
@PreDestroy
public void shutdown(){
log.info("关闭socket");
subscriber.close();
context.close();
}
}
定时代码
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
/**
* Zmq发布服务
* @author Wayne.M implements ApplicationRunner
*/
@Order(value = 4)
@Slf4j
@Component
@EnableScheduling
public class CheckHeartServer{
@Autowired
private ZmqClient zmqClient;
/**
* 推送定时心跳保持连接
* @throws InterruptedException
*/
@Scheduled(cron = "0/30 * * * * *")
public void run() throws InterruptedException {
try {
if(System.currentTimeMillis()-ZmqClient.oldTime>40000 && System.currentTimeMillis()-ZmqClient.oldTime<150000){
zmqClient.retryConnect();//长时间没接收到重链接
}
} catch (Exception e) {
e.printStackTrace();
}
}
}