netty 处理耗时任务

原文链接:https://blog.youkuaiyun.com/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、最后是服务器带宽上限问题。
————————————————
版权声明:本文为优快云博主「小太阳〃」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.youkuaiyun.com/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、最后是服务器带宽上限问题。
————————————————
版权声明:本文为优快云博主「小太阳〃」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.youkuaiyun.com/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、最后是服务器带宽上限问题。
————————————————
版权声明:本文为优快云博主「小太阳〃」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.youkuaiyun.com/lmq2582609/article/details/89152595

### IntelliJ IDEA 中通义 AI 功能介绍 IntelliJ IDEA 提供了一系列强大的工具来增强开发体验,其中包括与通义 AI 相关的功能。这些功能可以帮助开发者更高效地编写代并提高生产力。 #### 安装通义插件 为了使用通义的相关特性,在 IntelliJ IDEA 中需要先安装对应的插件: 1. 打开 **Settings/Preferences** 对话框 (Ctrl+Alt+S 或 Cmd+, on macOS)。 2. 导航到 `Plugins` 页面[^1]。 3. 在 Marketplace 中搜索 "通义" 并点击安装按钮。 4. 完成安装后重启 IDE 使更改生效。 #### 配置通义服务 成功安装插件之后,还需要配置通义的服务连接信息以便正常使用其提供的各项能力: - 进入设置中的 `Tools | Qwen Coding Assistant` 菜单项[^2]。 - 填写 API Key 和其他必要的认证参数。 - 测试连接以确认配置无误。 #### 使用通义辅助编程 一旦完成上述准备工作,就可以利用通义来进行智能编支持了。具体操作如下所示: ##### 自动补全代片段 当输入部分语句时,IDE 将自动提示可能的后续逻辑,并允许一键插入完整的实现方案[^3]。 ```java // 输入 while 循环条件前半部分... while (!list.isEmpty()) { // 激活建议列表选择合适的循环体内容 } ``` ##### 解释现有代含义 选中某段复杂的表达式或函数调用,右键菜单里会有选项可以请求通义解析这段代的作用以及优化意见。 ##### 生产测试案例 对于已有的业务逻辑模块,借助于通义能够快速生成单元测试框架及初始断言集,减少手动构建的成本。 ```python def test_addition(): result = add(2, 3) assert result == 5, f"Expected 5 but got {result}" ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值