netty 处理耗时任务

原文链接:https://blog.csdn.net/lmq2582609/article/details/89152595
只记录遇到的问题,适用于有Netty基础的小伙伴,如果可以帮到别人那最好,如果有问题,请及时指出。

ByteBuf内存泄漏
handler中如果继承的是ChannelInboundHandlerAdapter类,则需要在重写消息处理channelRead方法中,手动释放ByteBuf,可以在finally块中写上:

ReferenceCountUtil.release(msg);
1
如果继承的是SimpleChannelInboundHandler类,重写channelRead0方法,则不需要,因为这个类继承了ChannelInboundHandlerAdapter类,重写了ChannelRead方法,并帮咱们在finally中释放了ByteBuf。

客户端给服务端发消息注意Channel是否建立成功
客户端connect(IP,PORT)的时候,最好加上监听或者sync()方法,要监听绑定IP或端口是否连接成功,否则如果连接不成功,就发送消息,会导致消息发不出去。

//监听方式
ChannelFuture future = b.connect(HOST,PORT).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
//监听建立连接成功在发送消息
future.channel().writeAndFlush(JSONObject.toJSONString(“要发送的数据”)+"KaTeX parse error: Expected 'EOF', got '}' at position 22: … }̲ })…_");
1
2
3
4
5
6
7
8
9
10
11
粘包问题处理
我使用的是Netty自带的DelimiterBasedFrameDecoder类来使用特定字符来分隔数据的,这个也是网上看到的,当然客户端和服务端都需要加上这个分隔操作。

bootstrap.childHandler(new ChannelInitializer(){
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//创建分隔符缓冲对象,使用" " 作 为 分 隔 符 B y t e B u f d e l i m i t e r = U n p o o l e d . c o p i e d B u f f e r ( " _"作为分隔符 ByteBuf delimiter= Unpooled.copiedBuffer(" "ByteBufdelimiter=Unpooled.copiedBuffer("_".getBytes());
ch.pipeline().addLast(new DelimiterBasedFrameDecoder(Integer.MAX_VALUE, delimiter));
//读超时 5秒 写超时 1秒 读写超时 1秒 时间单位:秒
ch.pipeline().addLast(new IdleStateHandler(5,1,1, TimeUnit.SECONDS));
ch.pipeline().addLast(new ServerHeadler());//自定义的headler
ch.pipeline().addLast(new StringEncoder());
}
});
1
2
3
4
5
6
7
8
9
10
11
12
sync()方法
个人理解:它就像synchronized关键字效果一样,只要加上sync()方法,那么它就是同步的,所以如果并发很大,就避免使用sync()方法,除非不影响多线程操作的地方可以使用,否则尽量不要使用,很影响性能。

ctx.close和ctx.channel.close
这两个的区别,ctx.close就是关闭当前上下文的。ctx.channel.close是关闭整个channel。
比如添加了多个handler,然后使用ctx.channel.close,那么它会依次关闭各个handler,然后关闭channel。
比如添加了多个handler,有1、2、3,3个handler,在走到第1个handler,然后使用ctx.close,那么第2个、第3个handler就不会执行了,执行完第1个,就直接关闭channel了。如果走到第二个handler,然后使用ctx.close,那么第1个会执行,第2个会执行,然后就关闭了第3个handler不会执行就关闭了channel了。
这样来看,这两个方法其实差不多,都能达到关闭channel的效果。使用哪个根据自己的业务场景来定。

耗时业务
在服务端的handler中,channelRead0处理消息方法中,采用线程池进行业务处理,从而保证netty自身的线程不被阻塞。
线程池类:

package com.cnpc.Executor;

import java.util.concurrent.*;

/**

  • 描述:

  • 创建: 一念丶 - LiMingQiang

  • 时间: 2019-03-29 13:43
    */
    public class MyExecutor {

    private static volatile Executor executor = null;

    /**

    • 业务线程池
    • @param threadSize 池大小
    • @param queues 队列 - 为0时 设置为20000
    • @return
      */
      public static Executor getBusPool(int threadSize,int queues){
      if(executornull){
      synchronized (MyExecutor.class){
      if(executor
      null){
      executor = new ThreadPoolExecutor(threadSize, threadSize, 0L, TimeUnit.SECONDS,
      queues == 0 ? new LinkedBlockingQueue<>(5000) : new LinkedBlockingQueue<>(queues),
      new RejectedExecutionHandler() {
      //线程队列拒绝策略
      @Override
      public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
      if (!e.isShutdown()) {
      //移除队头元素
      e.getQueue().poll();
      //再尝试入队
      e.execute®;
      }
      }
      });
      }
      }
      }
      return executor;
      }
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      服务端handler采用线程池处理耗时业务:

