只记录遇到的问题,适用于有Netty基础的小伙伴,如果可以帮到别人那最好,如果有问题,请及时指出。
handler中如果继承的是ChannelInboundHandlerAdapter类,则需要在重写消息处理channelRead方法中,手动释放ByteBuf,可以在finally块中写上:
ReferenceCountUtil.release(msg);
如果继承的是SimpleChannelInboundHandler类,重写channelRead0方法,则不需要,因为这个类继承了ChannelInboundHandlerAdapter类,重写了ChannelRead方法,并帮咱们在finally中释放了ByteBuf。
客户端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("要发送的数据")+"$_");
}
});
或者
ChannelFuture future = b.connect(HOST,PORT).sync();//确保同步建立连接
future.channel().writeAndFlush(JSONObject.toJSONString("要发送的数据")+"$_");
我使用的是Netty自带的DelimiterBasedFrameDecoder类来使用特定字符来分隔数据的,这个也是网上看到的,当然客户端和服务端都需要加上这个分隔操作。
bootstrap.childHandler(new ChannelInitializer(){
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//创建分隔符缓冲对象,使用"$_"作为分隔符
ByteBuf delimiter= 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());
}
});
个人理解:它就像synchronized关键字效果一样,只要加上sync()方法,那么它就是同步的,所以如果并发很大,就避免使用sync()方法,除非不影响多线程操作的地方可以使用,否则尽量不要使用,很影响性能。
这两个的区别,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(executor==null){
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(r);
}
}
});
}
}
}
return executor;
}
}
服务端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);
}
}
}
区别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、保证channel的生命周期,确保channel可用
4、注意心跳检测关闭时机,可能出现先关闭后发送的情况,导致服务端发送消息成功,但客户端接收不到消息的情况。
5、大并发情况下,注意对象的创建、代码的编写可靠,尽量避免垃圾创建消耗服务器。
6、最后是服务器带宽上限问题。