Netty线程模型
来源:互联网 发布:收快递的软件 编辑:程序博客网 时间:2024/05/28 15:38
1.Netty线程模型分类
事实上,Netty线程模型与与Reactor线程模型(之前有介绍)相似,下面我们通过Netty服务端和客户端的线程处理流程来介绍Netty的线程模型。
1.1服务端线程模型
一种比较流行的做法是服务端监听线程和IO线程分离,类似于Reactor的多线程模型,它的工作原理图如下:
Netty服务端线程工作流程
下面结合Netty的源码,对服务端创建线程工作流程进行介绍:
第一步,从用户线程发起创建服务端操作,代码如下:
public void run() throws Exception{ //config the server EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup,workerGroup). option(ChannelOption.SO_BACKLOG,100). handler(new LoggingHandler(LogLevel.INFO)). childHandler(new ChannelInitial<SocketChannel>() { @override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addlast( //new LoggingHandler(LogLevel.INFO) new EchoSeverHandler()); } }); //Start the server. ChannelFuture f = b.bind(port).sync(); }}
通常情况下,服务端的创建是在用户进程启动的时候进行,因此一般由Main函数或者启动类负责创建,服务端的创建由业务线程负责完成。在创建服务端的时候实例化了2个EventLoopGroup,1个EventLoopGroup实际就是一个EventLoop线程组,负责管理EventLoop的申请与释放。
EventLoopGroup管理的线程数可以通过构造函数设置,如果没有设置,默认取-Dio.netty.eventLoopThreads,如果该系统参数也没有设定,则为可用的CPU内核数 x 2.
private static final int DEFAULT_EVENT_LOOP_THREADS;static { DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt( "io.netty.eventLoopThreads", Runtime.getRuntime().availableProcessors() * 2)); if (logger.isDebugEnabled()) { logger.debug("-Dio.netty.eventLoopThreads: {}", DEFAULT_EVENT_LOOP_THREADS); }}
bossGroup线程组实际就是Acceptor线程池,负责处理客户端的TCP连接请求,如果系统只有一个服务端端口需要监听,则建议bossGroup线程组线程数设置为1。
workerGroup线程是真正负责IO读写操作的线程组,通过SeverBootstrap的group方法进行设置,用于后续的Channel绑定。
第二步,Acceptor线程绑定监听端口,启动NIO服务,相关代码如下:
/*** 从bossGroup中选择一个Acceptor线程监听服务端*/@overrideChannel createChannel(){ EventLoop eventLoop = group().next(); return channelFactory().newChannel(eventLoop,childGroup);}
其中,group()组返回的就是bossGroup,它的next方法用于从线程组中获取可用线程,代码如下:
/*** 选择Acceptor线程* /@overridepublic EventExecutor next(){ return children[Math.abs(childIndex.getAndIncrement()%children.length)];}
服务端Channel创建完成之后,将其注册到多路复用器Selector上,用于接收客户端的TCP连接,核心代码如下:
/*** Create a new instance,注册ServerSocketChannel到Selector* /public NioServerSocketChannel(EventLoop eventLoop, EventLoopGroup childGroup){ super(null,eventLoop,childGroup,newSocket(),SelectionKey.OP_ACCEPT);config = new DefaultServerSocketChannelConfig(this,javaChannel().socket());}
第三步,如果监听到客户端连接,则创建客户端SocketChannel连接,重新注册到workerGroup的IO线程上。首先看Acceptor如何处理客户端的接入:
/*** 处理读或者连接事件* /try{ int readyOps = k.readyOps(); //Also check for readOps of 0 to workaround possible JDK bug which may otherwise lead //to a spin loop if(readyOps&(SelectionKey.OP_READ|SelectionKey.OP_ACCEPT)!=0||readyOps==0) unsafe.read(); if(!ch.isOpen()) { //connection already closed - no need to handle write. return; }}
调用unsafe的read()方法,对于NioServerSocketChannel,它调用了NioMesageUnsafe的read()方法,代码如下:
/*** NioServerSocketChannel的read()方法Throwable exception = null;try{ for(;;) { int localRead = doReadMessage(readBuf); if(localRead == 0) { break; } if(localRead<0) { closed = true; break; } }}
最终它会调用NioServerSocketChannel的doReadMessage()方法,代码如下:
/*** 创建客户端连接SocketChannel的read()方法protected int doReadMesage(List<Object> buf) throws Exception{ SocketChannel ch = javaChannel().accept(); try { if(ch!=null) { buf.add(new NioSocektChannel(this,childEventLoopGroup().next(),ch)); return 1; } }}
其中childEventLoopGroup就是之前的workerGroup,从中选择一个IO线程负责网络消息的读写。
第四步,选择IO线程后,将SocketChannel注册到多路复用器上,监听Read操作。
/***监听网络读事件*/protected AbstractNioByteChannel(Channel parent,EventLoop eventLoop,SelectableChannel ch){ super(parent,eventLoop,ch,SelectionKey.OP_READ);}
第五步,处理网络的IO读写事件,核心代码如下:
/*** 处理读写事件int readyOps = k.readyOps();//Also check for readOps of 0 to workaround possible JDK bug which may otherwise lead to a spin loopif((readyOps&(SelectionKey.OP_READ|Selection.OP_ACCEPT))!=0||readyOps==0){ unsafe.read(); if(!ch.isOpen()) { return; } if(readyOps&SelectinKey.OP_WRITE!=0) { ch.unsafe().forceFlush(); }}
1.2客户端线程模型
Netty 客户端线程模型
相比于服务端,客户端的线程模型简单一些,它的工作原理如下:
第一步,由用户线程发起客户端连接,示例代码如下:
/*** Netty 客户端创建代码//Configure the clientEventLoopGroup group = new NioEventLoopGroup();try{ Bootstrap b = new Bootstrap(); b.group(group) .channel(NioSocketChannel.class) .option(ChannelOption.TCP_NODELAY,ture) .handler(new ChannelInitializer<SocketChannel>() { @override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast ( //new LoggingHandler(LogLevel.INFO) new EchoClientHandler(firstMessageSize) ); } }); //Start the Client ChannelFuture f = b.connect(host,port).sync();}
相比于服务端,客户端只需要创建一个EventLoopGroup,因为它不需要独立的线程去监听客户端连接,也没有必要通过一个单独的客户端线程去连接服务端。Netty是异步事件驱动的NIO框架,它的连接和所有的IO操作都是异步的,因此不需要创建单独的连接线程。相关代码如下:
//绑定客户端连接线程@overrideChannel createChannel(){ EventLoop eventLoop = group.next(); return ChannelFactory().newChannel(EventLoop);}
当前的group()就是之前传入的EventLoopGroup,从中获取可用的IO线程EventLoop,然后作为参数设置到新创建的NioChannel中。
第二步,发起连接操作,判断连接结果,代码如下:
/*** 连接操作* /@overrideprotected boolean doConnect(SocketAddress remoteAddress,SocketAddress localAddress){ if(localAddress!=null) { javaChannel().socket().bind(localAddress); } boolean success = false; try { boolean connected = javaChannel().connect(remoteAddress); if(!connected) { selectionKey().interestOps(SelectionKey.OP_CONNECT); } success = true; return connected; }finally { if(!success) { doClose(); } }}
判断连接结果,如果没有连接成功,则监听连接网络操作位SelectionKey.OP_CONNECT。如果连接成功,则调用pipeline().fireChannelActive()将监听位修改为READ。
第三步,由NioEventLoop的多路复用轮询连接操作结果,代码如下:
/***Selector 发起轮询操作*/if(readyOps&SelectionKey.OP_CONNECT!=0){ int ops = k.interestOps(); ops&=~SelectionKey.OP_CONNECT; k.interestOps(ops); unsafe.finishConnect();}
判断连接结果,如果连接成功,重新设置监听位为READ:
/*** 判断连接操作结果@overridepublic void finishConnect(){ assert eventLoop().inEventLoop(); assert connectPromise !=null; try { boolean wasActive = isActive(); doFinishConnect(); FulfillConnectPromise(connectPromise,wasActive); }}
/*** 设置操作位为READ* /@override protected void doBeginRead() throws Exception{ if(inputShutDown) return; final SelectionKey selectionKey = this.selectionKey; if(!selectionKey.isValid()) { return; } final int interestOps = selectionKey.interestOps(); if(interestOps&readinterestOp==0) { selectionKey.interestOps(interestOps|readInterestOp); }}
第四步,由NioEventLoop线程负责IO读写,同服务端。
总结:客户端创建,线程模型如下:
1.由用户线程负责初始化客户端资源,发起连接操作;
2.如果连接成功,将SocketChannel注册到IO线程组的NioEventLoop线程中,监听读操作位;
3.如果没有立即连接成功,将SocketChannel注册大NioEventLoop线程中,监听连接操作位;
4.连接成功后,修改监听位为READ,但是不需要切换线程。
注:NioEventLoop的介绍,将会放在下一章节。
- 【Netty】Netty系列之Netty线程模型
- Netty线程模型
- Netty线程模型详解
- Netty线程模型
- Netty线程模型
- Netty线程模型详解
- netty nio线程模型
- Netty线程模型详解
- Netty的线程模型
- Netty线程模型
- Netty线程模型
- Netty的线程模型
- Netty线程模型
- Netty线程模型
- Netty线程模型
- Netty线程模型
- netty线程模型
- Netty系列之Netty线程模型
- 使用 Adobe Acrobat 裁剪 PDF 白边及其他操作
- Build & Install GCC4.6.3 in CentOS (5.2 & 6.3)
- linux 修改IP
- Java中System类
- Linux C 文件操作
- Netty线程模型
- 金融风控-->客户流失预警模型-->金融数据分析
- 【Spring】Spring整合Struts2
- Spring Boot 快速入门教程
- 嵌入式linux--电子相册
- 【bitset乱搞】BZOJ3687 简单题
- OpenBLAS:改进OpenBLASConfig.cmake生成方式,解除cmake脚本的路径依赖
- 前端集锦
- Maven的下载安装