前面看了服务端绑定和读写的流程,再来看客户端,瞬间感觉简单多了~
客户端和服务端相比,区别主要在NioClientBoss上,worker是一样的。来看一个简单的客户端:
ClientBootstrap bootstrap = new ClientBootstrap(
new NioClientSocketChannelFactory(
Executors.newCachedThreadPool(),
Executors.newCachedThreadPool()));
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
@Override
public ChannelPipeline getPipeline() throws Exception {
return Channels.pipeline(new PrintHandler());
}
});
ChannelFuture future = bootstrap.connect(new InetSocketAddress(1210));
future.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future)
throws Exception {
String message = "hello pony";
ChannelBuffer buffer = ChannelBuffers.buffer(message.length());
buffer.writeBytes(message.getBytes());
future.getChannel().write(buffer);
}
});
1. 首先是构造NioClientSocketChannelFactory
主要就是把boss和worker线程池传进去用于构造Channel。
① new NioWorkerPool
与服务端相同,启动了一组IOT,并构造对应的selector。
② new NioClientBossPool
与服务端也是一样的,启动BT和selector,只不过另外构造了一个HashedWheelTimer,经典的时间轮算法,经常用于处理连接超时的情况,时间复杂度很低,具体我也不大明白,求恶补啊。。。
2. 调用connect
① new Channel
前面构造了factory,现在来构造channel:
NioClientSocketChannel(
ChannelFactory factory, ChannelPipeline pipeline,
ChannelSink sink, NioWorker worker) {
super(null, factory, pipeline, sink, newSocket(), worker);
fireChannelOpen(this);
}
将factory,pipeline,sink,socket和worker等传入构造器,构造Netty层的“逻辑”channel对象。其中在执行父类构造器方法AbstractChannel时,会随机生成一个id,并注册当前channel到allChannels上(id唯一,若已占用则递增直到找到可用的为止)。
② channel构造好后,fireChannelOpen(上行事件),经过层层handler处理后,到达sink,默认处理为丢弃事件。
③ 调Channels.connect
这是Netty层发起的下行事件,也是经过层层handler,最终到NioClientSocketPipelineSink.eventSunk(不同的ChannelStateEvent处理不同)。
在connect方法中:
if (channel.channel.connect(remoteAddress)) {
channel.worker.register(channel, cf);
} else {
channel.getCloseFuture().addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture f)
throws Exception {
if (!cf.isDone()) {
cf.setFailure(new ClosedChannelException());
}
}
});
cf.addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
channel.connectFuture = cf;
nextBoss().register(channel, cf);
}
首先尝试在发起connect的线程中(UT)connect一次,如果成功则获取对应worker并注册INTEREST到worker.selector上;否则投递一个OP_CONNECT给BT(非阻塞模式下的connect会异步、并发的建立连接,当检查到连接已建立成功时,必须通过调用finishConnect来完成建立连接动作,NioClientBoss.process中调用了finishConnect)。
④ 投递OP_CONNECT后,BT在process时先processSelectedKeys,轮询key,若isConnected(即OP_CONNECT被选中),则finishConnect并注册INTEREST到worker.selector(与首次尝试connect成功后的操作相同)。然后BT processConnectTimeout,将selector中的每个channel(注册selector时以attachment形式放入的)取出进行超时判断。
总的来说客户端连接流程很简单,主要是finishConnect和连接超时的处理。HashedWheelTimer等哪天看了再发吧。。。
本人辛苦分析、码字,请尊重他人劳动成果,转载不注明出处的诅咒你当一辈子一线搬砖工,嘿嘿~
欢迎讨论、指正~~
下篇预告:暂时还没想好