Netty服务端
- Netty客户端程序员启动需要使用 Bootstrap 对象
- 和客户端不同,服务端使用 ServerBootStrap 对象来启动
Server代码实现
- 通过和客户端代码实现对比,可以发现
- 服务端设置了两个EventLoopGroup对象,分别用于处理客户端连接请求和处理IO操作
- 与客户端实现不同 服务端使用 ServerBootstrap 来实现程序启动
- 使用 ServerBootstrap 的 bind 方法来启动服务
public void start(int port) {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
...
}
});
ChannelFuture f = b.bind(this.port).sync();
log.info("服务已启动,监听端口" + this.port);
f.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
- 服务端使用ServerBootstrap而非Bootstarp
- ServerBootstrap同样提供channel方法设置Channel Type
- 完成实例化操作的对象是 ReflectiveChannelFactory,通过反射调用到 NioServerSocketChannel 的构造方法
NioServerSocketChannel初始化和注册
- 客户端使用Channel Type为NioSocketChannel
- 与客户端不同的,服务端的入口是ServerBootstrap的bind方法,然后调用到父类的 doBind 方法完成 初始化和注册
- 与客户端不同的一点是,服务端传入参数是 SelectionKey.OP_ACCEPT ,表示服务启动后要监听客户端的连接请求
- 在服务端由于有两个EventLoopGroup,所以Channel的注册与客户端略有不同
- 在initAndRegister方法中,首先完成了bossGroup和NioServerSocketChannel的关联
- 接下来在init方法中完成了workerGroup和NioServerSocketChannel的关联
- 当一个客户端连接发送到服务端时,Java底层NIO的ServerSocketChannel会有已经初始化完成的 SelectionKey.OP_ACCEPT就绪
- 由NioServerSocketChannel的doReadMessages方法通过调用javaChannel().accept()方法获取建立连接的SocketChannel对象
bossGroup和workerGroup
- 客户端初始化流程的时候实例化了一个EventLoopGroup对象
- 而服务端实例化了两个EventLoopGroup对象,其工作模型如下
- 服务端bossGroup不断监听是否有客户端的连接,当拿到一个连接之后,bossGroup初始化各项资源
- 然后workerGroup中选出一个EventLoop与目标连接建立绑定
- 接下来与客户端的交互过程在分配的 EventLoop 中完成
Selector事件轮询
- 服务端启动由ServerBootstrap的bind()方法开始,调用到AbstractBootstrap的doBind方法
- doBind0 方法,这里调用了 与 channel关联的 EventLoop 的execute方法 AbstractBootstrap -> doBind0
- 接下来调用到 SingleThreadEventExecutor 的 execute 方法
- 这里主要做的事情就是创建线程,将线程添加到EventLoop的队列中去
- SingleThreadEventExecutor.this.run方法,这里的this实际上时EventLoop对象
- 这里实现了具体的轮询逻,体现了java NIO中的 select() 方法
protected void run() {
for (;;) {
try {
switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
case SelectStrategy.CONTINUE:
continue;
case SelectStrategy.SELECT:
select(wakenUp.getAndSet(false));
if (wakenUp.get()) {
selector.wakeup();
}
default:
}
cancelledKeys = 0;
needsToSelectAgain = false;
final int ioRatio = this.ioRatio;
if (ioRatio == 100) {
try {
processSelectedKeys();
} finally {
runAllTasks();
}
} else {
final long ioStartTime = System.nanoTime();
try {
processSelectedKeys();
} finally {
final long ioTime = System.nanoTime() - ioStartTime;
runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
}
}
} catch (Throwable t) {
handleLoopException(t);
}
try {
if (isShuttingDown()) {
closeAll();
if (confirmShutdown()) {
return;
}
}
} catch (Throwable t) {
handleLoopException(t);
}
}
}
本文总结
- Netty作为服务端程序启动与客户端的区别
- 服务端使用NioServerSocketChannel进行初始化和注册的基本过程
- boosGroup与workerGroup的区别及其工作模型
- 跟踪源码找到了相关代码,体现了selector的轮询机制