从一段代码开始说起
- Java NIO实现
public class demo{
public void socket() {
// 使用 socket 提供的 ServerSocketChannel建立一个 通信通道 channel
ServerSocketChannel server = ServerSocketChannel.open();
// 进行 IP 端口绑定,默认本地IP
server.bind(new InetSocketAddress("port"));
// 手动设置非阻塞
server.configurationBlocking(false);
// 打开选择器 可以开始接收请求了
Selector selector = Selector.open();
// ON_ACCEPT :设置为可接收请求状态
server.register(this.selector, SelectionKey.OP_ACCEPT);
// 使用轮询的方法 处理请求
while (true) {
/* 多路复用体现
* 多路:select();方法可以接收多个TCP连接请求
* 复用:复用一个主线程去处理多个请求
*/
// 接收所有请求 select()方法会阻塞,直到拿到一个新的key
selector.select();
// 拿到请求
Set<SelectionKey> selectionKeys = this.selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
// 处理请求.....
while (iterator.hasNext()) {
// 每一个key代表一种状态
SelectionKey key = iterator.next();
// 每一个key都有一个通道去处理
key.channel();
// to do
}
}
}
}
Netty的做法
- 通过复习java NIO的实现,对NIO有了更深一层的记忆
- Netty是基于Java的一个高性能的RPC框架
- Netty提供了如 零拷贝、内存池、无锁串行化设计理念、Reactor线程模型 等高性能操作
- 回归正题:Netty既然是基于Java实现的,那么利用Netty编程和使用Java实现NIO必然有异曲同工之处
Netty代码示例
- 说实话,属实不知道怎么去搞懂它
- 不过记住一件事,这里做的事情和java NIO做的事情是一样的,只不过性能更优
- 其实从 serverBootstrap.bind() 方法也可以看出,是绑定端口的操作
从Channel()开始学起
- Java NIO中有 ServerSocketChannel.open() 建立一个channel对象
- 那么Netty中必然也有Channel对象,从代码中也可以窥见一二
- Netty的 Channel.java
- 部分 API
- 连接到socket网络或者组件进行IO操作、读、写、绑定或连接
- A channel providers a user : 用来提供Channel对象的
- 通过其很多的实现类可以了解到,Netty.Channel针对不同的连接协议提供了支持
Channel的创建
- 从代码入手,本例 完成了 对 NioServerSocketChannel的初始化
ServerBootStrap 提供了 channel() 方法 其本身是由 Netty 提供用来完成Netty客户端/服务端初始化操作的
channel方法 返回了 一个工厂对象
- 通过跟踪代码,channel初始化操作是由 serverBootstrap.bind(port) 完成
- 交给 channelFactory 工厂来创建
- 最终通过反射进行 Channel() 对象的创建
- 随后调用 NioServerSocketChannel 的构造方法 NioServerSocketChannel 在 调用Channel()方法阶段设置
private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider();
- 接下来就是通过父类的层层调用—直到 AbstractChannel 的构造器
- 至此,以 NioServerSocketChannel 为例的 Channel初始化就完成了
总结
- NioServerSocketChannel 构造方法中,使用 newSocket 方法新建 一个 ServerSocketChannel
- 需要初始化的属性在 AbstractChannel 中完成,并 为每一个channel设置了一个唯一的id
- 在执行AcstractChannel构造器的时候同时新建了一个unsafe对象并完成了pipeline对象的实例化
- 在代码编程方面
- 我们可以理解到,ServerBootstrap 完成了初始化预设参数
- 在调用 bind 方法的时候完成注册和端口绑定
Unsafe的初始化
- 在 Channel 的创建过程中,在父类 AbstractChannel 的构造方法中调用 newUnsafe() 方法来获取一个unsafe实例
- 了解一下 Unsafe 接口所提供的方法
interface Unsafe {
RecvByteBufAllocator.Handle recvBufAllocHandle();
SocketAddress localAddress();
SocketAddress remoteAddress();
void register(EventLoop eventLoop, ChannelPromise promise);
void bind(SocketAddress localAddress, ChannelPromise promise);
void connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise);
void disconnect(ChannelPromise promise);
void close(ChannelPromise promise);
void closeForcibly();
void deregister(ChannelPromise promise);
void beginRead();
void write(Object msg, ChannelPromise promise);
void flush();
ChannelPromise voidPromise();
ChannelOutboundBuffer outboundBuffer();
}
- Netty提供的Unsafe类对Java底层Socket的操作进行了封装,是Netty和Java底层**”沟通“**的重要桥梁
- Unsafe类以内部类的方式实现,同时避免了上层业务的调用
Pipeline初始化
- 在ChannelPipline的注释中有这样一句话
- 在上文Channel初始化过程中,AbstractChannel构造器中完成了对Pipline的初始化
- 传入 Channel 对象作为初始化参数,进行绑定
- 对Pipline暂时先了解到这里
关于EventLoop
-
最开始的时候我们创建了两个NioEventLoopGroup对象
- 通过其构造方法可以发现其构造器中可传入线程数量、线程工厂对象、线程池对象进行初始化
- 通过其构造方法可以发现其构造器中可传入线程数量、线程工厂对象、线程池对象进行初始化
-
通过跟踪代码了解其构造器参数的意义
- 首先Neyyt通过io.netty.eventLoopThreads从系统属性中获取值
- 默认值为处理器核心数*2
-
最终会调用到 MultithreadEventExecutorGroup 的构造方法
protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
EventExecutorChooserFactory chooserFactory, Object... args) {
......
children = new EventExecutor[nThreads];
......
chooser = chooserFactory.newChooser(children);
......
}
-
这里的判断意思是:如果nThreads是2的幂,则使用PowerOfTwoEventExecutorChooser否则GenericEventExecutorChooser
-
两种 Chooser 都重写了 next() 方法
-
next()方法中的运算逻辑是数组索引循环位移
-
当索引移动到最后要给位置,再次调用next()方法将会使索引位置重新指向0
- 运算逻辑:每次索引自增后和数组长度取模 idx.getAndIncrement() % executors.length
- Netty优化算法:idx.getAndIncrement() & executors.length-1
- 注:在计算机底层&比%运算效率更高
总结
- EventLoop其实就是创建了一个大小为nThreads类型为EventExecutor的 childern 数组,构成了一个线程池
- 当我们实例化NioEventLoopGroup的时候,如果指定线程池大小,则 nThreads 就是指定的值,反之为处理器核心数*2
Channel注册到Seclector
- 当Channel在 initAndRegister() 方法中注册的时候还会将初始化完成的Channel注册到NioEventloop的Selector中
// initAndRegister()方法
ChannelFuture regFuture = config().group().register(channel);
- 其调用关系如下
// MultithreadEventLoopGroup.java
public ChannelFuture register(Channel channel) {
return next().register(channel);
}
// SingleThreadEventLoop.java
public ChannelFuture register(Channel channel) {
return register(new DefaultChannelPromise(channel, this));
}
// AbstractChannel.java
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
AbstractChannel.this.eventLoop = eventLoop;
register0(promise);
}
private void register0(ChannelPromise promise) {
doRegister();
}
- 到这里,实现了SocketChannel注册到EventLoop对象的selector上
- 注册流程总结
- 本质上所做的事情就是建channel与对应的EventLoop关联。关联完成,把Channel注册到EcentLoop对象中的selector中
如何添加Handler
- 在开始时有这样的代码,这里体现了Netty的Handler机制
.group().channel().childHandler
- 从官方偷来一段注释瞧瞧
- 处理IO事件或拦截IO操作,然后交给其ChannelPipline去处理
Handles an I/O event or intercepts an I/O operation, and forwards it to its next handler in its
{@link ChannelPipeline}.
- 简单理解就是很强大还支持扩展。针对不同场景使用不同的Handler,先了解到这里
通过这次代码分析流程,有助于后续学习Netty