一、服务端启动
我们首先来看测试类,调试入口:io.netty.example.echo.EchoServer
public final class EchoServer {
static final boolean SSL = System.getProperty("ssl") != null;
static final int PORT = Integer.parseInt(System.getProperty("port", "8007"));
public static void main(String[] args) throws Exception {
// Configure SSL.
final SslContext sslCtx;
if (SSL) {
SelfSignedCertificate ssc = new SelfSignedCertificate();
sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build();
} else {
sslCtx = null;
}
// Configure the server.
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
final EchoServerHandler serverHandler = new EchoServerHandler();
try {
//创建服务端实例
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 100)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
//用于处理新创建的连接用的
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
if (sslCtx != null) {
p.addLast(sslCtx.newHandler(ch.alloc()));
}
//p.addLast(new LoggingHandler(LogLevel.INFO));
p.addLast(serverHandler);
}
});
// Start the server.
ChannelFuture f = b.bind(PORT).sync();
// Wait until the server socket is closed.
f.channel().closeFuture().sync();
} finally {
// Shut down all event loops to terminate all threads.
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
解释:
1、初始化了两个NioEventLoopGroup(可以理解为两个线程池),
1、其中一个是bossGroup:用于accept客户端的请求,并创建SocketChannel转发到workGroup
2、workGroup:用于处理真正的I/O操作等业务逻辑
接下来我们来看ServerBootstrap(服务端启动指引类),客户端的是Bootstrap后面再说
我们先看图大概了解一下工作原理
解释:
- 每个NioEventLoopGroup里边有多个NioEventLoop,一个NioEventLoop包含一个Selector和一个taskQueue
- NioEventLoop:就是一个线程,主要功能是将Client端的SocketChannel里的I/O操作绑定到Selector,Channel和Selector关系:1:N
- Selector:选择器,将I/O操作绑定到Selector后请求返回,Selector不断扫描准备好后进行通知
- taskQueue:将非I/O操作放入队列
- 服务启动时:
- bossGroup里的NioEventLoop让Selector监听Accept事件,并创建taskQueue
- workGroup里的NioEventLoop让Selector监听Read、Write等事件,并创建taskQueue
- Client发起请求后发生的3件事
- Selector不断循环扫描Accept事件
- 处理Accept事件,与客户端建立连接,并将I/O操作封装成SocketChannel发送到workGroup的NioEventLoop与Selector绑定;将非I/O操作放入taskQueue
- 处理taskQueue里的任务
- 请求发送到workGroup里时发生的3件事
- Selector不断扫描Read、Write等事件
- 处理事件并在NioSocketChannel可操作时发送ChannelPipeline(ChannelHandler链)进行处理
- 处理taskQueue任务
- Handler处理完毕后结果封装到future
我们来看启动时的代码分析
从上面b.bind(PORT)进入到AbstractBootstrap#bind(int inetPort);根据端口号生成SocketAddress(这里是本地启动所以没有host)
/**
* Create a new {@link Channel} and bind it.
*/
public ChannelFuture bind(int inetPort) {
return bind(new InetSocketAddress(inetPort));
}
然后进入doBind(finall SocketAddress localAddress)
private ChannelFuture doBind(final SocketAddress localAddress) {
//初始化并注册返回ChannelFuture
final ChannelFuture regFuture = initAndRegister();
final Channel channel = regFuture.channel();
if (regFuture.cause() != null) {
return regFuture;
}
if (regFuture.isDone()) {
// At this point we know that the registration was complete and successful.
//注册成功得到ChannelPromise(这也是一个Future)
ChannelPromise promise = channel.newPromise();
//端口绑定
doBind0(regFuture, channel, localAddress, promise);
return promise;
} else {
// Registration future is almost always fulfilled already, but just in case it's not.
final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
regFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
Throwable cause = future.cause();
if (cause != null) {
// Registration on the EventLoop failed so fail the ChannelPromise directly to not cause an
// IllegalStateException once we try to access the EventLoop of the Channel.
promise.setFailure(cause);
} else {
// Registration was successful, so set the correct executor to use.
// See https://github.com/netty/netty/issues/2586
promise.registered();
doBind0(regFuture, channel, localAddress, promise);
}
}
});
return promise;
}
}
解释:
1、初始化并注册返回ChannelFuture(因为注册时异步的所以返回Future)
2、绑定地址和ip
我们来看initAndRegister()
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
//Channel实例化
channel = channelFactory.newChannel();
//初始化Channel
init(channel);
} catch (Throwable t) {
if (channel != null) {
// channel can be null if newChannel crashed (eg SocketException("too many open files"))
channel.unsafe().closeForcibly();
// as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
}
// as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
return new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE).setFailure(t);
}
//注册
ChannelFuture regFuture = config().group().register(channel);
if (regFuture.cause() != null) {
if (channel.isRegistered()) {
channel.close();
} else {
channel.unsafe().closeForcibly();
}
}
// If we are here and the promise is not failed, it's one of the following cases:
// 1) If we attempted registration from the event loop, the registration has been completed at this point.
// i.e. It's safe to attempt bind() or connect() now because the channel has been registered.
// 2) If we attempted registration from the other thread, the registration request has been successfully
// added to the event loop's task queue for later execution.
// i.e. It's safe to attempt bind() or connect() now:
// because bind() or connect() will be executed *after* the scheduled registration task is executed
// because register(), bind(), and connect() are all bound to the same thread.
return regFuture;
}
解释:
1、实例化Channel
2、初始化
3、注册
我们冲newChannel()断电进入
/**
* Create a new instance using the given {@link ServerSocketChannel}.
* 使用给定的ServerSocketChannel穿件一个实例
* OP_ACCEPT:16:接收事件
*/
public NioServerSocketChannel(ServerSocketChannel channel) {
super(null, channel, SelectionKey.OP_ACCEPT);
config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}
我们进入super()
/**
* Create a new instance
*
* @param parent the parent {@link Channel} by which this instance was created. May be {@code null}
* @param ch the underlying {@link SelectableChannel} on which it operates
* @param readInterestOp the ops to set to receive data from the {@link SelectableChannel}
*/
protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
super(parent);
this.ch = ch;
this.readInterestOp = readInterestOp;
try {
//设置channel为非阻塞模式
ch.configureBlocking(false);
} catch (IOException e) {
try {
ch.close();
} catch (IOException e2) {
logger.warn(
"Failed to close a partially initialized socket.", e2);
}
throw new ChannelException("Failed to enter non-blocking mode.", e);
}
}
我们再进入super()
/**
* Creates a new instance.
*
* @param parent
* the parent of this channel. {@code null} if there's no parent.
*/
protected AbstractChannel(Channel parent) {
this.parent = parent;
id = newId();
unsafe = newUnsafe();
pipeline = newChannelPipeline();
}
我们看newChannelPipelin(),这个是不是就是我们上面说的ChannelHandler链
我们反回去看initAndRegister#init(channel)初始化操作
@Override
void init(Channel channel) {
//设置Channel可选操作集合
setChannelOptions(channel, options0().entrySet().toArray(newOptionArray(0)), logger);
//设置属性
setAttributes(channel, attrs0().entrySet().toArray(newAttrArray(0)));
//得到上步的pipeline
ChannelPipeline p = channel.pipeline();
final EventLoopGroup currentChildGroup = childGroup;
final ChannelHandler currentChildHandler = childHandler;
final Entry<ChannelOption<?>, Object>[] currentChildOptions =
childOptions.entrySet().toArray(newOptionArray(0));
final Entry<AttributeKey<?>, Object>[] currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(0));
p.addLast(new ChannelInitializer<Channel>() {
@Override
public void initChannel(final Channel ch) {
final ChannelPipeline pipeline = ch.pipeline();
ChannelHandler handler = config.handler();
//添加handler到pipeline
if (handler != null) {
pipeline.addLast(handler);
}
ch.eventLoop().execute(new Runnable() {
@Override
public void run() {
pipeline.addLast(new ServerBootstrapAcceptor(
ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
}
});
}
});
}
解释:初始化各种属性,将handler加入到pipeline中
我们再来看config().group().register()跟到里边
@Override
protected void doRegister() throws Exception {
boolean selected = false;
for (;;) {
try {
//原生的将Nio的channel对象注册到Selector上
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
return;
} catch (CancelledKeyException e) {
if (!selected) {
// Force the Selector to select now as the "canceled" SelectionKey may still be
// cached and not removed because no Select.select(..) operation was called yet.
eventLoop().selectNow();
selected = true;
} else {
// We forced a select operation on the selector before but the SelectionKey is still cached
// for whatever reason. JDK bug ?
throw e;
}
}
}
}
解释:到这一步Channel就已经初始化完毕,并且将自己注册到Selector
进入doBind()绑定端口,我们看AbstractChannel#bind(final SocketAddress localAddress,final ChannelPromise promise)
@Override
public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
assertEventLoop();
if (!promise.setUncancellable() || !ensureOpen(promise)) {
return;
}
// See: https://github.com/netty/netty/issues/576
if (Boolean.TRUE.equals(config().getOption(ChannelOption.SO_BROADCAST)) &&
localAddress instanceof InetSocketAddress &&
!((InetSocketAddress) localAddress).getAddress().isAnyLocalAddress() &&
!PlatformDependent.isWindows() && !PlatformDependent.maybeSuperUser()) {
// Warn a user about the fact that a non-root user can't receive a
// broadcast packet on *nix if the socket is bound on non-wildcard address.
logger.warn(
"A non-root user can't receive a broadcast packet if the socket " +
"is not bound to a wildcard address; binding to a non-wildcard " +
"address (" + localAddress + ") anyway as requested.");
}
boolean wasActive = isActive();
try {
//调用底层绑定ip端口
doBind(localAddress);
} catch (Throwable t) {
safeSetFailure(promise, t);
closeIfClosed();
return;
}
if (!wasActive && isActive()) {
//获取注册时生成的SelectionKey绑定真正感兴趣的accept事件
invokeLater(new Runnable() {
@Override
public void run() {
pipeline.fireChannelActive();
}
});
}
//设置future成功
safeSetSuccess(promise);
}
这里主要做了:
1、调用底层绑定ip端口;
2、获取注册时生产的SelectionKey绑定正在感兴趣的accept事件,这样就可以接受客户端请求了
3、设置返回结果
绑定直接看NioServerSocketChannel#doBind(SocketAddress localAddress)
@Override
protected void doBind(SocketAddress localAddress) throws Exception {
if (PlatformDependent.javaVersion() >= 7) {
javaChannel().bind(localAddress, config.getBacklog());
} else {
javaChannel().socket().bind(localAddress, config.getBacklog());
}
}
调用原始bind方法将ServerSocketChannel绑定ip端口,监听访问
接下来看绑定accept事件:pipeline.fireChannelActive()----->AbstractNioChannel#doBeginRead()
protected void doBeginRead() throws Exception {
// Channel.read() or ChannelHandlerContext.read() was called
final SelectionKey selectionKey = this.selectionKey;
if (!selectionKey.isValid()) {
return;
}
readPending = true;
//accept事件:16
final int interestOps = selectionKey.interestOps();
if ((interestOps & readInterestOp) == 0) {
//真正标注感兴趣操作accept事件
selectionKey.interestOps(interestOps | readInterestOp);
}
}
到此为止,
1、创建ServerSocketChannel并将其注册到Selector
2、绑定ip端口
3、将真正感兴趣的accept事件注册到Selector
4、设置future服务启动完毕
自我感觉理解太表面,异步代码太多,不好调试,大概应该是这个思路
总结:
- 创建boss、work线程池
- boss线程池:负责处理客户端的Accept请求,并转发到work线程池
- work线程池:负责处理IO操作
- 创建服务端启动类ServerBootstrap,设置属性(异步、线程池等),设置childhandler(用于处理接收新建的请求)
- 初始化SorcketChannel,获取channelPipeline,将默认handler放入Pipeline,并注册到Selector
- 将感兴趣的事件(Accept)绑定到selector
- 初始化NioServerSocketChannel,获取channelPipeline,将默认handler放入Pipeline,并注册到Selector
- 将NioServerSocketChannel绑定ip端口,监听服务
- 将感兴趣的事件(read,write)绑定到selector
公众号主要记录各种源码、面试题、微服务技术栈,帮忙关注一波,非常感谢