zmqclient的稳定接收(意外断开处理)

java开发 专栏收录该内容
13 篇文章 0 订阅

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();
        }

    }

}

  • 0
    点赞
  • 5
    评论
  • 1
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值