我是靠谱客的博主 结实草莓,最近开发中收集的这篇文章主要介绍Websocket客户端断网,服务端心跳超时检测并断开连接,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

起因

项目上想通过websocket做好友的上线下线通知功能,用户上线时客户端websocket连接服务端,调用服务端onOpen()方法,服务端通知所有好友当前用户上线;用户退出时客户端websocket断开连接,调用服务端onClose()方法,服务端通知所有好友当前用户离线。

问题

这样做会有一个很大的问题,如果客户端是关闭流量、关闭WIFI断网而不是正常退出,服务端就不会收到客户端的断连请求,因此服务端并不会触发onClose()方法,导致其好友无法收到当前用户的离线信息。

解决方案

经过网上大量资料的查找,发现绝大多数网友都采用心跳机制解决。

客户端定时向服务端发送空消息(ping),服务端启动心跳检测,超过一定时间范围没有新的消息进来就默认为客户端已断线,服务端主动执行close()方法断开连接

Netty是一个非常强大的NIO通讯框架,支持多种通讯协议,并且在网络通讯领域有许多成熟的解决方案。

因此决定采用Netty来实现websocket服务器心跳监听机制,不必再去重复造轮子

贴代码

netty启动类

public class WSServer {

    public void start(int port){
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap
                    .group(bossGroup, workerGroup)               //设置主从线程组
                    .channel(NioServerSocketChannel.class)      //设置nio双向通道
                    .childHandler(new WSServerInitializer());   //子处理器,用于处理workerGroup

            //用于启动server,同时启动方式为同步
            ChannelFuture channelFuture = bootstrap.bind(port).sync();
            //监听关闭的channel,设置同步方式
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) {
        new WSServer().start(8888);
    }
}

ChannelInitializer类,负责添加各种handler,注意添加顺序

public class WSServerInitializer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        //获取流水线
        ChannelPipeline pipeline = ch.pipeline();

        //websocket基于http协议,所以要有http编解码器
        pipeline.addLast(new HttpServerCodec());

        //对写大数据的支持
        pipeline.addLast(new ChunkedWriteHandler());

        //对httpMessage进行整合,聚合成FullHttpRequest或FullHttpResponse
        pipeline.addLast(new HttpObjectAggregator(1024 * 64));

        //心跳检测,读超时时间设置为30s,0表示不监控
        ch.pipeline().addLast(new IdleStateHandler(30, 0, 0, TimeUnit.SECONDS));
        //心跳超时处理事件
        ch.pipeline().addLast(new ServerHeartBeat());

        //自定义handler
        pipeline.addLast(new WSHandler());

        //websocket指定给客户端连接访问的路由:/ws
        pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));

    }
}

心跳超时处理器

public class ServerHeartBeat extends ChannelInboundHandlerAdapter {
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {//超时事件
            System.out.println("心跳检测超时");
            IdleStateEvent idleEvent = (IdleStateEvent) evt;
            if (idleEvent.state() == IdleState.READER_IDLE) {//读
                ctx.channel().close();  //关闭通道连接
            } else if (idleEvent.state() == IdleState.WRITER_IDLE) {//写

            } else if (idleEvent.state() == IdleState.ALL_IDLE) {//全部

            }
        }
        super.userEventTriggered(ctx, evt);
    }
}

好友上下线通知处理器

public class WSHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {

    //key为channelId value为uid 存储在Map中
    private static ConcurrentHashMap<String,String> uidMap = new ConcurrentHashMap<>();
	
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (msg instanceof FullHttpRequest){
            //拦截请求地址,获取地址上的uid值,并存入Map集合中
            FullHttpRequest fullHttpRequest = (FullHttpRequest) msg;
            String uri = fullHttpRequest.uri();
            String uid = uri.substring(uri.lastIndexOf("/")+1, uri.length());
            uidMap.put(ctx.channel().id().toString(),uid);//把uid放入集合中存储

            System.err.println(uid+"上线****************************");

            //TODO 通知所有好友,已上线
            

            // uri改为 /ws
            fullHttpRequest.setUri("/ws");
        }
        super.channelRead(ctx,msg);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        //获取Uid
        String uid = uidMap.get(ctx.channel().id().toString());

        System.err.println(uid+"断线****************************");
		uidMap.remove(ctx.channel().id().toString());
        //TODO 通知所有好友,已下线
        
    }

    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame) throws Exception {
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
    }
}

JS代码

    window.CHAT = {
        socket: null,
        init: function () {
            if (window.WebSocket) {
                CHAT.socket = new WebSocket("ws://127.0.0.1:8888/ws/00001");
                CHAT.socket.onopen = function () {
                    console.log("连接建立成功...");
                },
                    CHAT.socket.onclose = function () {
                        console.log("连接关闭...");
                    },
                    CHAT.socket.onerror = function () {
                        console.log("发生错误...");
                    },
                    CHAT.socket.onmessage = function (e) {
                        console.log("接收到消息" + e.data);
                    }
            } else {
                alert("浏览器不支持websocket协议...");
            }
        },
        sendMessage: function (value) {
            CHAT.socket.send(value);
        },
        heartBeat: function () {
            setInterval(this.sendMessage(""),5000);
        }
    };
    CHAT.init();
    CHAT.heartBeat();

websocket连接地址example:

ws://127.0.0.1:8888/ws/{uid}

最后

以上就是结实草莓为你收集整理的Websocket客户端断网,服务端心跳超时检测并断开连接的全部内容,希望文章能够帮你解决Websocket客户端断网,服务端心跳超时检测并断开连接所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(53)

评论列表共有 0 条评论

立即
投稿
返回
顶部