netty探索之旅四

上一篇我们研究了netty的客户端代码,这一篇我们研究一下服务端代码

以下是源码中服务端的启动代码
路径:example\src\main\java\io\netty\example\echo\EchoServer

public static void main(String[] args) throws Exception {
final SslContext sslCtx;
if (SSL) {
SelfSignedCertificate ssc = new SelfSignedCertificate();
sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build();
} else {
sslCtx = null;
}
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
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(new EchoServerHandler());
}
});

ChannelFuture f = b.bind(PORT).sync();
f.channel().closeFuture().sync();
} finally {
// Shut down all event loops to terminate all threads.
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}

和客户端的代码的层次比较相似。
NioEventLoopGroup:无论客户端还是服务端都需要指定。不过服务端中指定了两个NioEventLoopGroup,变量是bossGroup用于处理客户端的连接请求,变量workerGroup用于处理与各个客户端连接的IO操作。
Channel类型:因为是服务器端,所以是NioServerSocketChannel。

channel的初始化过程和客户端的大同小异。
同:结构基本一致
异:参数的类型不一样
客户端的Bootstrap和服务端的ServerBootstrap;客户端的NioSocketChannel和服务端的NioServerSocketChannel;

NioServerSocketChannel:

public NioServerSocketChannel() {
this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}


private static ServerSocketChannel newSocket(SelectorProvider provider) {
try {
return provider.openServerSocketChannel();
} catch (IOException e) {
throw new ChannelException(
"Failed to open a server socket.", e);
}
}

注意这里是openServerSocketChannel()。

this(newSocket(DEFAULT_SELECTOR_PROVIDER)):

public NioServerSocketChannel(ServerSocketChannel channel) {
super(null, channel, SelectionKey.OP_ACCEPT);
config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}

这里调用父类构造器时,传入的参数是SelectionKey.OP_ACCEPT。客户端的NioSocketChannel传入的参数是SelectionKey.OP_READ。看过我关于Reactor和Proactor模式的文章中,就知道NIO是一种Reactor模式,通过selector来实现I/O的多路复用,服务端开始时需要监听客户端的连接请求,因此在这里我们设置了SelectionKey.OP_ACCEPT,即通知selector我们对客户端的连接请求事件感兴趣。

同样在AbstractChannel中会实例化一个unsafe和pipeline:值得注意的是客户端的unsafe是一个AbstractNioByteChannel:NioByteUnsafe的实例。服务端的NioServerSocketChannel是继承的AbstractNioMessageChannel类,在此类中重写了newUnsafe方法,

@Override
protected AbstractNioUnsafe newUnsafe() {
return new NioMessageUnsafe();
}

服务器端,unsafe是AbstractNioMessageChannel.AbstractNioUnsafe的实例。

小总结一下:
1,NioServerSocketChannel.newSocket(DEFAULT_SELECTOR_PROVIDER)打开一个Java NIO ServerSocketChannel。
2,AbstractChannel(Channel parent)中初始化AbstractChannel属性:
parent 属性置为 null
unsafe 通过newUnsafe()实例化一个unsafe对象,类型是 AbstractNioMessageChannel.AbstractNioUnsafe
pipeline是new DefaultChannelPipeline(this)创建的实例.
3,AbstractNioChannel的属性:
SelectableChannel ch 被设置为ServerSocketChannel.
readInterestOp 被设置为 SelectionKey.OP_ACCEPT
SelectableChannel ch 被配置为非阻塞的 ch.configureBlocking(false)
4,NioServerSocketChannel的属性:
ServerSocketChannelConfig config = new NioServerSocketChannelConfig(this, javaChannel().socket())

ChannelPipeline初始化和channel的注册
这2块客户端和服务端过程是一样的。可以参考上一篇[url]http://jishuaige.iteye.com/admin/blogs/2356798[/url]。

NioEventLoopGroup
在客户端的时候,我们只提供了一个NioEventLoopGroup对象,而在服务端我们初始化了2个NioEventLoopGroup对象,一个bossGroup,一个workerGroup。
bossGroup:用于服务端处理客户端的连接请求。类似于前台接待员。
workerGroup:负责客户端连接通道的IO操作。就是实际做事情的人。
bossGroup把人员领进门后,就可以等待下一个人员进来。人员进门之后workerGroup就负责对他进行服务了。