package com.cnpc.netty;

import com.alibaba.fastjson.JSONObject;
import com.cnpc.Executor.MyExecutor;
import com.cnpc.medium.Media;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import java.nio.charset.Charset;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

/**

  • 描述: NettyServerHandler - 处理消息的headler

  •   此headler是每个请求对应一个headler
    
  • 创建: 一念丶 - LiMingQiang

  • 时间: 2019-03-11 20:54
    */
    public class ServerHeadler extends ChannelInboundHandlerAdapter {

    public static final AtomicLong aid = new AtomicLong(1);

    static{
    //定时任务,每10秒统计一次连接数
    Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() ->{
    System.out.println(“当前连接数:”+aid.get());
    },0,1, TimeUnit.SECONDS);
    }

    private final Executor executor = MyExecutor.getBusPool(500);//500大小,队列5000

    private int lossConnectCount = 0;
    //消息处理
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg)throws Exception {
    try {
    if(msg instanceof ByteBuf){
    final Object message = msg;
    final ChannelHandlerContext chx = ctx;
    //线程池处理耗时业务
    executor.execute(new Runnable() {
    @Override
    public void run() {
    ByteBuf b = (ByteBuf) message;
    String content = b.toString((Charset.defaultCharset()));
    Thread.sleep(7000);//模拟耗时业务
    ChannelFuture channelFuture = chx.channel().writeAndFlush(JSONObject.toJSONString(“要返回的数据”) + “$_”);
    channelFuture.addListener(new ChannelFutureListener() {
    @Override
    public void operationComplete(ChannelFuture future) throws Exception {
    System.out.println(“ctx:”+chx+“服务端响应完毕”);
    //释放ByteBuf防止内存溢出
    ReferenceCountUtil.release(msg);
    }
    });
    }
    });
    }
    }catch (Exception e){
    // e.printStackTrace();
    }
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
    aid.incrementAndGet();//连接进来+1
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
    aid.decrementAndGet();//连接断开-1
    }

    //异常处理
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)throws Exception {
    System.out.println(this.getClass().getName()+" -> [连接异常] "+ctx.channel().id()+
    “通道异常,异常原因:”+cause.getMessage());
    ctx.close();
    }

    //心跳监测
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
    if(evt instanceof IdleStateEvent){
    IdleStateEvent event = (IdleStateEvent)evt;
    if(event.state().equals(IdleState.READER_IDLE)){ //如果读通道处于空闲状态,说明没有接收到心跳命令
    lossConnectCount++;
    if (lossConnectCount > 2) {
    // System.out.println("[释放不活跃通道]"+ctx.channel().id());
    ctx.channel().close();
    }
    }
    }else{
    super.userEventTriggered(ctx, evt);
    }
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    高并发option的参数 - 慎用
    区别option和childOption,简单就是说,在服务端程序中,option是针对自己的服务配置(监听socket使用),childOption是客户端连接成功后的channel配置选项(客户端连接后使用)。参数要慎用,知其然知其所以然方可使用,不然很容易就接不到消息,或者发布出去消息。

bootstrap.option(ChannelOption.SO_BACKLOG,4096);//请求的队列的最大长度
bootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);//重用缓冲区
bootstrap.option(ChannelOption.TCP_NODELAY, true);//立即发送数据
bootstrap.option(ChannelOption.SO_RCVBUF,128);//接收缓冲区大小
bootstrap.option(ChannelOption.SO_SNDBUF,256);//发送缓冲区大小
bootstrap.option(ChannelOption.SO_REUSEADDR,true);//快速端口复用
bootstrap.option(ChannelOption.SO_KEEPALIVE, true);//连接保活
1
2
3
4
5
6
7
有的消息通信成功,有的消息通信失败
若出现这种情况,请注意以下问题。以下为个人遇到问题后梳理,仅供参考。

1、保证有足够的端口可以使用
2、保证有足够的连接数可以使用
3、保证channel的生命周期,确保channel可用
4、注意心跳检测关闭时机,可能出现先关闭后发送的情况,导致服务端发送消息成功,但客户端接收不到消息的情况。
5、大并发情况下,注意对象的创建、代码的编写可靠,尽量避免垃圾创建消耗服务器。
6、最后是服务器带宽上限问题。
————————————————
版权声明:本文为CSDN博主「小太阳〃」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/lmq2582609/article/details/89152595只记录遇到的问题,适用于有Netty基础的小伙伴,如果可以帮到别人那最好,如果有问题,请及时指出。

