前言
本博客讲述的是Netty是如何绑定端口、启动服务。启动服务的过程中你将会了解到Netty各大核心组件
GitHub地址:https://github.com/RobertoHuang
服务端启动DEMO
先从一个简单的服务端启动DEMO开始,以下是一个标准的Netyy服务端代码
public final class NettyServer {
public static void main(String[] args) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel channel) {
ChannelPipeline channelPipeline = channel.pipeline();
channelPipeline.addLast("decoder", new StringDecoder());
channelPipeline.addLast("encoder", new StringEncoder());
channelPipeline.addLast("handler", new ServerHandler());
}
});
ChannelFuture channelFuture = serverBootstrap.bind(8888).sync();
channelFuture.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
注:ServerBootstrap.childHandler()用于指定处理新连接数据的读写处理逻辑,同时ServerBootstrap还提供handler()用于指定在服务端启动过程中的一些逻辑,通常情况下我们用不着这个方法
ServerHandler代码如下:
public class ServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) {
System.out.println("channelActive");
}
@Override
public void channelRegistered(ChannelHandlerContext ctx) {
System.out.println("channelRegistered");
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) {
System.out.println("handlerAdded");
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelReadComplete");
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelInactive");
}
@Override
public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("service receive msg:" + msg);
}
}
当有新连接接入时,控制台打印出
handlerAdded
channelRegistered
channelActive
但接收到新消息时,控制台打印出
service receive msg:xxx
channelReadComplete
本文主要分析服务端的启动过程,而新连接接入 新消息的读取会在后续章节中说明
服务端启动源码分析
ServerBootstrap是Netty为方便开发者使用而设计的一个启动类,ServerBootstrap的核心代码入口在bind(),代码如下
public ChannelFuture bind(int inetPort) {
return bind(new InetSocketAddress(inetPort));
}
通过端口号创建一个InetSocketAddress,然后继续bind
public ChannelFuture bind(SocketAddress localAddress) {
// 校验参数
validate();
if (localAddress == null) {
throw new NullPointerException("localAddress");
}
// Channel绑定逻辑
return doBind(localAddress);
}
validate()验证服务启动需要的必要参数,然后调用doBind()
private ChannelFuture doBind(final SocketAddress localAddress) {
//...
final ChannelFuture regFuture = initAndRegister();
//...
final Channel channel = regFuture.channel();
//...
doBind0(regFuture, channel, localAddress, promise);
//...
return promise;
}
在doBind()中我们关注两个核心方法,initAndRegister()以及doBind0()
initAndRegister
final ChannelFuture initAndRegister() {
Channel channel = null;
// 新建Channel
channel = channelFactory.newChannel();
// 初始化Channel
init(channel);
// 将这个Channel Register到某个对象
ChannelFuture regFuture = config().group().register(channel);
return regFuture;
}
新建Channel
Channel是Netty的核心概念之一,它是Netty网络通信的主体由它负责同对端进行网络通信、注册和数据操作等功能。Channel的创建是由channelFactory.newChannel()完成的,ChannelFactory接口定义如下
public interface ChannelFactory<T extends Channel> {
T newChannel();
}
接下来我们跟踪此处的channelFactory是在何时被初始化,我们层层回溯最终发现是在这个函数中
public B channel(Class<? extends C> channelClass) {
if (channelClass == null) {
throw new NullPointerException("channelClass");
}
return channelFactory(new ReflectiveChannelFactory<C>(channelClass));
}
在Demo程序调用.channel(NioServerSocketChannel.class)方法,所以channelFactory.newChannel()真正执行代码如下
public class ReflectiveChannelFactory<T extends Channel> implements ChannelFactory<T> {
private final Class<? extends T> clazz;
public ReflectiveChannelFactory(Class<? extends T> clazz) {
if (clazz == null) {
throw new NullPointerException("clazz");
}
this.clazz = clazz;
}
@Override
public T newChannel() {
try {
return clazz.newInstance();
} catch (Throwable t) {
throw new ChannelException("Unable to create Channel from class " + clazz, t);
}
}
}
即在Netty服务端启动的时候通过反射的方式来创建一个NioServerSocketChannel对象,最终创建Channel相当于调用默认构造函数new出一个 NioServerSocketChannel对象,接下来我们继续跟进NioServerSocketChannel的默认构造函数
public NioServerSocketChannel() {
this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}
private static ServerSocketChannel newSocket(SelectorProvider provider) {
try {
// 利用SelectorProvider产生一个ServerSocketChannel对象
return provider.openServerSocketChannel();
} catch (IOException e) {
throw new ChannelException("Failed to open a server socket.", e);
}
}
通过newSocket(DEFAULT_SELECTOR_PROVIDER)创建一条server端channel,然后进入到以下方法
public NioServerSocketChannel(ServerSocketChannel channel) {
super(null, channel, SelectionKey.OP_ACCEPT);
config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}
该方法主要完成两个功能,首先是调用父类的构造方法然后初始化NioServerSocketChannelConfig属性。我们继续跟入super()
protected AbstractNioMessageChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
super(parent, ch, readInterestOp);
}
protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
super(parent);
this.ch = ch;
this.readInterestOp = readInterestOp;
ch.configureBlocking(false);
}
protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
super(parent);
this.ch = ch;
// 设置SelectionKey.OP_ACCEPT事件
this.readInterestOp = readInterestOp;
// 设置ServerSocketChannel为非阻塞的
ch.configureBlocking(false);
}
这里将前面通过provider.openServerSocketChannel()创建出来的ServerSocketChannel保存到成员变量,然后调用将该channel为非阻塞模式,这是个标准的JDK NIO编程的玩法。这里的readInterestOp即前面层层传入的SelectionKey.OP_ACCEPT,接下来继续跟进super(parent);(这里的parent其实是null,由前面写死传入)
protected AbstractChannel(Channel parent) {
this.parent = parent;
id = newId();
unsafe = newUnsafe();
pipeline = newChannelPipeline();
}
在AbstractChannel的构造方法中主要是初始化了id,unsafe,pipeline属性
初始化Channel
在创建完Channel后,我们在init方法中对Channel进行初始化操作,代码如下
void init(Channel channel) throws Exception {
// 设置option
final Map<ChannelOption<?>, Object> options = options0();
synchronized (options) {
channel.config().setOptions(options);
}
// 设置attr
final Map<AttributeKey<?>, Object> attrs = attrs0();
synchronized (attrs) {
for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) {
@SuppressWarnings("unchecked")
AttributeKey<Object> key = (AttributeKey<Object>) e.getKey();
channel.attr(key).set(e.getValue());
}
}
// 设置handler到pipeline上
ChannelPipeline p = channel.pipeline();
final EventLoopGroup currentChildGroup = childGroup;
final ChannelHandler currentChildHandler = childHandler;
final Entry<ChannelOption<?>, Object>[] currentChildOptions;
final Entry<AttributeKey<?>, Object>[] currentChildAttrs;
synchronized (childOptions) {
// 设置新接入channel的options
currentChildOptions = childOptions.entrySet().toArray(newOptionArray(childOptions.size()));
}
synchronized (childAttrs) {
// 设置新接入channel的attrs
currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(childAttrs.size()));
}
p.addLast(new ChannelInitializer<Channel>() {
@Override
public void initChannel(Channel ch) throws Exception {
final ChannelPipeline pipeline = ch.pipeline();
// 这里的handler()返回的就是.handler()所设置的值
ChannelHandler handler = config.handler();
if (handler != null) {
pipeline.addLast(handler);
}
ch.eventLoop().execute(new Runnable() {
@Override
public void run() {
// p.addLast()向serverChannel的流水线处理器中加入了一个ServerBootstrapAcceptor
// 从名字上就可以看出来这是一个接入器,专门接受新请求,把新的请求扔给某个事件循环器
pipeline.addLast(new ServerBootstrapAcceptor(currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
}
});
}
});
}
以上代码主要完成如下功能:先调用options0()以及attrs0()获取到服务器启动时设置的一些参数,然后将得到的options和attrs注入到channelConfig或者channel中;然后在当前Channel的pipeline中添加了一个ChannelInitializer,在ChannelInitializer中往pipeline中添加了一个handler,并通过NioEventLoop.execute()方法往pipeline中添加了一个ServerBootstrapAcceptor(请求接入器),此处的NioEventLoop.execute()方法为Netty Reactor线程执行的入口,关于Netty Reactor线程我们将在下一篇博客中介绍。我们总结一下发现代码执行到这里Netty并未真正启动服务,只是初始化了一些基本的配置和属性,以及在pipeline上加入了一个接入器用来专门接受新连接
完成Channel注册
完成Channel注册的代码如下
ChannelFuture regFuture = config().group().register(channel);
它调用到MultithreadEventLoopGroup中的register方法
@Override
public ChannelFuture register(Channel channel) {
// 调用了NioEvenLoop对象中的register方法
// EventLoopGroup extends SingleThreadEventLoop
return next().register(channel);
}
在next方法中返回一个EventLoop对象,每一个EventLoop都与一个selector绑定,在之前的代码中EventLoop中的Selector一直没有任何Channel注册,所以每次select操作都是空,但从这行代码开始这个selector中开始有Channel注册
public ChannelFuture register(Channel channel) {
return register(new DefaultChannelPromise(channel, this));
}
public ChannelFuture register(final ChannelPromise promise) {
ObjectUtil.checkNotNull(promise, "promise");
promise.channel().unsafe().register(this, promise);
return promise;
}
这里可以看到register操作是委托给Channel中的Unsafe对象来执行的,这里的Unsafe对象对上文稍有印象的同学应该能知道这个就是创建NioServerSocketChannel的时候创建的Unsafe对象,继续跟进Unsafe对象的register方法
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
// ...
AbstractChannel.this.eventLoop = eventLoop;
// ...
register0(promise);
}
先将EventLoop事件循环器绑定到该NioServerSocketChannel上,然后调用register0()代码如下
private void register0(ChannelPromise promise) {
try {
boolean firstRegistration = neverRegistered;
doRegister();
neverRegistered = false;
registered = true;
pipeline.invokeHandlerAddedIfNeeded();
safeSetSuccess(promise);
pipeline.fireChannelRegistered();
if (isActive()) {
if (firstRegistration) {
pipeline.fireChannelActive();
} else if (config().isAutoRead()) {
beginRead();
}
}
} catch (Throwable t) {
closeForcibly();
closeFuture.setClosed();
safeSetFailure(promise, t);
}
}
这一段其实也很清晰,先调用doRegister()进行注册
protected void doRegister() throws Exception {
boolean selected = false;
for (;;) {
try {
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
return;
} catch (CancelledKeyException e) {
// ...
}
}
}
在这里我们终于看到JDK底层的Channel注册到Selector的过程,但是这里的OPS为0即不关心任何事件,而我们期望OPS的值为SelectionKey.OP_ACCEPT,所以到了这里代码还没有结束。在执行完Channel注册后接着执行了几个pipeline相关的方法,我们后面详细剖析pipeline的时候再讲,然后我们回到doBind0方法中
doBind0
private static void doBind0(final ChannelFuture regFuture, final Channel channel, final SocketAddress localAddress, final ChannelPromise promise) {
channel.eventLoop().execute(new Runnable() {
@Override
public void run() {
if (regFuture.isSuccess()) {
channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
} else {
promise.setFailure(regFuture.cause());
}
}
});
}
在dobind0()方法中通过EventLoop执行一个任务,接下来我们进入到channel.bind()方法
public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
return pipeline.bind(localAddress, promise);
}
public final ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
return tail.bind(localAddress, promise);
}
关于Pipeline相关的内容将在后续博客中介绍,当前一个比较好的方式就是Debug单步进入。最后我们来到了如下区域
public void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception {
unsafe.bind(localAddress, promise);
}
这里的unsafe就是前面提到的AbstractUnsafe, 准确点应该是NioMessageUnsafe
@Override
public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
// ...
boolean wasActive = isActive();
// ...
doBind(localAddress);
if (!wasActive && isActive()) {
invokeLater(new Runnable() {
@Override
public void run() {
pipeline.fireChannelActive();
}
});
}
safeSetSuccess(promise);
}
在doBind方法中完成绑定操作,代码如下
protected void doBind(SocketAddress localAddress) throws Exception {
if (PlatformDependent.javaVersion() >= 7) {
javaChannel().bind(localAddress, config.getBacklog());
} else {
javaChannel().socket().bind(localAddress, config.getBacklog());
}
}
最终调用到了JDK里面的bind方法真正进行了端口的绑定。按照正常流程我们前面已经分析到isActive()方法返回false,进入到 doBind()之后如果channel被激活了,就发起pipeline.fireChannelActive()调用。接着我们跟进pipeline.channelActive方法
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelActive();
readIfIsAutoRead();
}
pipeline.channelActive会逐一调用pipeline中每一个节点的channelActive方法,并且在HeadContext中调用了readIfIsAutoRead
private void readIfIsAutoRead() {
if (channel.config().isAutoRead()) {
channel.read();
}
}
最终这个方法会调用到AbstractNioChannel的doBeginRead方法
protected void doBeginRead() throws Exception {
final SelectionKey selectionKey = this.selectionKey;
if (!selectionKey.isValid()) {
return;
}
readPending = true;
final int interestOps = selectionKey.interestOps();
if ((interestOps & readInterestOp) == 0) {
selectionKey.interestOps(interestOps | readInterestOp);
}
}
在最后一行中的readInterestOp即在上文中提到的SelectionKey.OP_ACCEPT,至此完成了Channel对ACCEPT事件的注册过程
总结
到目前为止我们看到的代码相当于传统NIO编程中的如下代码
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); => 创建NioServerSocketChannel
serverSocketChannel.configureBlocking(false); => AbstractNioChannel中ch.configureBlocking(false);
serverSocketChannel.bind(new InetSocketAddress("localhost", 8888)); => NioServerSocketChannel.doBind()
Selector selector = Selector.open(); => NioEventLoop.openSelector()
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT) => AbstractNioChannel.doBeginRead()
服务端启动完成的主要功能为创建一个Channel,并且将Channel注册到NioEventLoop的Selector上