Netty 运行一段时间后读不到数据,消息积压解决方法,适合初学者。

     Netty 是一个高性能的 NIO 通信框架,提供异步的、事件驱动的网络编程模型。在使用 Netty 进行数据传输时,可能会遇到运行一段时间后读不到数据的情况。这种情况一般是由以下6个原因导致,重点讨论消息积压的情况:
一、原因分析

  1. 网络问题:首先检查网络是否正常连接,如果网络存在问题,可能会导致无法正常接收数据。
  2. 配置问题:检查 Netty 的配置是否正确,例如端口号、协议类型等。如果配置有误,可能会导致无法接收到数据。
  3. 接收方关闭连接:如果接收方主动关闭了连接,发送方将无法继续接收数据。可以检查接收方是否已关闭连接,或者检查接收方是否发送了关闭连接的信号。
  4. 超时设置:检查 Netty 的超时设置,例如读取超时、发送超时等。如果超时设置不合理,可能会导致无法接收到数据。
  5. 数据处理问题:检查数据处理逻辑是否正确,如果数据处理逻辑有误,可能会导致无法正确接收到数据。
  6. 消息积压:如果客户端发送数据较为频繁,且服务端处理数据时间比较长,这时候我们可以使用netty通道或者线程池来解决
二、消息积压代码示例

1.1、服务端启动类

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;

/**
 * 服务端
 * @author syb
 * @date 2023/9/6
 */
public class NettyServerDemo {

    public static void main(String[] args) {

        //配置连接线程组1个线程
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        //配置数据读写线程组,默认线程
        EventLoopGroup workGroup = new NioEventLoopGroup();
        // 引导服务端的启动
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        try {
            serverBootstrap.group(bossGroup, workGroup)
                    // 指定IO模型为NIO,非阻塞模式
                    .channel(NioServerSocketChannel.class)
                    // 设置参数,这里设置的SO_BACKLOG,意思是客户端连接等待队列的长度为128
                    .option(ChannelOption.SO_BACKLOG, 128)
                    // SO_KEEPALIVE: 表示是否开启TCP底层心跳机制,true表示开启
                    //设置活动保持连接状态;
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    // TCP_NODELAY:表示是否开启Nagle算法,true表示关闭,false表示开启
                    //使用TCP_NODELAY可以减少小包数量,可以禁用Nagle算法,坏处就是小包比较多,对网络交通会有负担
                    .childOption(ChannelOption.TCP_NODELAY, true)
                    //初始化连接通道
                    .childHandler(new ServerChannelInitializer());
            //绑定本地端口号启动服务
            ChannelFuture channelFuture = serverBootstrap.bind(8001).sync();
            //等待服务端监听端口关闭
            channelFuture.channel().closeFuture().sync();
            channelFuture.addListener(future -> {
                if (future.isSuccess()) {
                    System.out.println("ChannelFuture:关闭成功");
                } else {
                    System.out.println("ChannelFuture:关闭失败");
                }

            });
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 优雅退出,释放线程池资源
            workGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }

    }

}

 1.2、服务端通道初始化

import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.timeout.IdleStateHandler;
import java.util.concurrent.TimeUnit;
/**
 *服务端连接通道初始化
 *@author syb
 *@date 2023/9/5
 */
public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel channel) {
        // 基于换行符号
        //channel.pipeline().addLast(new LineBasedFrameDecoder(1024));
        // 解码转String,注意调整自己的编码格式GBK、UTF-8
        //channel.pipeline().addLast(new StringDecoder(Charset.forName("UTF-8")));
       //这里设置下读空闲为30秒
        channel.pipeline().addLast(new IdleStateHandler(30,0,0, TimeUnit.SECONDS));
        // 在管道中添加我们自己的接收数据实现方法
        channel.pipeline().addLast(new NettyServerHandler());

    }

}

 1.3、服务端handler处理类

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.CharsetUtil;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
 *handler处理类
 *@author syb
 *@date 2023/9/6
 */
public class NettyServerHandler extends SimpleChannelInboundHandler {

    SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    /**
     * 新的客户端连接
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        Date connectTime = new Date(System.currentTimeMillis());
        System.out.println("时间:"+formatter.format(connectTime)+"  连接信息:" +ctx.channel().remoteAddress().toString());

    }

    /**
     * 断开连接
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        Date stopTime = new Date(System.currentTimeMillis());
        System.out.println("时间:"+formatter.format(stopTime)+"  断开连接:"+  ctx.channel().remoteAddress().toString());
    }

    /**
     * 发生异常
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        Date exceptionTime = new Date(System.currentTimeMillis());
        System.out.println("时间:"+formatter.format(exceptionTime)+ ctx.channel().remoteAddress().toString()+":客户端异常断开链接"+cause.getMessage());
        ctx.channel().close();

    }

    /**
     * 读写超时
     * @param ctx
     * @param evt
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt){
        Date eventTime = new Date(System.currentTimeMillis());
        String stateMsg="";
        if(evt instanceof IdleStateEvent){
            IdleStateEvent idleStateEvent=(IdleStateEvent)evt;
            switch (idleStateEvent.state()){
                case ALL_IDLE:
                    stateMsg="读写空闲";
                    break;
                case READER_IDLE:
                    stateMsg="读空闲";
                    break;
                case WRITER_IDLE:
                    stateMsg="写空闲";
                    break;

            }
        }
        System.out.println("时间:"+formatter.format(eventTime)+" IP:" +  ctx.channel().remoteAddress().toString() + "stateMsg:"+stateMsg);
    }

    /**
     * 获取客户端数据
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    public void channelRead0(ChannelHandlerContext ctx, Object  msg) throws Exception {
        Date createTime = new Date(System.currentTimeMillis());
       //读取数据
        ByteBuf buf=((ByteBuf)msg);
        String clientMsg=buf.toString(CharsetUtil.UTF_8);
        System.out.println(formatter.format(createTime)+"线程名:"+Thread.currentThread ().getName ()+"  连接信息:"+ ctx.channel().remoteAddress().toString()+" 数据内容:"+clientMsg);
       try {
           //休眠3分钟
           Thread.sleep(180*1000);
       }catch (Exception e){
           e.printStackTrace();
       }


    }
}

2.1、客户端启动类

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import java.net.InetSocketAddress;
import java.net.SocketAddress;

/**
 * 客户端  循环启动1000个客户端并2秒向服务器发送一次数据
 * @author syb
 * @date 2023/9/6
 */
public class NettyClientDemo {
    public static void main(String[] args) {
        //启动1000个客户端
        for(int i=1;i<1000;i++){
            int j=10000+i;//设置端口号
            new Thread(new Runnable() {
                @Override
                public void run() {
                    runClient(j);
                }
            }).start();
        }
    }

    /**
     * 启动客户端
     * @param port
     */
    public static void runClient(int port){

        //区别于服务端,我们在客户端只创建了一个NioEventLoopGroup实例,
        // 因为客户端你并不需要使用I/O多路复用模型,需要有一个Reactor来接受请求。只需要单纯的读写数据即可
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            //区别于服务端,我们在客户端只需要创建一个Bootstrap对象,它是客户端辅助启动类,功能类似于ServerBootstrap。
            Bootstrap b = new Bootstrap();
            //将NioEventLoopGroup实例绑定到Bootstrap对象中
            b.group(group)
                    //创建Channel(典型的channel有NioSocketChannel,NioServerSocketChannel,
                    // OioSocketChannel,OioServerSocketChannel,EpollSocketChannel,
                    // EpollServerSocketChannel),区别与服务端,这里创建的是NIOSocketChannel.
                    .channel(NioSocketChannel.class)
                    //设置参数,这里设置的TCP_NODELAY为true,意思是关闭延迟发送,一有消息就立即发送,默认为false。
                    .option(ChannelOption.TCP_NODELAY, true)
                    //建立连接后的具体Handler。注意这里区别与服务端,使用的是handler()而不是childHandler()。
                    // handler和childHandler的区别在于,
                    // handler是接受或发送之前的执行器;childHandler为建立连接之后的执行器。
                    .handler(new ClientChannelInitializer());
            //发起异步连接操作
            //设置远程连接的ip和端口号
            java.net.SocketAddress remoteAddress= new InetSocketAddress("127.0.0.1",8001) ;
            //设置本地连接的端口号
            SocketAddress localAddress =new InetSocketAddress("127.0.0.1",port) ;
            ChannelFuture f = b.connect(remoteAddress, localAddress).sync();
            //当代客户端链路关闭
            f.channel().closeFuture().sync();
        }catch (Exception e){
        }
        finally {
            // 优雅退出,释放NIO线程组
            group.shutdownGracefully();
        }
    }
}

2.2、客户端初始化通道

