netty学习笔记
netty使用
netty工作模型图
工作架构图如下:
- Netty 抽象出两组线程池:BossGroup 和 WorkerGroup,也可以叫做 BossNioEventLoopGroup 和 WorkerNioEventLoopGroup。每个线程池中都有 NioEventLoop 线程。BossGroup 中的线程专门负责和客户端建立连接,WorkerGroup 中的线程专门负责处理连接上的读写。BossGroup 和 WorkerGroup 的类型都是 NioEventLoopGroup。
- NioEventLoopGroup 相当于一个事件循环组,这个组中含有多个事件循环,每个事件循环就是一个 NioEventLoop。
- NioEventLoop 表示一个不断循环的执行事件处理的线程,每个 NioEventLoop 都包含一个 Selector,用于监听注册在其上的 Socket 网络连接(Channel)。
- NioEventLoopGroup 可以含有多个线程,即可以含有多个 NioEventLoop。
- 每个 BossNioEventLoop 中循环执行以下三个步骤:
- select:轮训注册在其上的 ServerSocketChannel 的 accept 事件(OP_ACCEPT 事件)
- processSelectedKeys:处理 accept 事件,与客户端建立连接,生成一个 NioSocketChannel,并将其注册到某个 WorkerNioEventLoop 上的 Selector 上
- runAllTasks:再去以此循环处理任务队列中的其他任务
- 每个 WorkerNioEventLoop 中循环执行以下三个步骤:
- select:轮训注册在其上的 NioSocketChannel 的 read/write 事件(OP_READ/OP_WRITE 事件)
- processSelectedKeys:在对应的 NioSocketChannel 上处理 read/write 事件
- runAllTasks:再去以此循环处理任务队列中的其他任务
- 在以上两个processSelectedKeys步骤中,会使用 Pipeline(管道),Pipeline 中引用了 Channel,即通过 Pipeline 可以获取到对应的 Channel,Pipeline 中维护了很多的处理器(拦截处理器、过滤处理器、自定义处理器等)。这里暂时不详细展开讲解 Pipeline。
创建BossGroup 和 WorkerGroup
根据上面的介绍,可以知道BossGroup 和 WorkerGroup 的类型都是 NioEventLoopGroup。
NioEventLoopGroup创建 默认的线程数目为CPU核数的2倍。
//构造两个线程组
EventLoopGroup bossrGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
BossGroup 中的线程专门负责和客户端建立连接,WorkerGroup 中的线程专门负责处理连接上的读写。BossGroup中accept到Channel会注册到workerGroup中线程的selector。
服务端启动器ServerBootstrap
创建
要启动netty服务端,就需要依靠netty提供的服务端启动类ServerBootstrap。同过对启动类传递需要的参数就可以以配置的参数启动netty应用了。ServerBootstrap的用的是链式编程的设计实现,使用我们等会使用的时候会链式调用。
//服务端启动类
ServerBootstrap bootstrap = new ServerBootstrap();
根据官方的API文档,我们可以得知有两个启动类,分别是:
- bootstrap (客户端使用的)
- serverbootstrap(服务端使用的)
AbstractBootstrap主要方法:
方法 | 说明 |
---|---|
group() | 用来指定线程模型bossrGroup和workerGroup |
channel() | 用来指定 IO 模型,如NioServerSocketChannel |
handler() | 用来指定服务端通道需要处理的业务逻辑 |
childHandler() | 用来指定客户端通道需要处理的业务逻辑 |
attr() | 给服务端通道绑定自定义属性 |
childAttr() | 给客户端通道绑定自定义属性 |
option() | 给服务端通道设置配置 |
childOption() | 给客户端通道设置配置 |
bind() | 用来绑定端口号 |
使用示例
这里先忽略HttpServerInitializer,这个是自定义的类,后面会讲到。这里的childHandler主要用于传递要处理业务逻辑的调度器类。当服务器和客户端建立连接获取的SocketChannel如何使用就关系到ChannelHandler的编写。
根据上面的工作模型图可以得知,当WorkerNioEventLoop通过select选择到的processSelectedKeys(即在对应的 SocketChannel 上处理 read/write 事件),如果处理这个事件,就是ChannelHandler的工作了。
bootstrap.group(bossGroup, workerGroup) //指定bossGroup, workerGroup
.channel(NioServerSocketChannel.class) //指定IO模型,有BIO,NIO,AIO
.childHandler(new HttpServerInitializer()); //传递ChannelHandler接口的实现类,以处理客户端SocketChannel的业务逻辑
ChannelFuture future = bootstrap.bind(8080).sync();//绑定一个8080端口
Channel、ChannelPipeline、ChannelHandler 之间的关系
Channel 是一个连接通道,客户端和服务端连接成功之后,会维持一个 Channel,可以通过 Channel 来发送数据。Channel 有且仅有一个 ChannelPipeline 与之相对应,ChannelPipeline 又维护着一个由多个 ChannelHandlerContext 组成的双向链表,ChannelHandlerContext 又关联着一个 ChannelHandler。
ChannelPipeline
通过ChannelPipeline,我们可以往里面放ChannelHandler,ChannelHandlerContext 又关联着一个 ChannelHandler。
ChannelPipeline主要方法:
方法 | 描述 |
---|---|
addFirst(…) | 添加 ChannelHandler 在 ChannelPipeline 的第一个位置 |
addBefore(…) | 在 ChannelPipeline 中指定的 ChannelHandler 名称之前添加 ChannelHandler |
addAfter(…) | 在 ChannelPipeline 中指定的 ChannelHandler 名称之后添加 ChannelHandler |
addLast(…) | 在 ChannelPipeline 的末尾添加 ChannelHandler |
remove(…) | 删除 ChannelPipeline 中指定的 ChannelHandler |
replace(…) | 替换 ChannelPipeline 中指定的 ChannelHandler |
ChannelHandler first() | 获取链表当中的第一个节点 |
ChannelHandler last() | 获取链表当中的最后一个节点 |
使用
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap
.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
protected void initChannel(NioSocketChannel ch) {
ch.pipeline().addLast(new InboundHandler1());
ch.pipeline().addLast(new InboundHandler2());
}
});
public class InboundHandler1 extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("inbound1在channelRead时刻进行处理");
//1.往下传递
super.channelRead(ctx, msg);
}
}
public class InboundHandler2 extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("inbound2在channelRead时刻进行处理");
}
}
ChannelinboundHandler 和 ChanneloutboundHandler
inboundHandler主要处理入站流量,而outboundHandler处理出战操作。
- ChannelinboundHandler接口的适配类主要有:ChannelInboundHandlerAdapter
- ChanneloutboundHandler接口的适配类主要有:ChannelOutboundHandlerAdapter
入站事件和出站事件在一个双向链表中,入站事件会从链表head往后传递到最后一个入站的handler,出站事件会从链表tail往前传递到最前一个出站的handler,两种类型的handler互不干扰。
ChannelHandler接口
Netty提供了很多ChannelHandler接口的实现类供外部继承使用,通过这些实现类,我们就可以在获取相关信息以处理我们的业务逻辑。
实现了ChannelHandler接口的Java类有许多,如:ChannelInitializer,ChannelInboundHandlerAdapter,ChannelOutboundHandlerAdapter,HttpServerCodec等
ChannelInitializer
public class HttpServerInitializer extends ChannelInitializer<SocketChannel> {
protected void initChannel(SocketChannel sc) throws Exception {
ChannelPipeline pipeline = sc.pipeline();
//处理http消息的编解码,HttpServerCodec类是官方提供的对http的[code-decode]的实现类
pipeline.addLast("httpServerCodec", new HttpServerCodec());
//添加自定义的ChannelHandler
pipeline.addLast("httpServerHandler", new HttpServerHandler());
}
}
ChannelInboundHandlerAdapter
public class InboundHandler1 extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("inbound1在channelRead时刻进行处理");
//1.往下传递
super.channelRead(ctx, msg);
}
}
主要:入站事件和出站事件在一个双向链表中,入站事件会从链表head往后传递到最后一个入站的handler,出站事件会从链表tail往前传递到最前一个出站的handler,两种类型的handler互不干扰。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)