就因为上述原因,所以JBoss基于NIO开发了Netty。
官网:https://netty.io/
Netty is an asynchronous event-driven network application framework
for rapid development of maintainable high performance protocol servers & clients
Netty 对 JDK 自带的 NIO 的 API 进行了封装,解决了上述NIO所带的问题。
不同的线程模式,对程序的性能有很大影响,为了搞清 Netty 线程模式,我们来系统的讲解下 各个线程模式
目前存在的线程模型有:
Netty 主要基于主从 Reactor 多线程模型做了一定的改进,其中主从 Reactor 多线程模型有多 个 Reactor
传统阻塞 I/O 服务模型工作原理如下图所示:
其中:黄色的框表示对象, 蓝色的框表示线程,白色的框表示方法(API)
传统阻塞 I/O 服务模型特点:
传统阻塞 I/O 服务模型存在的问题:
Reactor 模式有多种翻译:1. 反应器模式 2. 分发者模式(Dispatcher)3. 通知者模式(notifier)
针对上面所述的传统阻塞 I/O 服务模型的 2 个缺点,Reactor提出了下面的解决方案:
说明:
Reactor 模式中 核心组成:
根据 Reactor 的数量和处理资源池线程的数量不同,有 3 种典型的实现,下面将分别进行介绍:
Reactor 模式具有如下的优点:
原理图:
原理说明:
前面的 NIO 群聊系统就是单 Reactor 单线程模式
方案优缺点分析:
使用场景:客户端的数量有限,业务处理非常快速,比如 Redis 在业务处理的时间复杂度 O(1) 的情况
方案优缺点分析:
针对单 Reactor 多线程模型中,Reactor 在单线程中运行,高并发场景下容易成为性能瓶颈,可以让 Reactor 在多线程中运行
工作原理图 :
原理说明:
Reactor 主线程 MainReactor 对象通过 select 监听连接事件, 收到事件后,通过 Acceptor 处理连接事件
当 Acceptor 处理连接事件后,MainReactor 将连接分配给 SubReactor(有多个)
SubReactor 将连接加入到连接队列进行监听,并创建 handler 进行各种事件处理
当有新事件发生时, subreactor 就会调用对应的 handler 处理
handler 先read 读取数据,然后分发给后面的 worker 线程池处理
worker 线程池分配独立的 worker 线程进行业务处理,并返回结果
handler 收到响应的结果后,再通过 send 将结果返回给 client
Reactor 主线程可以对应多个 Reactor 子线程, 即 MainRecator 可以关联多个 SubReactor
这种模型在许多项目中广泛使用,包括 Nginx 主从 Reactor 多进程模型,Memcached 主从多线程, Netty 主从多线程模型的支持
Netty 主要基于主从 Reactors 多线程模型(如图)做了一定的改进,其中主从 Reactor 多线程模型有多个 Reactor
Netty 抽象出两组线程池: BossGroup 专门负责接收客户端的连接、WorkerGroup 专门负责网络的读写
BossGroup 和 WorkerGroup 类型都是 NioEventLoopGroup 。NioEventLoopGroup 相当于一个事件循环组, 这个组中含有多个事件循环 ,每一个事件循环是 NioEventLoop
NioEventLoop 表示一个不断循环的执行处理任务的线程, 每个 NioEventLoop 都有一个 selector, 用于监听绑 定在其上的 socket 的网络通讯
每个 BossNioEventLoop 循环执行的步骤有 3 步
每个 WorkerNIOEventLoop 循环执行的步骤
每个WorkerNIOEventLoop 处理业务时,会使用pipeline(管道),pipeline 中包含了 channel, 即通过pipeline 可以获取到对应通道, 管道中维护了很多的 处理器
Netty 服务器在 6666 端口监听,客户端能发送消息给服务器 “hello, 服务器”。服务器可以回复消息给客户端 “hello, 客户端”
注意运行前需要使用Maven导入Netty的包或者依赖
服务器端代码:
public class NettyServer {
public static void main(String[] args) throws InterruptedException {
//创建 bossGroup 和 workerGroup
//bossGroup 只处理连接请求,workerGroup 处理客户端业务
//两个都是无限循环
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
//创建一个服务器端启动对象,并设置参数
ServerBootstrap serverBootstrap = new ServerBootstrap();
//使用链式编程来设置启动器
serverBootstrap.group(bossGroup, workerGroup) //设置两个线程组
.channel(NioServerSocketChannel.class)//设置使用NioServerSocketChannel作为服务器的通道实现
.option(ChannelOption.SO_BACKLOG, 128) //设置线程队列可以得到的连接个数
.childOption(ChannelOption.SO_KEEPALIVE, true) //设置保持活动连接状态
.childHandler(new ChannelInitializer<SocketChannel>() { //创建一个管道测试对象
//给pipeline设置处理器
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//在管道中加入自定义的处理器(NettyServerHandler)
ch.pipeline().addLast(new NettyServerHandler());
}
}); //给workerGroup 的 EventLoop 对应的管道设置处理器
System.out.println("服务器 is ready");
//绑定端口并同步,生成一个 ChannelFuture对象
//启动服务器
ChannelFuture cf = serverBootstrap.bind(6666).sync();
//对关闭通道进行监听
cf.channel().closeFuture().sync();
} finally {
//关闭线程组
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
NettyServerHandler:
/**
* 该类用于实际处理数据,需要继承Netty规定好的HandlerAdapter
*
*/
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
/**
* 用于读取(接收)数据
* @param ctx 上下文对象,其中包含了管道pipeline,通道channel,地址等信息
* @param msg 客户端发送的信息
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("server ctx = " + ctx);
//将msg转为一个ByteBuf对象
//ByteBuf是 Netty提供的,与NIO的ByteBuffer不同
ByteBuf buf = (ByteBuf) msg;
System.out.println("客户端发送的信息是:" + buf.toString(CharsetUtil.UTF_8));
System.out.println("客户端地址为:" + ctx.channel().remoteAddress());
}
/**
* 定义数据读取完毕后的操作
* @param ctx
* @throws Exception
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
//writeAndFlush作用的是将数据写到缓冲区并发送
//需要对发送的数据进行编码
ctx.writeAndFlush(Unpooled.copiedBuffer("hello,客户端", CharsetUtil.UTF_8));
}
/**
* 处理异常,一般是关闭通道
* @param ctx
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
客户端代码:
public class NettyClient {
public static void main(String[] args) throws InterruptedException {
//创建事件组
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
try {
//创建客户端启动对象
//注意客户端是Bootstrap,不是ServerBootstrap
Bootstrap bootstrap = new Bootstrap();
//设置相关的参数
bootstrap.group(eventLoopGroup) //设置线程组
.channel(NioSocketChannel.class) //设置客户端通道的实现类(反射)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new NettyClientHandler());
}
});
System.out.println("客户端 is ok");
//启动客户端连接服务器端,异步
ChannelFuture sync = bootstrap.connect(new InetSocketAddress("127.0.0.1", 6666)).sync();
//对通道关闭进行监听
sync.channel().closeFuture().sync();
} finally {
eventLoopGroup.shutdownGracefully();
}
}
}
NettyClientHandler:
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
//当通道就绪就会触发
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("client ctx : " + ctx);
//向服务器端发送消息
ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 服务器", CharsetUtil.UTF_8));
}
//接收服务端发送的消息
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
System.out.println("服务器回复:" + buf.toString(CharsetUtil.UTF_8));
System.out.println("服务器地址:" + ctx.channel().remoteAddress());
}
//处理异常
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
运行结果:
服务端输出:
服务器 is ready
server ctx = ChannelHandlerContext(NettyServerHandler#0, [id: 0x67909ee8, L:/127.0.0.1:6666 - R:/127.0.0.1:3991])
客户端发送的信息是:hello, 服务器
客户端地址为:/127.0.0.1:3991
客户端输出:
客户端 is ok
client ctx : ChannelHandlerContext(NettyClientHandler#0, [id: 0xabaaab32, L:/127.0.0.1:3991 - R:/127.0.0.1:6666])
服务器回复:hello,客户端
服务器地址:/127.0.0.1:6666
注意:
对于服务器端的 bossGroup 和 workerGroup 分别对应模型中的两个NioEventLoopGroup。其中每个NioEventLoopGroup下面NioEventLoop的数目(线程数目)默认为CPU的核数 * 2
。如果需要设定,可以直接在构造函数传入,如代码第一行
//EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
CPU的核数可以通过下面的代码查看,我的是4核,而创建的NioEventLoop就是8个
System.out.println(NettyRuntime.availableProcessors());
可以在debug中看到从看到,如下图。同时也可以看出多个NioEventLoop是使用EventExecutor管理的。
任务队列中的 Task 有 3 种典型使用场景
示例:
/**
* 该类用于实际处理数据,需要继承Netty规定好的HandlerAdapter
*
*/
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
/**
* 用于读取(接收)数据
* @param ctx 上下文对象,其中包含了管道pipeline,通道channel,地址等信息
* @param msg 客户端发送的信息
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//比如这里我们有一个非常耗时长的业务-> 异步执行
// -> 提交该 channel 对应的 NIOEventLoop 的 taskQueue 中,
ctx.channel().eventLoop().execute(()->{
try {
Thread.sleep(5000);
ctx.writeAndFlush(Unpooled.copiedBuffer("hello 客户端-1", CharsetUtil.UTF_8));
} catch (InterruptedException e) {
e.printStackTrace();
}
});
//这个任务和上面的任务放到同一个eventLoop的taskQueue
//所以这两个任务是串行执行的
ctx.channel().eventLoop().execute(()->{
try {
Thread.sleep(10000);
ctx.writeAndFlush(Unpooled.copiedBuffer("hello 客户端-2", CharsetUtil.UTF_8));
} catch (InterruptedException e) {
e.printStackTrace();
}
});
//解决方案 2: 用户自定义定时任务 -》 该任务是提交到 scheduledTaskQueue 中
ctx.channel().eventLoop().schedule(()->{
try {
Thread.sleep(10000);
ctx.writeAndFlush(Unpooled.copiedBuffer("hello 客户端-2", CharsetUtil.UTF_8));
} catch (InterruptedException e) {
e.printStackTrace();
}
}, 5, TimeUnit.SECONDS);
}
}
Netty 服务器在 8888 端口监听,浏览器发出请求 http://localhost:8888/
服务器可以回复消息给客户端 “Hello,客户端”, 并对特定请求资源进行过滤.
实例代码:
NettyHttpServer(Http服务器)
public class NettyHttpServer {
public static void main(String[] args) throws InterruptedException {
//创建 bossGroup 和 workerGroup
//bossGroup 只处理连接请求,workerGroup 处理客户端业务
//两个都是无限循环
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
//创建一个服务器端启动对象,并设置参数
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new NettyHttpServerInitializer());
ChannelFuture channelFuture = serverBootstrap.bind(8888).sync();
System.out.println("服务端 is ready");
channelFuture.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
NettyHttpServerInitializer(Channel初始化器),可以向ChannelPipeline加入handler
public class NettyHttpServerInitializer extends ChannelInitializer<SocketChannel> {
//向管道加入处理器
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//得到管道
ChannelPipeline pipeline = ch.pipeline();
//加入netty提供的httpServerCodec =》(code + decode)
//HttpServerCodec 是netty提供的http编码-解码器
pipeline.addLast(new HttpServerCodec());
//添加自定义handler
pipeline.addLast(new NettyHttpServerHandler());
}
}
NettyHttpServerHandler(用于处理Http请求)
/**
* 说明:
* SimpleChannelInboundHandler 是 ChannelInboundHandlerAdapter 的子类
* HttpObject :客户端和服务器端通信的数据被封装成 HttpObject
*/
public class NettyHttpServerHandler extends SimpleChannelInboundHandler<HttpObject> {
//读取客户端发来的数据
@Override
protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
//判断是否是HttpRequest
if (msg instanceof HttpRequest) {
//对图标资源获取进行过滤
HttpRequest request = (HttpRequest) msg;
URI uri = new URI(request.uri());
if ("/favicon.ico".equals(uri.getPath())) {
System.out.println("请求了 favicon.ico ,不做响应");
return;
}
System.out.println("pipeline = " + ctx.pipeline().hashCode() + " handlder = " + this.hashCode());
System.out.println("msg 类型:" + msg.getClass());
System.out.println("客户端地址:" + ctx.channel().remoteAddress());
//回复信息给浏览器(Http协议)
ByteBuf content = Unpooled.copiedBuffer("hello,客户端", CharsetUtil.UTF_8);
//构造http响应,即httpResponse
DefaultFullHttpResponse response = new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content);
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain;charset=utf-8");
response.headers().set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes());
//返回httpResponse
ctx.writeAndFlush(response);
}
}
}
运行服务器之后,在浏览器内输入地址,可以看到下面的结果:
服务器端控制台输出:
服务端 is ready
pipeline = 1328644476 handlder = 5644135
msg 类型:class io.netty.handler.codec.http.DefaultHttpRequest
客户端地址:/0:0:0:0:0:0:0:1:8247
请求了 favicon.ico ,不做响应
其中最后第5行的输出是浏览器获取图标的请求,被拦截。对应NettyHttpServerHandler类中第12~22行代码所对应的功能
另外http是连接不是长连接,每次发送请求都会重新建立连接,而且在Netty中每次处理都会生成pipeline和handlder,下面再次刷新可以看出:
服务端 is ready
pipeline = 1328644476 handlder = 5644135
msg 类型:class io.netty.handler.codec.http.DefaultHttpRequest
客户端地址:/0:0:0:0:0:0:0:1:8247
请求了 favicon.ico ,不做响应
pipeline = 99933460 handlder = 1089885134
msg 类型:class io.netty.handler.codec.http.DefaultHttpRequest
客户端地址:/0:0:0:0:0:0:0:1:8255
请求了 favicon.ico ,不做响应
Netty 抽象出两组线程池,BossGroup 专门负责接收客户端连接,WorkerGroup 专门负责网络读写操作。
NioEventLoop 表示一个不断循环执行处理任务的线程,每个 NioEventLoop 都有一个 selector,用于监听绑定 在其上的 socket 网络通道。
NioEventLoop 内部采用串行化设计(taskQueue),从消息的读取->解码->处理->编码->发送,始终由 IO 线程 NioEventLoop负责
其中:
异步的概念和同步相对。当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的组件在
完成后,通过状态、通知和回调来通知调用者。
Netty 中的 I/O 操作是异步的,包括 Bind、Write、Connect 等操作会简单的返回一个 ChannelFuture。
调用者并不能立刻获得结果,而是通过 Future-Listener 机制,用户可以方便的主动获取或者通过通知机制获得 IO 操作结果
Netty 的异步模型是建立在 future 和 callback 的之上的。callback 就是回调。重点说 Future,它的核心思想 是:假设一个方法 fun,计算过程可能非常耗时,等待 fun 返回显然不合适。那么可以在调用 fun 的时候,立 马返回一个 Future,后续可以通过 Future 去监控方法 fun 的处理过程(即 : Future-Listener 机制)
public interface ChannelFuture extends Future
我们可以添加监听器,当监听的事件发生时,就会通知到监听器常见有如下操作:
举例说明:绑定端口是异步操作,当绑定操作处理完,将会调用相应的监听器处理逻辑
//绑定端口并同步,生成一个 ChannelFuture对象
//启动服务器
ChannelFuture cf = serverBootstrap.bind(6666).sync();
//为ChannelFuture注册监听器,监听关心的事件
cf.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()){
System.out.println("服务器绑定 6666 端口成功");
} else {
System.out.println("服务器绑定 6666 端口失败");
}
}
});
Bootstrap 意思是引导,一个 Netty 应用通常由一个 Bootstrap 开始,主要作用是配置整个 Netty 程序,串联 各个组件,Netty 中 Bootstrap 类是客户端程序的启动引导类,ServerBootstrap 是服务端启动引导类
常见的方法有 :
//该方法用于服务器端,用来设置两个 EventLoop
public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup);
//该方法用于客户端,用来设置一个 EventLoop
public B group(EventLoopGroup group);
//该方法用来设置一个服务器端的通道实现
public B channel(Class<? extends C> channelClass);
//用来给 ServerChannel 添加配置
public <T> B option(ChannelOption<T> option, T value);
//用来给接收到的通道添加配置
public <T> ServerBootstrap childOption(ChannelOption<T> childOption, T value);
//该方法用来设置业务处理类(自定义的 handler)
public ServerBootstrap childHandler(ChannelHandler childHandler);
//该方法用于服务器端,用来设置占用的端口号
public ChannelFuture bind(int inetPort);
//该方法用于客户端,用来连接服务器端
public ChannelFuture connect(String inetHost, int inetPort)
Netty 中所有的 IO 操作都是异步的,不能立刻得知消息是否被正确处理。但是可以过一会等它执行完成或 者直接注册一个监听
具体的实现就是通过 Future 和 ChannelFutures,他们可以注册一个监听,当操作执行成功或失败时监听会自动触发注册的监听事件
常见的方法有 :
Channel channel();//返回当前正在进行 IO 操作的通道
ChannelFuture sync();//等待异步操作执行完毕
Channel 是Netty 网络通信的组件,能够用于执行网络 I/O 操作。
通过 Channel 可获得当前网络连接的通道的状态
通过 Channel 可获得 网络连接的配置参数 (例如接收缓冲区大小)
Channel 提供异步的网络 I/O 操作(如建立连接,读写,绑定端口),异步调用意味着任何 I/O 调用都将立即返 回,并且不保证在调用结束时所请求的 I/O 操作已完成
调用立即返回一个 ChannelFuture 实例,通过注册监听器到 ChannelFuture 上,可以 I/O 操作成功、失败或取消时回调通知调用方
支持关联 I/O 操作与对应的处理程序
不同协议、不同的阻塞类型的连接都有不同的 Channel 类型与之对应,常用的 Channel 类型:
Netty 基于 Selector 对象实现 I/O 多路复用,通过 Selector 一个线程可以监听多个连接的 Channel 事件。
当向一个 Selector 中注册 Channel 后,Selector 内部的机制就可以自动不断地查询(Select) 这些注册的 Channel 是否有已就绪的 I/O 事件(例如可读,可写,网络连接完成等),这样程序就可以很简单地使用一个 线程高效地管理多个 Channel
ChannelHandler 是一个接口,处理 I/O 事件或拦截 I/O 操作,并将其转发到其 ChannelPipeline(业务处理链)
中的下一个处理程序。
ChannelHandler 本身并没有提供很多方法,因为这个接口有许多的方法需要实现,方便使用期间,可以继承它
的子类
经常需要自定义一个 Handler 类去继承 ChannelInboundHandlerAdapter,然后通过重写相应方法实现业务
逻辑,我们接下来看看一般都需要重写哪些方法
public class ChannelInboundHandlerAdapter extends ChannelHandlerAdapter implements ChannelInboundHandler {
public ChannelInboundHandlerAdapter() {
}
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelRegistered();
}
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelUnregistered();
}
//通道就绪事件
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelActive();
}
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelInactive();
}
//通道读取数据事件
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ctx.fireChannelRead(msg);
}
//通道读取数据完毕事件
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelReadComplete();
}
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
ctx.fireUserEventTriggered(evt);
}
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelWritabilityChanged();
}
//通道发生异常事件
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.fireExceptionCaught(cause);
}
}
ChannelPipeline 是一个 Handler 的集合,它负责处理和拦截 inbound 或者 outbound 的事件和操作,相当于 一个贯穿 Netty 的链。(也可以这样理解:ChannelPipeline 是 保存 ChannelHandler 的 List,用于处理或拦截 Channel 的入站事件和出站操作)
ChannelPipeline 实现了一种高级形式的拦截过滤器模式,使用户可以完全控制事件的处理方式,以及 Channel 中各个的 ChannelHandler 如何相互交互
在 Netty 中每个 Channel 都有且仅有一个 ChannelPipeline 与之对应,它们的组成关系如下
常用方法:
ChannelPipeline addFirst(ChannelHandler... handlers)
:把一个业务处理类(handler)添加到链中的第一个位置ChannelPipeline addLast(ChannelHandler... handlers)
:把一个业务处理类(handler)添加到链中的最后一个位置保存 Channel 相关的所有上下文信息,同时关联一个 ChannelHandler 对象
ChannelHandlerContext 中 包 含 一 个 具 体 的 事 件 处 理 器 ChannelHandler , 同 时ChannelHandlerContext 中也绑定了对应的 pipeline 和 Channel 的信息,方便对 ChannelHandler进行调用.
常用方法:
ChannelFuture close()
:关闭通道ChannelOutboundInvoker flush()
:刷新ChannelFuture writeAndFlush(Object msg)
: 将 数据写到 ChannelPipeline 中当前ChannelHandler 的下一个 ChannelHandler 开始处理(出站)Netty 在创建 Channel 实例后,一般都需要设置 ChannelOption 参数。
ChannelOption 参数如下:
EventLoopGroup 是一组 EventLoop 的抽象,Netty 为了更好的利用多核 CPU 资源,一般会有多个 EventLoop 同时工作,每个 EventLoop 维护着一个 Selector 实例。
EventLoopGroup 提供 next 接口,可以从组里面按照一定规则获取其中一个 EventLoop来处理任务。
在 Netty 服务器端编程中,我们一般都需要提供两个 EventLoopGroup,例如:BossEventLoopGroup 和 WorkerEventLoopGroup。
通常一个服务端口即一个 ServerSocketChannel对应一个Selector 和一个EventLoop线程。BossEventLoop 负责接收客户端的连接并将 SocketChannel 交给 WorkerEventLoopGroup 来进行 IO 处理,如下图所示
常用方法:
public NioEventLoopGroup()
:构造方法public Future> shutdownGracefully()
:断开连接,关闭线程Unpooled 类是Netty 提供一个专门用来操作缓冲区(即Netty的数据容器,如ByteBuf)的工具类
常用方法:
////通过给定的数据和字符编码返回一个 ByteBuf 对象(类似于 NIO 中的 ByteBuffer 但有区别)
public static ByteBuf copiedBuffer(CharSequence string, Charset charset);
//使用
ByteBuf content = Unpooled.copiedBuffer("hello,客户端", CharsetUtil.UTF_8);
使用示例:
public class NettyByteBuf01 {
public static void main(String[] args) {
//创建一个ByteBuf
//说明
//1. 创建 对象,该对象包含一个数组arr , 是一个byte[10]
//2. 在netty 的buffer中,不需要使用flip 进行反转
// 底层维护了 readerindex 和 writerIndex
//3. 通过 readerindex 和 writerIndex 和 capacity, 将buffer分成三个区域
// 0---readerindex 已经读取的区域
// readerindex---writerIndex , 可读的区域
// writerIndex -- capacity, 可写的区域
ByteBuf buffer = Unpooled.buffer(10);
for(int i = 0; i < 10; i++) {
buffer.writeByte(i);
}
System.out.println("capacity=" + buffer.capacity());//10
//输出
// for(int i = 0; i
// System.out.println(buffer.getByte(i));
// }
for(int i = 0; i < buffer.capacity(); i++) {
System.out.println(buffer.readByte());
}
System.out.println("执行完毕");
}
}
public class NettyByteBuf02 {
public static void main(String[] args) {
//创建ByteBuf
ByteBuf byteBuf = Unpooled.copiedBuffer("hello,world!", Charset.forName("utf-8"));
//使用相关的方法
if(byteBuf.hasArray()) { // true
byte[] content = byteBuf.array();
//将 content 转成字符串
System.out.println(new String(content, Charset.forName("utf-8")));
System.out.println("byteBuf=" + byteBuf);
System.out.println(byteBuf.arrayOffset()); // 0
System.out.println(byteBuf.readerIndex()); // 0
System.out.println(byteBuf.writerIndex()); // 12
System.out.println(byteBuf.capacity()); // 36
//System.out.println(byteBuf.readByte()); //
System.out.println(byteBuf.getByte(0)); // 104
int len = byteBuf.readableBytes(); //可读的字节数 12
System.out.println("len=" + len);
//使用for取出各个字节
for(int i = 0; i < len; i++) {
System.out.println((char) byteBuf.getByte(i));
}
//按照某个范围读取
System.out.println(byteBuf.getCharSequence(0, 4, Charset.forName("utf-8")));
System.out.println(byteBuf.getCharSequence(4, 6, Charset.forName("utf-8")));
}
}
}