客户端启动流程基本跟server端大同小异。
public class Bootstrap extends AbstractBootstrap<Bootstrap, Channel> {
private ChannelFuture doResolveAndConnect(SocketAddress remoteAddress,SocketAddress localAddress) {
//普通任务队列添加 NioSocketChannel在Selector注册任务,并开启reactor线程不断轮询任务队列
ChannelFuture regFuture = initAndRegister();
Channel channel = regFuture.channel();
if (regFuture.isDone()) {
...
} else {
// 线程切换完毕后返回DefaultChannelPromise,并且将selectionKey关注的connect事件封装为
//监听器ChannelFutureListener感兴趣的任务。NioSocketChannel注册过程中由reactor线程触发监听器并完成connect事件的关注
PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
regFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
promise.registered();
// 同样是将connect事件添加到普通任务队列中
doResolveAndConnect0(channel, remoteAddress, localAddress, promise);
}
});
return promise;
}
}
}
public abstract class AbstractChannel extends DefaultAttributeMap implements Channel {
protected abstract class AbstractUnsafe implements Unsafe {
private void register0(ChannelPromise promise) {//main线程切换为某个reactor线程
...
boolean firstRegistration = neverRegistered;
doRegister();//NioSocketChannel完成在Selector注册
neverRegistered = false;
registered = true;
// 将用户自定义ChannelHandler添加到NioSocketChannel中的pipeline中
pipeline.invokeHandlerAddedIfNeeded();
//reactor线程触发监听器ChannelFutureListener完成connect事件的关注
safeSetSuccess(promise);
// 调用ChannelInboundHandler所有子类重写后的方法:channelRegistered
pipeline.fireChannelRegistered();
if (isActive()) {
if (firstRegistration) {
pipeline.fireChannelActive();
} else if (config().isAutoRead()) {
beginRead();
}
}
}
}
}
通过上述得知:启动流程中分别将NioSocketChannel在Selector注册、connect事件绑定都作为任务添加到普通任务队列中,并且在首次注册时唤醒某个reactor线程不断轮询执行队列中任务。
1.reactor线程执行队列任务
public final class NioEventLoop extends SingleThreadEventLoop {
@Override
protected void run() {
int selectCnt = 0;
for (;;) {
try {
int strategy;
try {
// 策略是指:优先从普通队列获取任务,否则返回SELECT【-1】
strategy = selectStrategy.calculateStrategy(selectNowSupplier, hasTasks());
switch (strategy) {
case SelectStrategy.CONTINUE:
continue;
case SelectStrategy.BUSY_WAIT:
case SelectStrategy.SELECT:
// 获取selector存在的IO事件
strategy = select(curDeadlineNanos);
...
}
}
//伪代码
if (strategy > 0) {
processSelectedKeys();// 处理IO事件
}
long ioTime = System.nanoTime() - ioStartTime;
// 处理队列中普通任务
ranTasks = runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
...
}
...
}
}
private int select(long deadlineNanos) throws IOException {
if (deadlineNanos == NONE) {
return selector.select();
}
// Timeout will only be 0 if deadline is within 5 microsecs
long timeoutMillis = deadlineToDelayNanos(deadlineNanos + 995000L) / 1000000L;
// selector.selectNow():非阻塞方式,如果没有相应的IO事件则返回0
return timeoutMillis <= 0 ? selector.selectNow() : selector.select(timeoutMillis);
}
}
1.1.执行队列普通任务
public abstract class SingleThreadEventExecutor extends AbstractScheduledEventExecutor{
protected boolean runAllTasks(long timeoutNanos) {
...
final long deadline = timeoutNanos > 0 ? ScheduledFutureTask.nanoTime() + timeoutNanos : 0;
long runTasks = 0;
long lastExecutionTime;
for (;;) {
safeExecute(task);
runTasks ++;
...
task = pollTask();
if (task == null) {
break;
}
}
return true;
}
}
每次处理队列中普通任务都是全部处理完毕后,reactor线程才会退出当前轮询。例如先处理NioSocketChannel在Selector注册,后处理connect事件的绑定。
1.2.执行IO事件
2.NioSocketChannel之Connect事件的绑定
public class NioSocketChannel{
protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {
...
boolean success = false;
boolean connected = SocketUtils.connect(javaChannel(), remoteAddress);// 跟服务端建立连接~ TCP协议
if (!connected) {
selectionKey().interestOps(SelectionKey.OP_CONNECT);//绑定注册 OP_CONNECT = 8 连接事件
}
success = true;
return connected;
}
}
利用TCP协议客户端与服务端建立连接。此时服务端会在NioServerSocketChannel对应SelectedKey上监测到对应的Acceptor事件。
不理解的现象:当NioSocketChannel绑定连接事件后,在其事件循环组中会监测到该IO事件,区别于服务端绑定Accept事件。通过processSelectedKeys方法处理其上的IO事件时触发channelActive方法。
3.客户端发送数据并NioSocketChannel关注read事件
public final class NioEventLoop extends SingleThreadEventLoop {
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
...
// We first need to call finishConnect() before try to trigger a read(...) or write(...) as otherwise
// the NIO JDK channel implementation may throw a NotYetConnectedException.
//NioSocketChannel一旦完成connect事件,就会在SelectorKey注册一个事件,即readyOps = 8
int readyOps = k.readyOps();
if ((readyOps & SelectionKey.OP_CONNECT) != 0) {//正常读写事件发生之前该条件必须成立
int ops = k.interestOps();// ops = 8
ops &= ~SelectionKey.OP_CONNECT;
k.interestOps(ops);//ops = 0 ,关注事件为ops = 0
unsafe.finishConnect();//此处会触发channelActive 方法的执行,客户端通过通道发送数据到服务端
}
// Process OP_WRITE first as we may be able to write some queued buffers and so free memory.
if ((readyOps & SelectionKey.OP_WRITE) != 0) {
// Call forceFlush which will also take care of clear the OP_WRITE once there is nothing left to write
ch.unsafe().forceFlush();
}
// Also check for readOps of 0 to workaround possible JDK bug which may otherwise lead
// to a spin loop
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
unsafe.read();//处理服务端的响应,并回调ChannelInboundHandler所有子类重写后的方法:channelRead
}
}
}
3.1.客户端执行channelActive 方法发送数据
public abstract class AbstractNioChannel{
public final void finishConnect() {
boolean wasActive = isActive();//此时客户端完成connect事件,但是 wasActive = false
doFinishConnect();// 此处修改connect的状态值为 连接状态。如果此时再执行 isActive 方法则返回true
fulfillConnectPromise(connectPromise, wasActive);
}
private void fulfillConnectPromise(ChannelPromise promise, boolean wasActive) {
...
boolean active = isActive();//返回true,表明当前连接可以正常使用
boolean promiseSet = promise.trySuccess();
if (!wasActive && active) {// 条件成立
pipeline().fireChannelActive();
}
...
}
}
public class DefaultChannelPipeline implements ChannelPipeline {
public final ChannelPipeline fireChannelActive() {
AbstractChannelHandlerContext.invokeChannelActive(head);
return this;
}
public void channelActive(ChannelHandlerContext ctx) {
ctx.fireChannelActive();//负责调用自定义的channelActive方法,客户端发送数据
readIfIsAutoRead();//数据发送完毕,NioSocketChannel注册read事件
}
}
3.2.read事件的关注
public abstract class AbstractNioChannel extends AbstractChannel {
protected void doBeginRead() throws Exception {
final SelectionKey selectionKey = this.selectionKey;
...
final int interestOps = selectionKey.interestOps();//上述得知,connect事件完成之后,设置的感兴趣的事件ops = 0
if ((interestOps & readInterestOp) == 0) {
selectionKey.interestOps(interestOps | readInterestOp);
}
}
}
readInterestOp:1,读事件。
至此,NioSocketChannel、NioServerSocketChannel都完成读IO事件的关注。各自reactor线程着重开始处理read等IO事件,完成普通任务队列中任务消费处理。
截止客户端关注read事件为止,整个流程都是reactor线程触发执行的。当然在客户端发送数据时用户可以异步执行,这种情况最终也是被放入任务队列,由reactor线程最终发出。
3、客户端发送数据
尾部 -> 首部方向分别执行管道pipeline中的handler。
abstract class AbstractChannelHandlerContext{
public ChannelFuture writeAndFlush(Object msg) {
return writeAndFlush(msg, newPromise());
}
private void write(Object msg, boolean flush, ChannelPromise promise) {//flush:true
...
final AbstractChannelHandlerContext next = findContextOutbound(flush ?
(MASK_WRITE | MASK_FLUSH) : MASK_WRITE);
final Object m = pipeline.touch(msg, next);
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
if (flush) {//默认为true
next.invokeWriteAndFlush(m, promise);
} else {
next.invokeWrite(m, promise);
}
} else {// 客户端如果是通过自定义线程发送数据,则会将其包装台为WriteTask,最终作为普通任务添加到普通队列任务中。最终
final WriteTask task = WriteTask.newInstance(next, m, promise, flush);
if (!safeExecute(executor, task, promise, m, !flush)) {
task.cancel();
}
}
}
void invokeWriteAndFlush(Object msg, ChannelPromise promise) {
if (invokeHandler()) {
invokeWrite0(msg, promise);// 首先执行所有handler的Write方法
invokeFlush0();// 最后执行所有handler的Flush方法,最终由HeadContext将数据flush到套接字,触发服务端IO事件继续处理
} else {
writeAndFlush(msg, promise);
}
}
}
findContextOutbound:判断当前handler是否存在对应的方法,即write、flush。【并不是所有handler都实现接口inboundHandler、outboundHandler的所有方法】。
终极猜想:在客户端与服务端交互过程中,客户端Netty内部都是某个具体reactor线程在执行,初始化客户端指定多个线程好像没有啥意义。