通过源码来看看这两位的工作:
EchoServer中:

ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)

看看ServerBootstrap中的group方法

public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
super.group(parentGroup);
if (childGroup == null) {
throw new NullPointerException("childGroup");
}
if (this.childGroup != null) {
throw new IllegalStateException("childGroup set already");
}
this.childGroup = childGroup;
return this;
}

把bossGroup赋值给了ServerBootstrap的父类AbstractBootstrap的group(EventLoopGroup)变量。
把workerGroup赋值给了ServerBootstrap的childGroup(EventLoopGroup)变量。


ChannelFuture f = b.bind(PORT).sync();

这个方法调用链是:
AbstractBootstrap.bind-->AbstractBootstrap.doBind--> AbstractBootstrap.initAndRegister
又看到了initAndRegister,前面在分析客户端的时候就看过了,里面有几段重要的代码

final Channel channel = channelFactory().newChannel();
init(channel);
ChannelFuture regFuture = group().register(channel);

newChannel()是一个NioServerSocketChannel实例,group()就是bossGroup(NioEventLoopGroup),group().register(channel)【和客户端的一样】将NioServerSocketChannel和bossGroup关联起来(将ServerSocketChannel注册到eventLoop关联的selector上)。

接着我们来看看服务端是怎样接受客户端请求的。首先根据以上的分析,我们已经知道NioServerSocketChannel是对SelectionKey.OP_ACCEPT事件感兴趣。那么当ACCEPT事件发生的时候netty是在哪里进行处理的喃?
回到b.bind(PORT)的调用链AbstractBootstrap.bind -> AbstractBootstrap.doBind方法中可以看到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());
}
}
});
}

eventLoop()返回NioEventLoop对象,调用NioEventLoop.execute的方法

public void execute(Runnable task) {
if (task == null) {
throw new NullPointerException("task");
}

boolean inEventLoop = inEventLoop();
if (inEventLoop) {
addTask(task);
} else {
startThread();
addTask(task);
if (isShutdown() && removeTask(task)) {
reject();
}
}

if (!addTaskWakesUp && wakesUpForTask(task)) {
wakeup(inEventLoop);
}
}

继续看里面的startThread()方法:

private void startThread() {
if (STATE_UPDATER.get(this) == ST_NOT_STARTED) {
if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {
thread.start();
}
}
}

thread对象是什么喃!还记得NioEventLoop的初始化方法不?在一系列的super方法后在SingleThreadEventExecutor的构造方法中

....................
thread = threadFactory.newThread(new Runnable() {
@Override
public void run() {
boolean success = false;
updateLastExecutionTime();
try {
SingleThreadEventExecutor.this.run();
success = true;
.......................

对thread变量赋值一个匿名线程,回到startThread()方法中。这个方法中会将这个匿名线程启动起来。上面代码中run()方法会调用。注意:SingleThreadEventExecutor.this.run()
SingleThreadEventExecutor的子类NioEventLoop重写了run()方法,

@Override
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);
}
}
}

1,轮询selector上的所有的channel的IO事件:select(wakenUp.getAndSet(false));
2,处理产生网络IO事件的channel:processSelectedKeys();
3,处理任务队列runAllTasks();
当发生了OP_ACCEPT事件就绪后,processSelectedKeys就开始处理就绪的事件,继续跟踪:
看到NioEventLoop中的processSelectedKey方法:

private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
final NioUnsafe unsafe = ch.unsafe();
if (!k.isValid()) {
final EventLoop eventLoop;
try {
eventLoop = ch.eventLoop();
} catch (Throwable ignored) {
return;
}
if (eventLoop != this || eventLoop == null) {
return;
}
unsafe.close(unsafe.voidPromise());
return;
}

try {
int readyOps = k.readyOps();NotYetConnectedException.
if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
int ops = k.interestOps();
ops &= ~SelectionKey.OP_CONNECT;
k.interestOps(ops);

unsafe.finishConnect();
}
if ((readyOps & SelectionKey.OP_WRITE) != 0) {
ch.unsafe().forceFlush();
}