ByteBuf内存泄漏
handler中如果继承的是ChannelInboundHandlerAdapter类,则需要在重写消息处理channelRead方法中,手动释放ByteBuf,可以在finally块中写上:

ReferenceCountUtil.release(msg);
1
如果继承的是SimpleChannelInboundHandler类,重写channelRead0方法,则不需要,因为这个类继承了ChannelInboundHandlerAdapter类,重写了ChannelRead方法,并帮咱们在finally中释放了ByteBuf。

客户端给服务端发消息注意Channel是否建立成功
客户端connect(IP,PORT)的时候,最好加上监听或者sync()方法,要监听绑定IP或端口是否连接成功,否则如果连接不成功,就发送消息,会导致消息发不出去。

//监听方式
ChannelFuture future = b.connect(HOST,PORT).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
//监听建立连接成功在发送消息
future.channel().writeAndFlush(JSONObject.toJSONString(“要发送的数据”)+"KaTeX parse error: Expected 'EOF', got '}' at position 22: … }̲ })…_");
1
2
3
4
5
6
7
8
9
10
11
粘包问题处理
我使用的是Netty自带的DelimiterBasedFrameDecoder类来使用特定字符来分隔数据的,这个也是网上看到的,当然客户端和服务端都需要加上这个分隔操作。

bootstrap.childHandler(new ChannelInitializer(){
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//创建分隔符缓冲对象,使用" " 作 为 分 隔 符 B y t e B u f d e l i m i t e r = U n p o o l e d . c o p i e d B u f f e r ( " _"作为分隔符 ByteBuf delimiter= Unpooled.copiedBuffer(" "ByteBufdelimiter=Unpooled.copiedBuffer("_".getBytes());
ch.pipeline().addLast(new DelimiterBasedFrameDecoder(Integer.MAX_VALUE, delimiter));
//读超时 5秒 写超时 1秒 读写超时 1秒 时间单位:秒
ch.pipeline().addLast(new IdleStateHandler(5,1,1, TimeUnit.SECONDS));
ch.pipeline().addLast(new ServerHeadler());//自定义的headler
ch.pipeline().addLast(new StringEncoder());
}
});
1
2
3
4
5
6
7
8
9
10
11
12
sync()方法
个人理解:它就像synchronized关键字效果一样,只要加上sync()方法,那么它就是同步的,所以如果并发很大,就避免使用sync()方法,除非不影响多线程操作的地方可以使用,否则尽量不要使用,很影响性能。

ctx.close和ctx.channel.close
这两个的区别,ctx.close就是关闭当前上下文的。ctx.channel.close是关闭整个channel。
比如添加了多个handler,然后使用ctx.channel.close,那么它会依次关闭各个handler,然后关闭channel。
比如添加了多个handler,有1、2、3,3个handler,在走到第1个handler,然后使用ctx.close,那么第2个、第3个handler就不会执行了,执行完第1个,就直接关闭channel了。如果走到第二个handler,然后使用ctx.close,那么第1个会执行,第2个会执行,然后就关闭了第3个handler不会执行就关闭了channel了。
这样来看,这两个方法其实差不多,都能达到关闭channel的效果。使用哪个根据自己的业务场景来定。

耗时业务
在服务端的handler中,channelRead0处理消息方法中,采用线程池进行业务处理,从而保证netty自身的线程不被阻塞。
线程池类:

package com.cnpc.Executor;

import java.util.concurrent.*;

/**

  • 描述:

  • 创建: 一念丶 - LiMingQiang

  • 时间: 2019-03-29 13:43
    */
    public class MyExecutor {

    private static volatile Executor executor = null;

    /**

    • 业务线程池
    • @param threadSize 池大小
    • @param queues 队列 - 为0时 设置为20000
    • @return
      */
      public static Executor getBusPool(int threadSize,int queues){
      if(executornull){
      synchronized (MyExecutor.class){
      if(executor
      null){
      executor = new ThreadPoolExecutor(threadSize, threadSize, 0L, TimeUnit.SECONDS,
      queues == 0 ? new LinkedBlockingQueue<>(5000) : new LinkedBlockingQueue<>(queues),
      new RejectedExecutionHandler() {
      //线程队列拒绝策略
      @Override
      public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
      if (!e.isShutdown()) {
      //移除队头元素
      e.getQueue().poll();
      //再尝试入队
      e.execute®;
      }
      }
      });
      }
      }
      }
      return executor;
      }
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      服务端handler采用线程池处理耗时业务:

