Netty的应用实践总结(断线重连,心跳,粘包拆包)

前言

 

最近学习了Netty后,总想让Netty发挥点作用。于是自己用了两个场景,一个是web聊天室,一个是Netty同步缓存数据。这篇文章对使用Netty的核心关键点做一次总结;

websocket实现

  • 核心就是在pipeline中加入netty提供的WebSocketServerProtocolHandler和Http解码器HttpServerCodec
// websocket协议本身是基于http协议的,所以这边也要使用http解编码器
ch.pipeline().addLast(new HttpServerCodec());
// 以块的方式来写的处理器
ch.pipeline().addLast(new ChunkedWriteHandler());
ch.pipeline().addLast(new HttpObjectAggregator(8192));
ch.pipeline().addLast(new WebSocketServerProtocolHandler("/ws", "WebSocket", true, 65536 * 10));
ch.pipeline().addLast(new MyWebSocketHandler());
  • 前端通过IP+Netty端口+/ws连接,得到socket对象即可
 socket = new WebSocket("ws://192.168.2.12:8091/ws");
 socket.onmessage = function (event) {
 	...
 }

断线重连的实现

  • 主要注意客户端这边编码,将连接服务端方法包装成一个单独的方法,便于重连时调用
public void doConnect(Bootstrap bootstrap, EventLoopGroup eventLoopGroup) {

  // 关键代码
  ChannelFuture channelFuture = bootstrap.connect(serverHost, serverPort).addListener((ChannelFuture futureListener) -> {
      EventLoop eventLoop = futureListener.channel().eventLoop();
      if (futureListener.isSuccess()) {
          // 可以在这发送登录成功的消息给server
          //futureListener.channel().writeAndFlush(Unpooled.copiedBuffer("msg!!!", CharsetUtil.UTF_8));
      } else {
          System.out.println("客户端已启动,与服务端建立连接失败,10s之后尝试重连!");
          eventLoop.schedule(() -> doConnect(new Bootstrap(), eventLoop), 10, TimeUnit.SECONDS);
      }
  }).sync();

}
  • handler中重写channelInactive方法逻辑
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        log.info("与服务器连接断开,尝试重新连接...");
        if (nettyClient == null) {
            nettyClient = SpringContextUtil.getBean(NettyClient.class);
        }
        nettyClient.retryConnectFlag = true;
        final EventLoop eventLoop = ctx.channel().eventLoop();
        // 立即重连
        nettyClient.doConnect(new Bootstrap(), eventLoop);
        super.channelInactive(ctx);
    }

心跳实现

  • 重写userEventTriggered方法,捕获到空闲状态事件,发送一条心跳数据(其实也就是一条普通消息)
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        // 如果事件类型是【空闲状态事件(超时)】,则发送心跳
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) evt;
            log.info(ctx.channel().remoteAddress() + ",超时类型:" + event.state());
            // 发送心跳
            sendPingMsg(ctx);
        } else {
            super.userEventTriggered(ctx, evt);
        }
    }

    /**
     * 发送ping消息,就是心跳消息
     *
     * @param context
     */
    protected void sendPingMsg(ChannelHandlerContext context) {
        NettyMsgBody body = new NettyMsgBody(NettyMessageType.CLIENT_HEARTBEAT, 1);
        context.writeAndFlush(body);
        heartbeatCount++;
        log.info("客户端发送心跳给服务端:" + context.channel().remoteAddress() + ", 心跳次数: " + heartbeatCount);
    }

解决粘包拆包问题

  • 当我测试了下缓存数据过大时的数据同步时,发现数据被截断传输,相当于拆包了。而循环发送较小数据时,则将数据拼到一块一起发送了。
  • 这将导致我客户端或服务端收到数据后,不好处理。
  • 这里就介绍一种简单的解决方案,用固定分隔符号来区别一整条消息内容;
  • 服务端,设置一个使用换行符的编码器进行编码,长度尽量大一点
ch.pipeline().addLast(new LineBasedFrameDecoder(10240));
  • 发送数据时,在每个消息体后面追加一个换行符即可
ByteBuf buf = Unpooled.copiedBuffer(msgBody.toJSONString() + System.getProperty("line.separator"), CharsetUtil.UTF_8);
channelHandlerContext.writeAndFlush(buf);
  • 更多类型的解决方案可以查看LineBasedFrameDecoder上层父类及接口

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值