if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
unsafe.read();
}
} catch (CancelledKeyException ignored) {
unsafe.close(unsafe.voidPromise());
}
}


int ops = k.interestOps();
ops &= ~SelectionKey.OP_CONNECT;
k.interestOps(ops);
unsafe.finishConnect();

完成了客户端的连接操作,删除SelectionKey.OP_CONNECT事件。从以上代码看到OP_READ事件就绪后,会调用unsafe.read();就是调用到NioMessageUnsafe的read方法:看重点代码

int localRead = doReadMessages(readBuf);

继续调用到NioServerSocketChannel.doReadMessages方法:

@Override
protected int doReadMessages(List<Object> buf) throws Exception {
SocketChannel ch = SocketUtils.accept(javaChannel());

try {
if (ch != null) {
buf.add(new NioSocketChannel(this, ch));
return 1;
}
} catch (Throwable t) {
logger.warn("Failed to create a new channel from an accepted socket.", t);

try {
ch.close();
} catch (Throwable t2) {
logger.warn("Failed to close a socket.", t2);
}
}

return 0;
}

在doReadMessages中,通过javaChannel().accept()获取到客户端新连接的SocketChannel,接着就实例化一个NioSocketChannel,并且传入NioServerSocketChannel对象。这个方法后返回到NioMessageUnsafe的read方法中

for (int i = 0; i < size; i ++) {
pipeline.fireChannelRead(readBuf.get(i));
}

当有读取信息的后,通过ChannelPipeline机制,将读取事件逐级发送到各个handler中,这样就顺利完成数据读取。

上面的分析,我们已经知道了bossGroup和NioServerSocketChannel关联起来了,那么剩下的 workerGroup怎么关联喃???这个我们就要回到initAndRegister方法中接着往下看了:init方法。在ServerBootstrap中重写了init方法

@Override
void init(Channel channel) throws Exception {
final Map<ChannelOption<?>, Object> options = options();
synchronized (options) {
setChannelOptions(channel, options, logger);
}

final Map<AttributeKey<?>, Object> attrs = attrs();
synchronized (attrs) {
for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) {
@SuppressWarnings("unchecked")
AttributeKey<Object> key = (AttributeKey<Object>) e.getKey();
channel.attr(key).set(e.getValue());
}
}

ChannelPipeline p = channel.pipeline();

final EventLoopGroup currentChildGroup = childGroup;
final ChannelHandler currentChildHandler = childHandler;
final Entry<ChannelOption<?>, Object>[] currentChildOptions;
final Entry<AttributeKey<?>, Object>[] currentChildAttrs;
synchronized (childOptions) {
currentChildOptions = childOptions.entrySet().toArray(newOptionArray(childOptions.size()));
}
synchronized (childAttrs) {
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();
ChannelHandler handler = handler();
if (handler != null) {
pipeline.addLast(handler);
}
ch.eventLoop().execute(new Runnable() {
@Override
public void run() {
pipeline.addLast(new ServerBootstrapAcceptor(
currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
}
});
}
});
}

ch.eventLoop().execute方法会把这个匿名线程放到SingleThreadEventExecutor的任务队列中。请关注:pipeline.addLast(new ServerBootstrapAcceptor(currentChildGroup, currentChildHandler,currentChildOptions,currentChildAttrs));这句代码中的ServerBootstrapAcceptor对象(这句代码把ServerBootstrapAcceptor对象当成了一个handle放入了管道的最后)。childGroup对象就是前面的workerGroup赋值的值。

ServerBootstrapAcceptor中重写了channelRead方法

public void channelRead(ChannelHandlerContext ctx, Object msg) {
final Channel child = (Channel) msg;
child.pipeline().addLast(childHandler);
setChannelOptions(child, childOptions, logger);
for (Entry<AttributeKey<?>, Object> e: childAttrs) {
child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
}
try {
childGroup.register(child).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (!future.isSuccess()) {
forceClose(child, future.cause());
}
}
});
} catch (Throwable t) {
forceClose(child, t);
}
}

