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}

  • 8
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
### 回答1: WebSocket 是一种基于 TCP 协议的网络协议,它支持双向通信,可以在客户端服务端之间实时地传输数据。在 WebSocket 中,客户端服务端之间的通信是基于消息的,这意味着它可以传输各种类型的数据,包括文本、二进制数据等。 要在 WebSocket 客户端服务端之间进行图片交互,可以采用以下步骤: 1. 在服务端上启动 WebSocket 服务器,并监听客户端连接的请求。 2. 在客户端上创建 WebSocket 对象,并连接服务端WebSocket 服务器。 3. 在客户端上选择要传输的图片,并将其转换为 Base64 编码格式。 4. 将 Base64 编码格式的图片数据封装成 WebSocket 消息,并发送给服务端。 5. 在服务端上接收到客户端发送的 WebSocket 消息后,解析消息中的图片数据,并将其保存到文件系统中。 6. 在服务端上将保存在文件系统中的图片数据转换为 Base64 编码格式,并封装成 WebSocket 消息,发送给客户端。 7. 在客户端上接收到服务端发送的 WebSocket 消息后,解析消息中的图片数据,并将其显示在客户端上。 需要注意的是,在传输大量的图片数据时,WebSocket 可能会产生较大的带宽消耗,因此建议在传输之前对图片进行压缩处理,以减小数据量。同时,为了保证传输的安全性,可以使用 SSL/TLS 协议来保护 WebSocket 连接。 ### 回答2: WebSocket是一种基于TCP协议的全双工通信协议,可以实现客户端服务端之间的实时数据传输。在图片交互方面,WebSocket客户端服务端可以通过以下步骤进行图片交互: 1. WebSocket客户端服务端建立连接WebSocket客户端通过HTTP请求与服务端建立WebSocket连接服务端会返回一个握手响应,在响应头中包含必要的信息验证该连接。 2. 客户端发送请求:客户端在建立好连接后,可以通过WebSocket发送请求给服务端。在图片交互中,可以使用消息的方式向服务端传递图片相关的请求,如请求某个图片资源。 3. 服务端处理请求:服务端接收到客户端的请求后,对其进行解析和处理。根据请求中的参数,服务端可以读取指定的图片资源。 4. 服务端响应请求:服务端会将图片资源以二进制数据的形式返回给客户端。可以将图片数据作为WebSocket消息的一部分,或者通过WebSocket连接发送图片路径等信息,使客户端能够通过该路径获取图片资源。 5. 客户端处理响应:客户端接收到服务端返回的数据后,解析数据并进行处理。可以将二进制数据转换为图片展示在界面上,或者通过提取图片路径等信息,通过网络请求获取图片资源后展示。 6. 数据传输完毕,关闭连接:当图片交互完成后,可以选择手动关闭WebSocket连接,释放资源。 WebSocket客户端服务端的图片交互通过实时双向通信,可以实现快速传输和实时展示图片,提供了更好的用户体验和交互性。 ### 回答3: WebSocket客户端服务端可以通过传输图片来实现交互。在WebSocket的通信过程中,客户端可以发送图片数据给服务端服务端也可以将图片数据发送给客户端。 首先,客户端可以通过JavaScript的WebSocket API连接服务端。然后,客户端可以选择一个图片文件并将其转换为二进制数据。接着,客户端可以将二进制数据发送给服务端,使用WebSocket的send()方法将数据传输给服务端服务端在接收到图片数据后,可以将其保存到服务器的文件系统中,或者进行其他处理。服务端可以使用任何服务器端的编程语言来处理WebSocket消息,并根据需要进行解码和处理接收到的图片数据。 对于服务端发送图片给客户端的交互,服务端可以将图片数据转换为二进制数据,并使用WebSocket的send()方法将其发送给客户端客户端收到图片数据后,可以将其转换为图片格式,并在页面上显示出来。 需要注意的是,在传输大量图片数据时,可能需要对数据进行压缩和数据包分割,以避免网络传输过程中的性能问题和数据丢失或损坏。 综上所述,WebSocket客户端服务端可以通过传输图片数据来实现交互。客户端可以将图片数据发送给服务端,而服务端也可以将图片数据发送给客户端。这种交互可以通过WebSocket的API和相关的编程语言和技术来实现。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值