package com.cnpc.netty;

import com.alibaba.fastjson.JSONObject;
import com.cnpc.Executor.MyExecutor;
import com.cnpc.medium.Media;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import java.nio.charset.Charset;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

/**

  • 描述: NettyServerHandler - 处理消息的headler

  •   此headler是每个请求对应一个headler
    
  • 创建: 一念丶 - LiMingQiang

  • 时间: 2019-03-11 20:54
    */
    public class ServerHeadler extends ChannelInboundHandlerAdapter {

    public static final AtomicLong aid = new AtomicLong(1);

    static{
    //定时任务,每10秒统计一次连接数
    Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() ->{
    System.out.println(“当前连接数:”+aid.get());
    },0,1, TimeUnit.SECONDS);
    }

    private final Executor executor = MyExecutor.getBusPool(500);//500大小,队列5000

    private int lossConnectCount = 0;
    //消息处理
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg)throws Exception {
    try {
    if(msg instanceof ByteBuf){
    final Object message = msg;
    final ChannelHandlerContext chx = ctx;
    //线程池处理耗时业务
    executor.execute(new Runnable() {
    @Override
    public void run() {
    ByteBuf b = (ByteBuf) message;
    String content = b.toString((Charset.defaultCharset()));
    Thread.sleep(7000);//模拟耗时业务
    ChannelFuture channelFuture = chx.channel().writeAndFlush(JSONObject.toJSONString(“要返回的数据”) + “$_”);
    channelFuture.addListener(new ChannelFutureListener() {
    @Override
    public void operationComplete(ChannelFuture future) throws Exception {
    System.out.println(“ctx:”+chx+“服务端响应完毕”);
    //释放ByteBuf防止内存溢出
    ReferenceCountUtil.release(msg);
    }
    });
    }
    });
    }
    }catch (Exception e){
    // e.printStackTrace();
    }
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
    aid.incrementAndGet();//连接进来+1
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
    aid.decrementAndGet();//连接断开-1
    }

    //异常处理
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)throws Exception {
    System.out.println(this.getClass().getName()+" -> [连接异常] "+ctx.channel().id()+
    “通道异常,异常原因:”+cause.getMessage());
    ctx.close();
    }

    //心跳监测
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
    if(evt instanceof IdleStateEvent){
    IdleStateEvent event = (IdleStateEvent)evt;
    if(event.state().equals(IdleState.READER_IDLE)){ //如果读通道处于空闲状态,说明没有接收到心跳命令
    lossConnectCount++;
    if (lossConnectCount > 2) {
    // System.out.println("[释放不活跃通道]"+ctx.channel().id());
    ctx.channel().close();
    }
    }
    }else{
    super.userEventTriggered(ctx, evt);
    }
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    高并发option的参数 - 慎用
    区别option和childOption,简单就是说,在服务端程序中,option是针对自己的服务配置(监听socket使用),childOption是客户端连接成功后的channel配置选项(客户端连接后使用)。参数要慎用,知其然知其所以然方可使用,不然很容易就接不到消息,或者发布出去消息。

bootstrap.option(ChannelOption.SO_BACKLOG,4096);//请求的队列的最大长度
bootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);//重用缓冲区
bootstrap.option(ChannelOption.TCP_NODELAY, true);//立即发送数据
bootstrap.option(ChannelOption.SO_RCVBUF,128);//接收缓冲区大小
bootstrap.option(ChannelOption.SO_SNDBUF,256);//发送缓冲区大小
bootstrap.option(ChannelOption.SO_REUSEADDR,true);//快速端口复用
bootstrap.option(ChannelOption.SO_KEEPALIVE, true);//连接保活
1
2
3
4
5
6
7
有的消息通信成功,有的消息通信失败
若出现这种情况,请注意以下问题。以下为个人遇到问题后梳理,仅供参考。

1、保证有足够的端口可以使用
2、保证有足够的连接数可以使用
3、保证channel的生命周期,确保channel可用
4、注意心跳检测关闭时机,可能出现先关闭后发送的情况,导致服务端发送消息成功,但客户端接收不到消息的情况。
5、大并发情况下,注意对象的创建、代码的编写可靠,尽量避免垃圾创建消耗服务器。
6、最后是服务器带宽上限问题。
————————————————
版权声明:本文为CSDN博主「小太阳〃」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/lmq2582609/article/details/89152595只记录遇到的问题,适用于有Netty基础的小伙伴,如果可以帮到别人那最好,如果有问题,请及时指出。

