Q1:服务器主动关闭连接后,客户端没有监听到socket关闭的事件。
想象中,调用org.jboss.netty.channel.Channel.close();-->GateServerHandler.closeRequested-->channelDisconnected-->channelClosed-->client
实际上,调用org.jboss.netty.channel.Channel.close();-->GateServerHandler.closeRequested 就停止了,导致游戏实现上,玩家重复登陆,客户端没法收到服务器已经关闭了同一玩家前面建立的连接,所以客户端表现为无法断开与服务器的连接。
后来发现少了这样的一句话,导致closeRequested 就停止了。
public void closeRequested(ChannelHandlerContext ctx, ChannelStateEvent e) { ctx.sendDownstream(e); }
closeRequested 一般为服务器主动发起的关闭,另外,关于channelDisconnected和channelClosed的区别,看源码:
org.jboss.netty.channel.socket.nio.NioWorker.java
//netty version: 3.1.5GA //file: org.jboss.netty.channel.socket.nio.NioWorker.java //method: static void close(NioSocketChannel channel, ChannelFuture future) //line: 581 future.setSuccess(); if (connected) { fireChannelDisconnected(channel); } if (bound) { fireChannelUnbound(channel); } cleanUpWriteBuffer(channel); fireChannelClosed(channel);
我们可以看到,在上述代码中,在close channel的时候,会先判断当前channel是否处于connected状态,即是否已经成功地与远程地址建立了连接,如果是的话,就触发channelDisconnected事件;最后,再统一触发channelClosed事件。
也就是说,任何对NioWorker.close(NioSocketChannel channel, ChannelFuture future)方法的调用都会触发channelClosed事件,这些事件可能包括如下几种:
1. 已经与远程主机建立的连接,远程主机主动关闭连接,或者网络异常连接被断开的情况
2. 已经与远程主机建立的连接,本地客户机主动关闭连接的情况
3. 本地客户机在试图与远程主机建立连接时,遇到类似与connection refused这样的异常,未能连接成功时
而只有当本地客户机已经成功的与远程主机建立连接(connected)时,连接断开的时候才会触发channelDisconnected事件,即对应上述的1和2两种情况。
上述猜想已经通过编写测试代码,模拟不同的情况得到了证实。
Q2:一个Boss线程(一个服务器端口对于一个)--->接收到客户端连接--->生成Channel--->交给Work线程池(多个Work线程)来处理。
一个连接会绑定一个worker线程,选择的线程算法如下:
public E nextWorker() {
return (E) workers[Math.abs(workerIndex.getAndIncrement() % workers.length)];
}
按顺序循坏往复。
这个连接的所有接受消息和发送消息最终都会由这个worker来执行
在写出消息时,如果不是IO线程,则会写入到发消息队列里,由IO worker线程去处理,如果是IO 线程则直接写出去
监控点:UnpooledSendBuffer.transferTo
http://www.haogongju.net/art/2034726
http://xsh5324.iteye.com/blog/1915534
具体的Work线程---读完已接收的数据到ChannelBuffer---触发ChannelPipeline中的ChannelHandler链来处理业务逻辑。
注意:执行ChannelHandler链的整个过程是同步的,如果业务逻辑的耗时较长,会将导致Work线程长时间被占用得不到释放,从而影响了整个服务器的并发处理能力。
所以,为了提高并发数,一般通过ExecutionHandler线程池来异步处理ChannelHandler链(worker线程在经过ExecutionHandler后就结束了,它会被ChannelFactory的worker线程池所回收)。
Boostrap.bind 方法包含两个参数NioServerSocketChannelFactory、ChannelPipelineFactory。NioServerSocketChannelFactory包含连个线程池bossExecutor和workerExecutor,workerExecutor: 包含缺省为处理器个数×2个NioWorker进程。
对于ExecutionHandler需要的线程池模型,Netty提供了两种可选:
1) MemoryAwareThreadPoolExecutor 通过对线程池内存的使用控制,可控制Executor中待处理任务的上限(超过上限时,后续进来的任务将被阻塞),并可控制单个Channel待处理任务的上限,防止内存溢出错误;
2) OrderedMemoryAwareThreadPoolExecutor 是 MemoryAwareThreadPoolExecutor 的子类。除了MemoryAwareThreadPoolExecutor 的功能之外,它还可以保证同一Channel中处理的事件流的顺序性,这主要是控制事件在异步处理模式下可能出现的错误的事件顺序,但它并不保证同一Channel中的事件都在一个线程中执行(通常也没必要)。
Q3:如果压力测试写日志频率较高,会阻塞netty发送消息,需要给日志起单独线程
Q4:默认netty起来后,如果用Executors.newCachedThreadPool(),默认会起1个boss线程,8个work线程,但实际上后台并不需要这么多work,
// Configure the server.
ServerBootstrap bootstrap = new ServerBootstrap(new NioServerSocketChannelFactory(Executors.newCachedThreadPool(),
Executors.newSingleThreadExecutor()));
发现,其他客户端连上来后,断开后,就再也没法连了,
Executors.newSingleThreadExecutor()换成Executors.newFixedThreadPool(1) 就没问题了。
netty客户端这边一个ClientBootstrap 可以连多个server
Q5:字节打印,很漂亮
ByteBufUtil.hexDump(byte[] array)
ByteBufUtil.prettyHexDumpbyte[] array)