import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
/**
 *客户端连接通道初始化
 *@author syb
 *@date 2023/9/6
 */
public class ClientChannelInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel channel) {
        // 在管道中添加我们自己的接收数据实现方法
        channel.pipeline().addLast(new ClientServerHandler());
    }

}

 2.3、客户端handler处理类

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;
import java.text.SimpleDateFormat;
import java.util.*;
/**
 *客户端handler处理类
 *@author syb
 *@date 2023/9/6
 */
public class ClientServerHandler extends SimpleChannelInboundHandler {
    SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    /**
     * 新的客户端连接 循环2秒发一次数据
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        Date connectTime = new Date(System.currentTimeMillis());
        System.out.println("时间:"+formatter.format(connectTime)+"  服务端连接信息:" +ctx.channel().remoteAddress().toString());
        while(true){
            try {
                //休眠2秒
                Thread.sleep(2*1000);
                Date time = new Date(System.currentTimeMillis());
                String date=formatter.format(time);
                System.out.println("发送时间:"+date);
                //向服务端发送当前时间
                ctx.writeAndFlush(Unpooled.copiedBuffer(date,CharsetUtil.UTF_8));
            }catch (Exception exception){

            }
        }

    }

    /**
     * 断开连接
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        Date stopTime = new Date(System.currentTimeMillis());
        System.out.println("时间:"+formatter.format(stopTime)+"  断开连接:"+ ctx.channel().remoteAddress().toString());
    }

    /**
     * 发生异常
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        Date exceptionTime = new Date(System.currentTimeMillis());
        String clientIp=ctx.channel().remoteAddress().toString().replace("/","").split(":")[0];
        System.out.println("时间:"+formatter.format(exceptionTime)+clientIp+":客户端异常断开链接"+cause.getMessage());
        ctx.channel().close();

    }

    @Override
    public void channelRead0(ChannelHandlerContext ctx, Object  msg) throws Exception {
        Date createTime = new Date(System.currentTimeMillis());
        String clientIp=ctx.channel().remoteAddress().toString().replace("/","").split(":")[0];
        //读取数据
        ByteBuf buf=((ByteBuf)msg);
        String clientMsg=buf.toString(CharsetUtil.UTF_8);
        System.out.println(formatter.format(createTime)+"  IP:"+clientIp+"数据内容:"+clientMsg+"线程名:"+Thread.currentThread ().getName ());
    }


}

三、解决方法 

 1、可以看到以上客户端上传数据速度远远大于服务端处理数据的速度,随着时间的增长,会有很       多的消息积压,此时我们可以用netty的通道队列去处理。可以创建定时任务和普通任务,这里      只创建了普通任务。

    /**
     * 获取客户端数据
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    public void channelRead0(ChannelHandlerContext ctx, Object  msg) throws Exception {
        Date createTime = new Date(System.currentTimeMillis());
        //读取数据
        ByteBuf buf=((ByteBuf)msg);
        String clientMsg=buf.toString(CharsetUtil.UTF_8);
        //交给通道队列中处理,队列内的任务不是异步的,是循环处理的
        ctx.channel().eventLoop().execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(formatter.format(createTime)+"线程名:"+Thread.currentThread ().getName ()+"  连接信息:"+ ctx.channel().remoteAddress().toString()+" 数据内容:"+clientMsg);
                try {
                    //休眠3分钟
                    Thread.sleep(180*1000);
                }catch (Exception e){
                    e.printStackTrace();
                }
            }

        });
        
    }

2、也可以使用线程池去处理

  private static ExecutorService pool = Executors.newCachedThreadPool();

    /**
     * 获取客户端数据
     *
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    public void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        Date createTime = new Date(System.currentTimeMillis());
        //读取数据
        ByteBuf buf = ((ByteBuf) msg);
        String clientMsg = buf.toString(CharsetUtil.UTF_8);
        pool.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(formatter.format(createTime) + "线程名:" + Thread.currentThread().getName() + "  连接信息:" + ctx.channel().remoteAddress().toString() + " 数据内容:" + clientMsg);
                try {
                    //休眠3分钟
                    Thread.sleep(180 * 1000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });

    }


总结:要解决 Netty 运行一段时间后读不到数据的问题,需要从多个方面进行排查,包括网络、配置、缓冲区、连接状态、超时设置,增加netty通道或者线程池和调整数据处理逻辑等,可以解决该问题。

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值