ByteBuf内存泄漏
handler中如果继承的是ChannelInboundHandlerAdapter类,则需要在重写消息处理channelRead方法中,手动释放ByteBuf,可以在finally块中写上:

ReferenceCountUtil.release(msg);
1
如果继承的是SimpleChannelInboundHandler类,重写channelRead0方法,则不需要,因为这个类继承了ChannelInboundHandlerAdapter类,重写了ChannelRead方法,并帮咱们在finally中释放了ByteBuf。

客户端给服务端发消息注意Channel是否建立成功
客户端connect(IP,PORT)的时候,最好加上监听或者sync()方法,要监听绑定IP或端口是否连接成功,否则如果连接不成功,就发送消息,会导致消息发不出去。

//监听方式
ChannelFuture future = b.connect(HOST,PORT).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
//监听建立连接成功在发送消息
future.channel().writeAndFlush(JSONObject.toJSONString(“要发送的数据”)+"KaTeX parse error: Expected 'EOF', got '}' at position 22: … }̲ })…_");
1
2
3
4
5
6
7
8
9
10
11
粘包问题处理
我使用的是Netty自带的DelimiterBasedFrameDecoder类来使用特定字符来分隔数据的,这个也是网上看到的,当然客户端和服务端都需要加上这个分隔操作。

bootstrap.childHandler(new ChannelInitializer(){
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//创建分隔符缓冲对象,使用" " 作 为 分 隔 符 B y t e B u f d e l i m i t e r = U n p o o l e d . c o p i e d B u f f e r ( " _"作为分隔符 ByteBuf delimiter= Unpooled.copiedBuffer(" "ByteBufdelimiter=Unpooled.copiedBuffer("_".getBytes());
ch.pipeline().addLast(new DelimiterBasedFrameDecoder(Integer.MAX_VALUE, delimiter));
//读超时 5秒 写超时 1秒 读写超时 1秒 时间单位:秒
ch.pipeline().addLast(new IdleStateHandler(5,1,1, TimeUnit.SECONDS));
ch.pipeline().addLast(new ServerHeadler());//自定义的headler
ch.pipeline().addLast(new StringEncoder());
}
});
1
2
3
4
5
6
7
8
9
10
11
12
sync()方法
个人理解:它就像synchronized关键字效果一样,只要加上sync()方法,那么它就是同步的,所以如果并发很大,就避免使用sync()方法,除非不影响多线程操作的地方可以使用,否则尽量不要使用,很影响性能。

ctx.close和ctx.channel.close
这两个的区别,ctx.close就是关闭当前上下文的。ctx.channel.close是关闭整个channel。
比如添加了多个handler,然后使用ctx.channel.close,那么它会依次关闭各个handler,然后关闭channel。
比如添加了多个handler,有1、2、3,3个handler,在走到第1个handler,然后使用ctx.close,那么第2个、第3个handler就不会执行了,执行完第1个,就直接关闭channel了。如果走到第二个handler,然后使用ctx.close,那么第1个会执行,第2个会执行,然后就关闭了第3个handler不会执行就关闭了channel了。
这样来看,这两个方法其实差不多,都能达到关闭channel的效果。使用哪个根据自己的业务场景来定。

耗时业务
在服务端的handler中,channelRead0处理消息方法中,采用线程池进行业务处理,从而保证netty自身的线程不被阻塞。
线程池类:

package com.cnpc.Executor;

import java.util.concurrent.*;

/**

  • 描述:

  • 创建: 一念丶 - LiMingQiang

  • 时间: 2019-03-29 13:43
    */
    public class MyExecutor {

    private static volatile Executor executor = null;

    /**

    • 业务线程池
    • @param threadSize 池大小
    • @param queues 队列 - 为0时 设置为20000
    • @return
      */
      public static Executor getBusPool(int threadSize,int queues){
      if(executornull){
      synchronized (MyExecutor.class){
      if(executor
      null){
      executor = new ThreadPoolExecutor(threadSize, threadSize, 0L, TimeUnit.SECONDS,
      queues == 0 ? new LinkedBlockingQueue<>(5000) : new LinkedBlockingQueue<>(queues),
      new RejectedExecutionHandler() {
      //线程队列拒绝策略
      @Override
      public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
      if (!e.isShutdown()) {
      //移除队头元素
      e.getQueue().poll();
      //再尝试入队
      e.execute®;
      }
      }
      });
      }
      }
      }
      return executor;
      }
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      服务端handler采用线程池处理耗时业务:

