目录
SingleThreadEventExecutor.execute(task)
processSelectedKey(SelectionKey,Channel)
Accept:NioMessageUnsafe.read()
注册NioSocketChannel到workerGroup:ServerBootstrapAcceptor
随便写写,代码基于netty4.0.35版本。
服务端代码
NioEventLoopGroup
创建NioEventLoopGroup,会默认创建2*jvm可用cpu个数个NioEventLoop(可以指定)。
NioEventLoopGroup的父类MultithreadEventExecutorGroup有一个children属性,是一个EventExecutor[],用来存放指定数量的NioEventLoop.
实例化children之后,为每个NioEventLoop的执行结果Future添加监听线程执行结果的listener。
NioEventLoop
NioEventLoop名为事件循环,主要用来处理channel上的accept、read、write等事件。NioEventLoop主要包含thread(线程类)、selector(多路复用器)、taskQueue等属性。
- selector:多路复用器。一个NioEventLoop管理一个或多个通道(通道会注册到NioEventLoop的selector上),然后执行select()来监听channel上的事件。
- thread:和NioEventLoop绑定的线程。在创建NioEventLoop时会创建thread并执行thread.run()。thread启动时会执行NioEventLoop的run方法(NioEventLoop本身并不是线程,只是有一个名字叫run的方法),run方法的具体内容见下面介绍。
- taskQueue:taskQueue是一个Queue<Runnable>,仅仅用来存放上面的thread(NioEventLoop.run())还没启动时的任务,如ServerChannel和端口绑定时的fireChannelActive执行,还有通道第一次在NioEventLoop上注册的任务,这些任务会在NioEventLoop.run()中调用runAllTasks()来执行。当NioEventLoop的thread启动时,后面的任务就都不会存放在taskQueue中而是直接在NioEventLoop中同步执行了。
这是一段比较典型的代码,当selector监听到read事件时,会调用通道上的head(ChannelHandler)的fireChannelRead(),如果当前执行线程是这个通道注册的NioEventLoop的thread时,就直接执行,否则就执行execute(),将任务封装成一个Thread,放入taskQueue中。
public ChannelHandlerContext fireChannelRead(final Object msg) {
final AbstractChannelHandlerContext next = findContextInbound();
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
next.invokeChannelRead(msg);
} else {
executor.execute(new OneTimeTask() {
@Override
public void run() {
next.invokeChannelRead(msg);
}
});
}
return this;
}
public boolean inEventLoop() {
return inEventLoop(Thread.currentThread());
}
public void execute(Runnable task) {
boolean inEventLoop = inEventLoop();
if (inEventLoop) {
addTask(task);
} else {
startThread();
addTask(task);
if (isShutdown() && removeTask(task)) {
reject();
}
}
......
}
创建NioEventLoop会调用自己及父类SingleThreadEventExecutor的构造方法,创建线程绑定到的NioEventLoop的thread属性,同时初始化taskQueue。
protected SingleThreadEventExecutor(
EventExecutorGroup parent, ThreadFactory threadFactory, boolean addTaskWakesUp) {
......
// 创建thread
thread = threadFactory.newThread(new Runnable() {
@Override
public void run() {
......
// 执行NioEventLoop.run()
SingleThreadEventExecutor.this.run();
......
}
});
threadProperties = new DefaultThreadProperties(thread);
// 初始化taskQueue
taskQueue = newTaskQueue();
}
NioEventLoop.run()监听到IO事件后,是直接在当前NioEventLoop中同步调用处理方法的,即监听IO事件的线程和处理事件的线程是同一个线程,这样做的好处就是能避免创建太多线程时频繁的上下文切换。当然netty也并不由将线程数量定死,你在创建NioEventLoopGroup时可以指定线程数量。
ServerBootstrap
这是服务器通道的启动类。有两个NioEventLoopGroup属性(group【bossGroup】和childGroup【workerGroup】)
服务端一般会传两个NioEventLoopGroup,一个boss,一个worker;客户端只会传一个,worker。
Bind(port)
两大核心方法:initAndRegister()和doBind0()。
initAndRegister()
首先会new一个channel(包括pipeline、ChannelConfig、ChannelId、ChannelHandler等)。
Init(channel)
然后进行初始化,执行init(channel)。该方法仅初始化了一些基本的选项(options)或属性(attr),以及在pipeline中添加一些ChannelHandler(包括一个ServerBootstrapAcceptor)。详见ServerBootstrap.init(channel)。
group().register(channel)
Group()会返回一个NioEventLoopGroup(服务端是bossGroup),然后调用它的register(channel)
AbstractChannel.this.eventLoop = eventLoop;将传入的NioEventLoop与创建的这个Channel绑定。
接下来会执行register0(),但是然后会判断eventLoop.inEventLoop()即Thread.currentThread()【当前执行的线程】是否是eventLoop的thread(此处是main线程),是就直接执行,不是就执行eventLoop.execute(),将执行的方法放入到一个线程中,存入taskQueue,稍后执行。
register0(promise)
这个方法是真正注册的方法,看方法就知道是将这个channel注册到绑定的eventLoop的selector上
doBind0()
如果通道注册成功,则进行端口绑定
最终到了AbstractChannel.bind()。
doBind(localAddress)
doBind(localAddress)就是真正将程序和通道进行了绑定。
isActive()
就是判断当前服务通道是否已经和一个地址绑定,是返回true,否则false。
pipeline.fireChannelActive()
如果channel和端口绑定了就将pipeline.fireChannelActive()这个任务创建为一个线程放入到eventLoop的taskQueue中,任务执行时在该channelPipeline上执行fireChannelActive(),然后沿着pipeline链一路传递下去。Server启动是沿着inbound链传递。
SingleThreadEventExecutor.execute(task)
SingleThreadEventExecutor是NioEventLoop的祖先类。Execute(task)的作用是将task加入到taskQueue中,此外如果不是从NioEventLoop.run()中进入execute()的话,就会启动NioEventLoop绑定的thread。
这个绑定的thread启动会执行NioEventLoop.run()的run方法。
创建server的过程中,会执行execute()来将通道注册任务(register0())和pipeline.fireChannelActive()任务放入到taskQueue中,所以此时thread会被启动。之后server就是监听连接事件,所以的连接事件会在NioEventLoop.run()中进行处理,就都是直接执行了。
NioEventLoop.run()
NioEventLoop.run()是一个无限循环。主要进行:
- 监听注册在NioEventLoop的selector上的通道事件(连接、读写等)。
- 处理所有selector监听到的事件【processSelectedKeys()】。
- 处理所有taskQueue中的task。【runAllTasks()】。
Select()
processSelectedKey(SelectionKey,Channel)
对于监听到的channel事件,进行判断,然后进行操作,可以看到,包括了四种IO事件
runAllTasks()
就是执行了taskQueue中的所有task的run方法。
Unsafe.read()
这个unsafe是调用了channel的unsafe()后返回的。而NioServerSocketChannel(父类是AbstractNioMessageChannel)返回NioMessageUnsafe,NioSocketChannel(父类是AbstractNioByteChannel)返回NioByteUnsafe。
上面监听到read或accept事件都会调用unsafe.read(),就是因为NioMessageUnsafe.read()是处理accept事件、NioByteUnsafe.read()是处理读事件的。
Accept:NioMessageUnsafe.read()
NioMessageUnsafe.read()最终会在一个无限循环中去接受连接并创建NioSocketchannel(因为可能同时有多个客户端建立连接),所有创建的channel都会存放在readBuf中,然后执行readBuf中所有的channel的pipeline的fireChannelRead()。最后执行NioServerSocketChannel的fireChannelReadComplete(),表示本次accept事件处理完成。
private final List<Object> readBuf = new ArrayList<Object>();
public void read() {
for (;;) {
int localRead = doReadMessages(readBuf);
// dosomething();
}
......
int size = readBuf.size();
for (int i = 0; i < size; i ++) {
// 注意这里是将创建的NioSocketChannel当做参数
pipeline.fireChannelRead(readBuf.get(i));
}
readBuf.clear();
pipeline.fireChannelReadComplete();
}
doReadMessages(readBuf)
首先accept()接受连接,获取连接通道,然后将通道添加到buf中。
protected int doReadMessages(List<Object> buf) throws Exception {
SocketChannel ch = javaChannel().accept();
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;
}
注册NioSocketChannel到workerGroup:ServerBootstrapAcceptor
上满提到了在bind(port)的时候会初始化NioServerSocketChannel,就是init()。这个方法中会将ServerBootstrapAcceptor(ChannelInboundHandlerAdapter)添加到NioServerSocketChannel的pipeline中,所以当一个连接创建成功触发channelRead()时,ServerBootstrapAcceptor的channlRead()会被调用。
public void channelRead(ChannelHandlerContext ctx, Object msg) {
final Channel child = (Channel) msg;
......
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);
}
}
显然,这与NioServerSocketChannel的注册过程是一样的,不过NioServerSocketChannel是被注册到bossGroup中,而NioSocketChannel是被注册到workerGroup中。
Read:NioByteUnsafe.read()
NioByteUnsafe.read()首先会不断的读取NioSocketChannel中的数据到一个byteBuf中,然后触发inbound链的channelRead方法,将数据交给ChannelHanlder去处理,最终会传递到用户自定义的ChannelHanlder中,对数据进行业务处理。等所有数据都读取完成后,就调用fireChannelReadComplete()。
public final void read() {
......
do{
int localReadAmount = doReadBytes(byteBuf);
......
pipeline.fireChannelRead(byteBuf);
while (++ messages < maxMessagesPerRead);
......
pipeline.fireChannelReadComplete();
}
ChannelPipeline
netty中的ChannelPipeline与channel绑定,每创建一个channel,就会创建一个ChannelPipeline。
protected AbstractChannel(Channel parent) {
this.parent = parent;
unsafe = newUnsafe();
pipeline = new DefaultChannelPipeline(this);
}
ChannelPipeline相当于一个双向链表,节点类型就是ChannelHandler。而ChannelHandler又分为两类:inbound和outbound。ChannelPipeline中初始就有两个节点:head(outbound)和tail(inbound)。
public DefaultChannelPipeline(AbstractChannel channel) {
if (channel == null) {
throw new NullPointerException("channel");
}
this.channel = channel;
tail = new TailContext(this);
head = new HeadContext(this);
head.next = tail;
tail.prev = head;
}
读数据时,数据会在inbound链上传播(从head到tail),而写数据时(wirteAndFlush),数据会在outbound链上传播(从tail到head)。
inbound传播过程
这里以客户端从通道读取数据为例,最终unsafe.read()会执行到NioByteUnsafe.read()。
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
unsafe.read();
......
}
从前面可知,对于每次读到的数据都会调用pipeline.fireChannelRead(byteBuf),进入该方法里面发现是调用的head.fireChannelRead(msg),所以是从head开始传递。然后在下一个方法调用findContextInbound()来找到下一个ChannelHandler,并执行它的channelRead()。
public ChannelPipeline fireChannelRead(Object msg) {
head.fireChannelRead(msg);
return this;
}
public ChannelHandlerContext fireChannelRead(final Object msg) {
......
final AbstractChannelHandlerContext next = findContextInbound();
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
next.invokeChannelRead(msg);
} else {
executor.execute(new OneTimeTask() {
@Override
public void run() {
next.invokeChannelRead(msg);
}
});
}
return this;
}
很明显,就是在链表上通过循环的方式找到下一个inbound类型的ChannelHandler,直到tail。
private AbstractChannelHandlerContext findContextInbound() {
AbstractChannelHandlerContext ctx = this;
do {
ctx = ctx.next;
} while (!ctx.inbound);
return ctx;
}
outbound传播过程
这里以channel.writeAndFlush(msg)为例。可以看到首先调用的tail.writeAndFlush(msg)。然后进入到了一个write方法中,通过findContextOutbound()来找出下一个要传播的ChannelHandler。
public ChannelFuture writeAndFlush(Object msg) {
return tail.writeAndFlush(msg);
}
public ChannelFuture writeAndFlush(Object msg, ChannelPromise promise) {
......
write(msg, true, promise);
return promise;
}
private void write(Object msg, boolean flush, ChannelPromise promise) {
AbstractChannelHandlerContext next = findContextOutbound();
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
next.invokeWrite(msg, promise);
if (flush) {
next.invokeFlush();
}
} else {
AbstractWriteTask task;
if (flush) {
task = WriteAndFlushTask.newInstance(next, msg, promise);
} else {
task = WriteTask.newInstance(next, msg, promise);
}
safeExecute(executor, task, promise, msg);
}
}
跟上面一样,从尾往头找到outbound类型的ChannelHandler进行传播,直到head。
private AbstractChannelHandlerContext findContextOutbound() {
AbstractChannelHandlerContext ctx = this;
do {
ctx = ctx.prev;
} while (!ctx.outbound);
return ctx;
}