Channel child是一个NioSocketChannel,通过childGroup.register(child)这样workerGroup就和NioSocketChannel关联起来了。

[color=blue]workerGroup关联NioSocketChannel。
bossGroup关联NioServerSocketChannel。[/color]

看看ServerBootstrapAcceptor中的channelRead方法是怎么调用的。
上面提到了:[color=brown]当有读取信息的后,通过ChannelPipeline机制,将读取事件逐级发送到各个handler中[/color]的方法:

pipeline.fireChannelRead(readBuf.get(i));

这个代码会调用到DefaultChannelPipeline的fireChannelRead方法中:

@Override
public final ChannelPipeline fireChannelRead(Object msg) {
AbstractChannelHandlerContext.invokeChannelRead(head, msg);
return this;
}

继续:AbstractChannelHandlerContext的invokeChannelRead

static void invokeChannelRead(final AbstractChannelHandlerContext next, final Object msg) {
ObjectUtil.checkNotNull(msg, "msg");
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
next.invokeChannelRead(msg);
} else {
executor.execute(new Runnable() {
@Override
public void run() {
next.invokeChannelRead(msg);
}
});
}
}

会调用next(HeadContext):AbstractChannelHandlerContext的方法invokeChannelRead,也是在AbstractChannelHandlerContext中:

private void invokeChannelRead(Object msg) {
if (invokeHandler()) {
try {
((ChannelInboundHandler) handler()).channelRead(this, msg);
} catch (Throwable t) {
notifyHandlerException(t);
}
} else {
fireChannelRead(msg);
}
}

handler()就会得到ServerBootstrapAcceptor对象,这样就调到了ServerBootstrapAcceptor重写的channelRead方法。

还记得我们上面的ServerBootstrap的init方法吗?在这个方法中添加了handler,方法里面通过handler()方法获取一个handler,不为空的情况下会添加到pipeline中,那么handler()得到的是什么对象喃?
其实handler()返回的就是我们在EchoServer类中添加的handler:

.handler(new LoggingHandler(LogLevel.INFO))

在ServerBootstrap初始化时的管道里面的handler情况是:
head---->ChannelInitializer--->tail

根据上一篇客户端的经验,当channel绑定到eventLoop后,这里是NioServerSocketChannel绑定到bossGroup中,会在pipeline中发出fireChannelRegistered事件,接着就会触发 ChannelInitializer.initChannel方法的调用这个方法被覆盖了的:

p.addLast(new ChannelInitializer<Channel>() {
@Override
public void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
ChannelHandler handler = handler();
if (handler != null) {
pipeline.addLast(handler);
}
pipeline.addLast(new ServerBootstrapAcceptor(
currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
}
});

[color=blue]这个时候NioServerSocketChannel对应的管道里面就是:
head---->LoggingHandler--->ServerBootstrapAcceptor--->tail[/color]

在ServerBootstrapAcceptor.channelRead中会为新建的Channel设置handler并注册到一个 eventLoop中:

final Channel child = (Channel) msg;
child.pipeline().addLast(childHandler);
childGroup.register(child).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (!future.isSuccess()) {
forceClose(child, future.cause());
}
}
});

childHandler就是我们在启动的时候,设置的:

.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 EchoServerHandler());
}
});

当这个客户端连接Channel注册到eventLoop后就会触发事件,调用ChannelInitializer.initChannel方法。那么新客户端连接后,[color=blue]NioSocketChannel对于的管道里面对应就是:
head---->sslHandler--->EchoServerHandler--->tail[/color]

[color=blue]看了服务端有2个handler:一个是通过handler()方法设置handler字段,一个是过 childHandler()设置childHandler字段。handler负责处理客户端的连接请求;childHandler 就是负责和客户端的连接的IO交互。[/color]

算是走完了服务端和客户端吧!但是还是比较混乱,只是跟着代码在走,还是跳跃的在走。有时自己都走晕了。至于netty为什么要这样写,这样写的优势,各种机制的实现还是没有分析到位,后面再努力吧!
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值