package com.cnpc.netty;

import com.alibaba.fastjson.JSONObject;
import com.cnpc.Executor.MyExecutor;
import com.cnpc.medium.Media;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import java.nio.charset.Charset;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

/**

  • 描述: NettyServerHandler - 处理消息的headler

  •   此headler是每个请求对应一个headler
    
  • 创建: 一念丶 - LiMingQiang

  • 时间: 2019-03-11 20:54
    */
    public class ServerHeadler extends ChannelInboundHandlerAdapter {

    public static final AtomicLong aid = new AtomicLong(1);

    static{
    //定时任务,每10秒统计一次连接数
    Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() ->{
    System.out.println(“当前连接数:”+aid.get());
    },0,1, TimeUnit.SECONDS);
    }

    private final Executor executor = MyExecutor.getBusPool(500);//500大小,队列5000

    private int lossConnectCount = 0;
    //消息处理
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg)throws Exception {
    try {
    if(msg instanceof ByteBuf){
    final Object message = msg;
    final ChannelHandlerContext chx = ctx;
    //线程池处理耗时业务
    executor.execute(new Runnable() {
    @Override
    public void run() {
    ByteBuf b = (ByteBuf) message;
    String content = b.toString((Charset.defaultCharset()));
    Thread.sleep(7000);//模拟耗时业务
    ChannelFuture channelFuture = chx.channel().writeAndFlush(JSONObject.toJSONString(“要返回的数据”) + “$_”);
    channelFuture.addListener(new ChannelFutureListener() {
    @Override
    public void operationComplete(ChannelFuture future) throws Exception {
    System.out.println(“ctx:”+chx+“服务端响应完毕”);
    //释放ByteBuf防止内存溢出
    ReferenceCountUtil.release(msg);
    }
    });
    }
    });
    }
    }catch (Exception e){
    // e.printStackTrace();
    }
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
    aid.incrementAndGet();//连接进来+1
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
    aid.decrementAndGet();//连接断开-1
    }

    //异常处理
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)throws Exception {
    System.out.println(this.getClass().getName()+" -> [连接异常] "+ctx.channel().id()+
    “通道异常,异常原因:”+cause.getMessage());
    ctx.close();
    }

    //心跳监测
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
    if(evt instanceof IdleStateEvent){
    IdleStateEvent event = (IdleStateEvent)evt;
    if(event.state().equals(IdleState.READER_IDLE)){ //如果读通道处于空闲状态,说明没有接收到心跳命令
    lossConnectCount++;
    if (lossConnectCount > 2) {
    // System.out.println("[释放不活跃通道]"+ctx.channel().id());
    ctx.channel().close();
    }
    }
    }else{
    super.userEventTriggered(ctx, evt);
    }
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    高并发option的参数 - 慎用
    区别option和childOption,简单就是说,在服务端程序中,option是针对自己的服务配置(监听socket使用),childOption是客户端连接成功后的channel配置选项(客户端连接后使用)。参数要慎用,知其然知其所以然方可使用,不然很容易就接不到消息,或者发布出去消息。

bootstrap.option(ChannelOption.SO_BACKLOG,4096);//请求的队列的最大长度
bootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);//重用缓冲区
bootstrap.option(ChannelOption.TCP_NODELAY, true);//立即发送数据
bootstrap.option(ChannelOption.SO_RCVBUF,128);//接收缓冲区大小
bootstrap.option(ChannelOption.SO_SNDBUF,256);//发送缓冲区大小
bootstrap.option(ChannelOption.SO_REUSEADDR,true);//快速端口复用
bootstrap.option(ChannelOption.SO_KEEPALIVE, true);//连接保活
1
2
3
4
5
6
7
有的消息通信成功,有的消息通信失败
若出现这种情况,请注意以下问题。以下为个人遇到问题后梳理,仅供参考。

1、保证有足够的端口可以使用
2、保证有足够的连接数可以使用
3、保证channel的生命周期,确保channel可用
4、注意心跳检测关闭时机,可能出现先关闭后发送的情况,导致服务端发送消息成功,但客户端接收不到消息的情况。
5、大并发情况下,注意对象的创建、代码的编写可靠,尽量避免垃圾创建消耗服务器。
6、最后是服务器带宽上限问题。
————————————————
版权声明:本文为CSDN博主「小太阳〃」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/lmq2582609/article/details/89152595

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值