对于使用过Netty的程序员来说,下面这段代码应该非常的熟悉。
EventLoopGroup bossGroup = new NioEventLoopGroup(); // (1)
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap(); // (2)
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class) // (3)
.childHandler(new ChannelInitializer<SocketChannel>() { // (4)
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
ch.pipeline().addLast("decoder", new StringDecoder());
ch.pipeline().addLast("encoder", new StringEncoder());
}
})
.option(ChannelOption.SO_BACKLOG, 128) // (5)
.childOption(ChannelOption.SO_KEEPALIVE, true); // (6)
// 绑定端口,开始接收进来的连接
ChannelFuture f = b.bind(0).sync(); // (7)
// 等待服务器 socket 关闭 。
// 在这个例子中,这不会发生,但你可以优雅地关闭你的服务器。
f.channel().closeFuture().sync();
System.out.println("hello");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
这是一个典型的搭建一个Netty服务器的流程。我们都知道Netty底层是基于Java NIO,但是却对Java NIO做了非常丰富的扩展和包装,使得程序员能够很方便地搭建一个服务器。这篇文章将对上述过程进行一个总体的概述,并在接下来的系列文章中从源码出发,深入解析Netty的每一个模块。
一. 组件的概述
1. Channel
Channel是对一个网络套接字(network socket)的抽象(socket channel)或者一个可以进行IO操作的组件的抽象。Netty中的Channel的IO操作都是异步的,比较常用的Channel有NioServerSocketChannel(服务器的socket channel)和NioSocketChannel(客户端的socket channel)
2. EventLoopGroup和EventLoop
EventLoopGroup和EventLoop其实就相当于线程组和线程,EventLoop可以用来处理IO事件,也可以用来执行一个普通的Runnable或Scheduled Task。当服务器每接收到一个连接后,会将该连接保存为一个Channel,然后交由EventLoopGroup处理,EventLoopGroup会从他的线程组中选择一个EventLoop来负责该Channel后续的IO事件,并且每一个Channel都由唯一的一个EventLoop来处理(但一个EvenLoop可以负责处理多个Channel的IO事件)。
3. ServerBootStrap
ServerBootstrap很好理解,他是用来启动一个ServerSocketChannel。
通过该方法
public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup)
可以为一个ServerBootstrap设置两个EventLoopGroup:parentGroup和childGroup,其中parentGroup用于处理ServerSocketChannel的各种操作,例如bind,register;而当一个客户端连接到ServerSocketChannel上时,childGroup用于处理该连接的SocketChannel的IO事件,例如read,write。
4. ChannelPipeline和ChannelHandler
ChannelHandler用于处理一个IO事件或者拦截一个IO操作,而由多个ChannelHandler组成的队列即为一个ChannelPipeline,每个Channel都有自己的ChannelPipeline,当这个Channel触发一个IO事件或者执行一个IO操作的时候,该IO事件或IO操作会交给ChannelPipeline,pipeline上的每个handler可以对其进行处理或者传递给下一个handler(责任链模式)。
二. 服务器的启动和接收连接流程
1. 服务器的启动
服务器的启动逻辑主要在ServerBootstrap.bind()方法中,可以总结为以下几个主要步骤:
- 通过反射创建一个NioServerSocketChannel实例
- 给ServerSocketChannel的ChannelPipeline添加一个ChannelInitializer,这个ChannelInitializer会在Channel初始化的时候再给ChannelPipeline添加一个名为ServerBootstrapAcceptor的Handler,这个Handler就是专门用来处理客户端的连接请求。
- ServerSocketChannel通过Java NIO的方式将自己注册到Selector上,但注册的SelectionKey为0,也就是不对任何事件感兴趣。
- ServerSocketChannel触发ChannelPipeline上的所有Handler的handlerAdded事件,此时就会触发第2步中所添加的ChannelInitializer的handlerAdded方法,而ChannelInitializer的handlerAdded方法会调用initChannel方法,从而将ServerBootstrapAcceptor正式添加到ServerSocketChannel的ChannelPipeline中。
- ServerSocketChannel的ChannelPipeline触发一个channelRegistered事件,此时ChannelPipeline中的所有Handler的channelRegistered方法被依次调用。
- ServerSocketChannel的ChannelPipeline触发一个channelActive事件,此时ChannelPipeline中的所有Handler的channelActive方法被依次调用。
- ServerSocketChannel将注册到Selector上的SelectionKey改为OP_ACCEPT。
- ServerSocketChannel将自己绑定到某个InetSocketAddress上。至此,该ServerSocketChannel就可以正式接收来自客户端的连接了。
2. 客户端的连接
客户端的连接的逻辑主要在NioEventLoop.run()方法中,run()方法中是一个死循环,说明EventLoop会一直处理IO事件。可以总结为以下几个主要步骤:
- Selector调用select方法,判断是否有IO事件的发生(是否有连接请求)。
- 如果有连接请求,调用ServerSocketChannel的accept方法获取客户端的SocketChannel,并将该SocketChannel包装为一个NioSocketChannel。
- ServerSocketChannel触发一个ChannelRead事件,ChannelPipeline会将该事件进行传递,然后由ServerBootstrapAcceptor捕获(就是服务器启动流程的第2步中为ServerSocketChannel所添加的Handler)。
- ServerBootstrapAcceptor为客户端NioSocketChannel添加childHandler并设置childOption(就是代码中第4步和第6步中用户自己添加的childHandler和childOption)。
- ServerBootstrapAcceptor将客户端NioSocketChannel交由childEventLoopGroup处理,childEventLoopGroup会将该NioSocketChannel注册到Selector上,该NioSocketChannel后续的读写IO事件都将由childEventLoopGroup负责。
- ServerSocketChannel触发一个ChannelReadComplete事件。
如图:
上述过程是对服务器的启动以及客户端的连接的大致流程,在下一篇文章中,我将从源码出发,对上述过程进行详细地解析。