Netty 是基于 Java NIO 的异步事件驱动的网络应用框架,使用 Netty 能够快速开发网络应用,Netty 提供了高层次的抽象来简化 TCP 和 UDP 服务器的编程,可是你仍然可使用底层的 API。react
Netty 的内部实现是很复杂的,可是 Netty 提供了简单易用的API从网络处理代码中解耦业务逻辑。Netty 是彻底基于 NIO 实现的,因此整个 Netty 都是异步的。编程
Netty 是最流行的 NIO 框架,它已经获得成百上千的商业、商用项目验证,许多框架和开源组件的底层 rpc 都是使用的 Netty,如 Dubbo、Elasticsearch 等等。下面是官网给出的一些 Netty 的特性:json
设计方面bootstrap
易用性后端
性能浏览器
安全性安全
对于初学者,上面的特性咱们在脑中有个简单了解和印象便可, 下面开始咱们的实战部分。性能优化
开始前说明下我这里使用的开发环境是 IDEA+Gradle+Netty4,固然你使用 Eclipse 和 Maven 都是能够的,而后在 Gradle 的 build 文件中添加依赖 compile 'io.netty:netty-all:4.1.26.Final',这样就能够编写咱们的 Netty 程序了,正如在前面介绍 Netty 特性中提到的,Netty 不须要额外的依赖。服务器
第一个示例咱们使用 Netty 编写一个 Http 服务器的程序,启动服务咱们在浏览器输入网址来访问咱们的服务,便会获得服务端的响应。功能很简单,下面咱们看看具体怎么作?网络
首先编写服务启动类
public class HttpServer { public static void main(String[] args) { //构造两个线程组 EventLoopGroup bossrGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { //服务端启动辅助类 ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new HttpServerInitializer()); ChannelFuture future = bootstrap.bind(8080).sync(); //等待服务端口关闭 future.channel().closeFuture().sync(); } catch (InterruptedException e) { e.printStackTrace(); }finally { // 优雅退出,释放线程池资源 bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } }
在编写 Netty 程序时,一开始都会生成 NioEventLoopGroup 的两个实例,分别是 bossGroup 和 workerGroup,也能够称为 parentGroup 和 childGroup,为何建立这两个实例,做用是什么?能够这么理解,bossGroup 和 workerGroup 是两个线程池, 它们默认线程数为 CPU 核心数乘以 2,bossGroup 用于接收客户端传过来的请求,接收到请求后将后续操做交由 workerGroup 处理。
在这里我向你们推荐一个架构学习交流群。交流学习群号:747981058 里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。
接下来咱们生成了一个服务启动辅助类的实例 bootstrap,boostrap 用来为 Netty 程序的启动组装配置一些必需要组件,例如上面的建立的两个线程组。channel 方法用于指定服务器端监听套接字通道 NioServerSocketChannel,其内部管理了一个 Java NIO 中的ServerSocketChannel实例。
channelHandler 方法用于设置业务职责链,责任链是咱们下面要编写的,责任链具体是什么,它其实就是由一个个的 ChannelHandler 串联而成,造成的链式结构。正是这一个个的 ChannelHandler 帮咱们完成了要处理的事情。
接着咱们调用了 bootstrap 的 bind 方法将服务绑定到 8080 端口上,bind 方法内部会执行端口绑定等一系列操,使得前面的配置都各就各位各司其职,sync 方法用于阻塞当前 Thread,一直到端口绑定操做完成。接下来一句是应用程序将会阻塞等待直到服务器的 Channel 关闭。
启动类的编写大致就是这样了,下面要编写的就是上面提到的责任链了。如何构建一个链,在 Netty 中很简单,不须要咱们作太多,代码以下:
public class HttpServerInitializer extends ChannelInitializer<SocketChannel> { protected void initChannel(SocketChannel sc) throws Exception { ChannelPipeline pipeline = sc.pipeline(); //处理http消息的编解码 pipeline.addLast("httpServerCodec", new HttpServerCodec()); //添加自定义的ChannelHandler pipeline.addLast("httpServerHandler", new HttpServerHandler()); } }
咱们自定义一个类 HttpServerInitializer 继承 ChannelInitializer 并实现其中的 initChannel方法。
ChannelInitializer 继承 ChannelInboundHandlerAdapter,用于初始化 Channel 的 ChannelPipeline。经过 initChannel 方法参数 sc 获得 ChannelPipeline 的一个实例。
当一个新的链接被接受时, 一个新的 Channel 将被建立,同时它会被自动地分配到它专属的 ChannelPipeline。
ChannelPipeline 提供了 ChannelHandler 链的容器,推荐读者仔细本身看看 ChannelPipeline 的 Javadoc,文章后面也会继续说明 ChannelPipeline 的内容。
Netty 是一个高性能网络通讯框架,同时它也是比较底层的框架,想要 Netty 支持 Http(超文本传输协议),必需要给它提供相应的编解码器。
因此咱们这里使用 Netty 自带的 Http 编解码组件 HttpServerCodec 对通讯数据进行编解码,HttpServerCodec 是 HttpRequestDecoder 和 HttpResponseEncoder 的组合,由于在处理 Http 请求时这两个类是常用的,因此 Netty 直接将他们合并在一块儿更加方便使用。因此对于上面的代码:
pipeline.addLast("httpServerCodec", new HttpServerCodec())
咱们替换成以下两行也是能够的。
pipeline.addLast("httpResponseEndcoder", new HttpResponseEncoder()); pipeline.addLast("HttpRequestDecoder", new HttpRequestDecoder());
经过 addLast 方法将一个一个的 ChannelHandler 添加到责任链上并给它们取个名称(不取也能够,Netty 会给它个默认名称),这样就造成了链式结构。在请求进来或者响应出去时都会通过链上这些 ChannelHandler 的处理。
最后再向链上加入咱们自定义的 ChannelHandler 组件,处理自定义的业务逻辑。下面就是咱们自定义的 ChannelHandler。
public class HttpServerChannelHandler0 extends SimpleChannelInboundHandler<HttpObject> { private HttpRequest request; @Override protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception { if (msg instanceof HttpRequest) { request = (HttpRequest) msg; request.method(); String uri = request.uri(); System.out.println("Uri:" + uri); } if (msg instanceof HttpContent) { HttpContent content = (HttpContent) msg; ByteBuf buf = content.content(); System.out.println(buf.toString(io.netty.util.CharsetUtil.UTF_8)); ByteBuf byteBuf = Unpooled.copiedBuffer("hello world", CharsetUtil.UTF_8); FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, byteBuf); response.headers().add(HttpHeaderNames.CONTENT_TYPE, "text/plain"); response.headers().add(HttpHeaderNames.CONTENT_LENGTH, byteBuf.readableBytes()); ctx.writeAndFlush(response); } } }
至此一个简单的 Http 服务器就完成了。首先咱们来看看效果怎样,咱们运行 HttpServer 中的 main 方法。让后使用 Postman 这个工具来测试下,使用 post 请求方式(也能够 get,但没有请求体),并一个 json 格式数据做为请求体发送给服务端,服务端返回给咱们一个hello world字符串。
服务端控制台打印以下:
对于自定义的 ChannelHandler, 通常会继承 Netty 提供的SimpleChannelInboundHandler类,而且对于 Http 请求咱们能够给它设置泛型参数为 HttpOjbect 类,而后覆写 channelRead0 方法,在 channelRead0 方法中编写咱们的业务逻辑代码,此方法会在接收到服务器数据后被系统调用。
Netty 的设计中把 Http 请求分为了 HttpRequest 和 HttpContent 两个部分,HttpRequest 主要包含请求头、请求方法等信息,HttpContent 主要包含请求体的信息。
因此上面的代码咱们分两块来处理。在 HttpContent 部分,首先输出客户端传过来的字符,而后经过 Unpooled 提供的静态辅助方法来建立未池化的 ByteBuf 实例, Java NIO 提供了 ByteBuffer 做为它的字节容器,Netty 的 ByteBuffer 替代品是 ByteBuf。
接着构建一个 FullHttpResponse 的实例,并为它设置一些响应参数,最后经过 writeAndFlush 方法将它写回给客户端。
上面这样获取请求和消息体则至关不方便,Netty 又提供了另外一个类 FullHttpRequest,FullHttpRequest 包含请求的全部信息,它是一个接口,直接或者间接继承了 HttpRequest 和 HttpContent,它的实现类是 DefalutFullHttpRequest。
所以咱们能够修改自定义的 ChannelHandler 以下:
public class HttpServerChannelHandler extends SimpleChannelInboundHandler<FullHttpRequest> { protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception { ctx.channel().remoteAddress(); FullHttpRequest request = msg; System.out.println("请求方法名称:" + request.method().name()); System.out.println("uri:" + request.uri()); ByteBuf buf = request.content(); System.out.print(buf.toString(CharsetUtil.UTF_8)); ByteBuf byteBuf = Unpooled.copiedBuffer("hello world", CharsetUtil.UTF_8); FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, byteBuf); response.headers().add(HttpHeaderNames.CONTENT_TYPE, "text/plain"); response.headers().add(HttpHeaderNames.CONTENT_LENGTH, byteBuf.readableBytes()); ctx.writeAndFlush(response); } }
这样修改就能够了吗,若是你去启动程序运行看看,是会抛异常的。前面说过 Netty 是一个很底层的框架,对于将请求合并为一个 FullRequest 是须要代码实现的,然而这里咱们并不须要咱们本身动手去实现,Netty 为咱们提供了一个 HttpObjectAggregator 类,这个 ChannelHandler做用就是将请求转换为单一的 FullHttpReques。
因此在咱们的 ChannelPipeline 中添加一个 HttpObjectAggregator 的实例便可。
public class HttpServerInitializer extends ChannelInitializer<SocketChannel> { protected void initChannel(SocketChannel sc) { ChannelPipeline pipeline = sc.pipeline(); //处理http消息的编解码 pipeline.addLast("httpServerCodec", new HttpServerCodec()); pipeline.addLast("aggregator", new HttpObjectAggregator(65536)); //添加自定义的ChannelHandler pipeline.addLast("httpServerHandler", new HttpServerChannelHandler0()); } }
启动程序运行,一切都顺畅了,好了,这个简单 Http 的例子就 OK 了。
在这里我向你们推荐一个架构学习交流群。交流学习群号:747981058 里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。
上面的两个示例中咱们都是以 Netty 作为服务端,接下来看看如何编写 Netty 客户端,以第一个 Http 服务的例子为基础,编写一个访问 Http 服务的客户端。
public class HttpClient { public static void main(String[] args) throws Exception { String host = "127.0.0.1"; int port = 8080; EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap b = new Bootstrap(); b.group(group) .channel(NioSocketChannel.class) .handler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new HttpClientCodec()); pipeline.addLast(new HttpObjectAggregator(65536)); pipeline.addLast(new HttpClientHandler()); } }); // 启动客户端. ChannelFuture f = b.connect(host, port).sync(); f.channel().closeFuture().sync(); } finally { group.shutdownGracefully(); } } }
客户端启动类编写基本和服务端相似,在客户端咱们只用到了一个线程池,服务端使用了两个,由于服务端要处理 n 条链接,而客户端相对来讲只处理一条,所以一个线程池足以。
而后服务端启动辅助类使用的是 ServerBootstrap,而客户端换成了 Bootstrap。经过 Bootstrap 组织一些必要的组件,为了方便,在 handler 方法中咱们使用匿名内部类的方式来构建 ChannelPipeline 链容器。最后经过 connect 方法链接服务端。
接着编写 HttpClientHandler 类。
public class HttpClientHandler extends SimpleChannelInboundHandler<FullHttpResponse> { @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { URI uri = new URI("http://127.0.0.1:8080"); String msg = "Are you ok?"; FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, uri.toASCIIString(), Unpooled.wrappedBuffer(msg.getBytes("UTF-8"))); // 构建http请求 // request.headers().set(HttpHeaderNames.HOST, "127.0.0.1"); // request.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE); request.headers().set(HttpHeaderNames.CONTENT_LENGTH, request.content().readableBytes()); // 发送http请求 ctx.channel().writeAndFlush(request); } @Override public void channelRead0(ChannelHandlerContext ctx, FullHttpResponse msg) { FullHttpResponse response = msg; response.headers().get(HttpHeaderNames.CONTENT_TYPE); ByteBuf buf = response.content(); System.out.println(buf.toString(io.netty.util.CharsetUtil.UTF_8)); } }
在 HttpClientHandler 类中,咱们覆写了 channelActive 方法,当链接创建时,此方法会被调用,咱们在方法中构建了一个 FullHttpRequest 对象,而且经过 writeAndFlush 方法将请求发送出去。
channelRead0 方法用于处理服务端返回给咱们的响应,打印服务端返回给客户端的信息。至此,Netty 客户端的编写就完成了,咱们先开启服务端,而后开启客户端就能够看到效果了。
但愿经过前面介绍的几个例子能让你们基本知道如何编写 Netty 客户端和服务端,下面咱们来讲说 Netty 程序为何是这样编写的,这也是 Netty 中最为重要的一部分知识,可让你在编写 netty 程序时作到心中有数。
在这里我向你们推荐一个架构学习交流群。交流学习群号:747981058 里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。
在编写 Netty 程序时,常常跟咱们打交道的是上面这几个对象,这也是 Netty 中几个重要的对象,下面咱们来看看它们之间有什么样的关系。
Netty 中的 Channel 是框架本身定义的一个通道接口,Netty 实现的客户端 NIO 套接字通道是 NioSocketChannel,提供的服务器端 NIO 套接字通道是 NioServerSocketChannel。
当服务端和客户端创建一个新的链接时, 一个新的 Channel 将被建立,同时它会被自动地分配到它专属的 ChannelPipeline。
ChannelPipeline 是一个拦截流经 Channel 的入站和出站事件的 ChannelHandler 实例链,并定义了用于在该链上传播入站和出站事件流的 API。那么就很容易看出这些 ChannelHandler 之间的交互是组成一个应用程序数据和事件处理逻辑的核心。
上图描述了 IO 事件如何被一个 ChannelPipeline 的 ChannelHandler 处理的。
ChannelHandler分为 ChannelInBoundHandler 和 ChannelOutboundHandler 两种,若是一个入站 IO 事件被触发,这个事件会从第一个开始依次经过 ChannelPipeline中的 ChannelInBoundHandler,先添加的先执行。
如果一个出站 I/O 事件,则会从最后一个开始依次经过 ChannelPipeline 中的 ChannelOutboundHandler,后添加的先执行,而后经过调用在 ChannelHandlerContext 中定义的事件传播方法传递给最近的 ChannelHandler。
在 ChannelPipeline 传播事件时,它会测试 ChannelPipeline 中的下一个 ChannelHandler 的类型是否和事件的运动方向相匹配。
若是某个ChannelHandler不能处理则会跳过,并将事件传递到下一个ChannelHandler,直到它找到和该事件所指望的方向相匹配的为止。
假设咱们建立下面这样一个 pipeline:
ChannelPipeline p = ...; p.addLast("1", new InboundHandlerA()); p.addLast("2", new InboundHandlerB()); p.addLast("3", new OutboundHandlerA()); p.addLast("4", new OutboundHandlerB()); p.addLast("5", new InboundOutboundHandlerX());
在上面示例代码中,inbound 开头的 handler 意味着它是一个ChannelInBoundHandler。outbound 开头的 handler 意味着它是一个 ChannelOutboundHandler。
当一个事件进入 inbound 时 handler 的顺序是 1,2,3,4,5;当一个事件进入 outbound 时,handler 的顺序是 5,4,3,2,1。在这个最高准则下,ChannelPipeline 跳过特定 ChannelHandler 的处理:
ChannelHandler 能够经过添加、删除或者替换其余的 ChannelHandler 来实时地修改 ChannelPipeline 的布局。
(它也能够将它本身从 ChannelPipeline 中移除。)这是 ChannelHandler 最重要的能力之一。
ChannelHandlerContext 表明了 ChannelHandler 和 ChannelPipeline 之间的关联,每当有 ChannelHandler 添加到 ChannelPipeline 中时,都会建立 ChannelHandlerContext。
ChannelHandlerContext 的主要功能是管理它所关联的 ChannelHandler 和在同一个 ChannelPipeline 中的其余 ChannelHandler 之间的交互。事件从一个 ChannelHandler 到下一个 ChannelHandler 的移动是由 ChannelHandlerContext 上的调用完成的。
可是有些时候不但愿老是从 ChannelPipeline 的第一个 ChannelHandler 开始事件,咱们但愿从一个特定的 ChannelHandler 开始处理。
你必须引用于此 ChannelHandler 的前一个 ChannelHandler 关联的 ChannelHandlerContext,利用它调用与自身关联的 ChannelHandler 的下一个 ChannelHandler。
以下:
ChannelHandlerContext ctx = ...; // 得到 ChannelHandlerContext引用 // write()将会把缓冲区发送到下一个ChannelHandler ctx.write(Unpooled.copiedBuffer("Netty in Action", CharsetUtil.UTF_8)); //流经整个pipeline ctx.channel().write(Unpooled.copiedBuffer("Netty in Action", CharsetUtil.UTF_8));
若是咱们想有一些事件流所有经过 ChannelPipeline,有两个不一样的方法能够作到:
那为何你可能会须要在 ChannelPipeline 某个特定的位置开始传递事件呢?
在这里我向你们推荐一个架构学习交流群。交流学习群号:747981058 里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。
在前面的示例中咱们程序一开始都会生成两个 NioEventLoopGroup 的实例,为何须要这两个实例呢?这两个实例能够说是 Netty 程序的源头,其背后是由 Netty 线程模型决定的。
Netty 线程模型是典型的 Reactor 模型结构,其中经常使用的 Reactor 线程模型有三种,分别为:Reactor 单线程模型、Reactor 多线程模型和主从 Reactor 多线程模型。
而在 Netty 的线程模型并不是固定不变,经过在启动辅助类中建立不一样的 EventLoopGroup 实例并经过适当的参数配置,就能够支持上述三种 Reactor 线程模型。
Reactor 线程模型
Reactor 单线程模型指的是全部的 IO 操做都在同一个 NIO 线程上面完成。做为 NIO 服务端接收客户端的 TCP 链接,做为 NIO 客户端向服务端发起 TCP 链接,读取通讯对端的请求或向通讯对端发送消息请求或者应答消息。
因为 Reactor 模式使用的是异步非阻塞 IO,全部的 IO 操做都不会致使阻塞,理论上一个线程能够独立处理全部 IO 相关的操做。
Netty 使用单线程模型的的方式以下:
EventLoopGroup bossGroup = new NioEventLoopGroup(1); ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup) .channel(NioServerSocketChannel.class) ...
在实例化 NioEventLoopGroup 时,构造器参数是 1,表示 NioEventLoopGroup 的线程池大小是 1。而后接着咱们调用 b.group(bossGroup) 设置了服务器端的 EventLoopGroup,所以 bossGroup和 workerGroup 就是同一个 NioEventLoopGroup 了。
对于一些小容量应用场景,可使用单线程模型,可是对于高负载、大并发的应用却不合适,须要对该模型进行改进,演进为 Reactor 多线程模型。
Rector 多线程模型与单线程模型最大的区别就是有一组 NIO 线程处理 IO 操做。
在该模型中有专门一个 NIO 线程 -Acceptor 线程用于监听服务端,接收客户端的 TCP 链接请求;而 1 个 NIO 线程能够同时处理N条链路,可是 1 个链路只对应 1 个 NIO 线程,防止发生并发操做问题。
网络 IO 操做-读、写等由一个 NIO 线程池负责,线程池能够采用标准的 JDK 线程池实现,它包含一个任务队列和 N 个可用的线程,由这些 NIO 线程负责消息的读取、解码、编码和发送。
Netty 中实现多线程模型的方式以下:
EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup(); ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) ...
bossGroup 中只有一个线程,而 workerGroup 中的线程是 CPU 核心数乘以 2,那么就对应 Recator 的多线程模型。
在并发极高的状况单独一个 Acceptor 线程可能会存在性能不足问题,为了解决性能问题,产生主从 Reactor 多线程模型。
主从 Reactor 线程模型的特色是:服务端用于接收客户端链接的再也不是 1 个单独的 NIO 线程,而是一个独立的 NIO 线程池。
Acceptor 接收到客户端 TCP 链接请求处理完成后,将新建立的 SocketChannel 注册到 IO 线程池(sub reactor 线程池)的某个 IO 线程上,由它负责 SocketChannel 的读写和编解码工做。
Acceptor 线程池仅仅只用于客户端的登录、握手和安全认证,一旦链路创建成功,就将链路注册到后端 subReactor 线程池的 IO 线程上,由 IO 线程负责后续的 IO 操做。
根据前面所讲的两个线程模型,很容想到 Netty 实现多线程的方式以下:
EventLoopGroup bossGroup = new NioEventLoopGroup(4); EventLoopGroup workerGroup = new NioEventLoopGroup(); ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) ...
可是,在 Netty 的服务器端的 acceptor 阶段,没有使用到多线程, 所以上面的主从多线程模型在 Netty 的实现是有误的。
服务器端的 ServerSocketChannel 只绑定到了 bossGroup 中的一个线程,所以在调用 Java NIO 的 Selector.select 处理客户端的链接请求时,其实是在一个线程中的,因此对只有一个服务的应用来讲,bossGroup 设置多个线程是没有什么做用的,反而还会形成资源浪费。
至于 Netty 中的 bossGroup 为何使用线程池,我在 stackoverflow 找到一个对于此问题的讨论 。
the creator of Netty says multiple boss threads are useful if we share NioEventLoopGroup between different server bootstraps
EventLoopGroup 和 EventLoop
当系统在运行过程当中,若是频繁的进行线程上下文切换,会带来额外的性能损耗。多线程并发执行某个业务流程,业务开发者还须要时刻对线程安全保持警戒,哪些数据可能会被并发修改,如何保护?这不只下降了开发效率,也会带来额外的性能损耗。
为了解决上述问题,Netty采用了串行化设计理念,从消息的读取、编码以及后续 ChannelHandler 的执行,始终都由 IO 线程 EventLoop 负责,这就意外着整个流程不会进行线程上下文的切换,数据也不会面临被并发修改的风险。
EventLoopGroup 是一组 EventLoop 的抽象,一个 EventLoopGroup 当中会包含一个或多个 EventLoop,EventLoopGroup 提供 next 接口,能够从一组 EventLoop 里面按照必定规则获取其中一个 EventLoop 来处理任务。
在 Netty 服务器端编程中咱们须要 BossEventLoopGroup 和 WorkerEventLoopGroup 两个 EventLoopGroup 来进行工做。
BossEventLoopGroup 一般是一个单线程的 EventLoop,EventLoop 维护着一个注册了 ServerSocketChannel 的 Selector 实例,EventLoop 的实现涵盖 IO 事件的分离,和分发(Dispatcher),EventLoop 的实现充当 Reactor 模式中的分发(Dispatcher)的角色。
因此一般能够将 BossEventLoopGroup 的线程数参数为 1。
BossEventLoop 只负责处理链接,故开销很是小,链接到来,立刻按照策略将 SocketChannel 转发给 WorkerEventLoopGroup,WorkerEventLoopGroup 会由 next 选择其中一个 EventLoop 来将这 个SocketChannel 注册到其维护的 Selector 并对其后续的 IO 事件进行处理。
ChannelPipeline 中的每个 ChannelHandler 都是经过它的 EventLoop(I/O 线程)来处理传递给它的事件的。因此相当重要的是不要阻塞这个线程,由于这会对总体的 I/O 处理产生严重的负面影响。但有时可能须要与那些使用阻塞 API 的遗留代码进行交互。
对于这种状况, ChannelPipeline 有一些接受一个 EventExecutorGroup 的 add() 方法。若是一个事件被传递给一个自定义的 EventExecutorGroup, DefaultEventExecutorGroup 的默认实现。
就是在把 ChannelHanders 添加到 ChannelPipeline 的时候,指定一个 EventExecutorGroup,ChannelHandler 中全部的方法都将会在这个指定的 EventExecutorGroup 中运行。
static final EventExecutor group = new DefaultEventExecutorGroup(16); ... ChannelPipeline p = ch.pipeline(); pipeline.addLast(group, "handler", new MyChannelHandler());