![a8cd8d4849194b82ed100447cd0c1522.png](https://i-blog.csdnimg.cn/blog_migrate/7138b16a88519325aa9ed3e3491fbda1.jpeg)
首先这是一个创建zk对象的代码,通过查阅资料得知 创建过程是异步的,所以我们来一探究竟。jar版本3.4.7
![2c88da891f9c0ee266a3880c3df02e80.png](https://i-blog.csdnimg.cn/blog_migrate/78c570e6314958b240653a951aeb9afc.jpeg)
这段代码最后两行是重点;看下new ClientCnxn 代码
![bf16bd156edfd33f1d4793681cac9b73.png](https://i-blog.csdnimg.cn/blog_migrate/12b4be19ad7ee60bc9fe2f44720be369.jpeg)
前面都是参数准备,最后两行启动了两个线程;
sendThread发送线程 eventThread 事件相关
因为最后cnxn.start();方法是启动两个线程,所以继续追踪两个Thread run方法,
先看sendThread。
![4eb772cb6d26b6c40a48f38aa081e811.png](https://i-blog.csdnimg.cn/blog_migrate/c991a598fec8f2bc1b392629d42246ef.jpeg)
大致逻辑如下:
while (state.isAlive()) //判断连接是否存活 try {
if (!clientCnxnSocket.isConnected()) {clientCnxnSocket不是已经链接
if(!isFirstConnect){不是第一次连接
try {
Thread.sleep(r.nextInt(1000));休息
} catch (InterruptedException e) {LOG.warn("Unexpected exception", e);
}
}
// don't re-establish connection if we are closing
if (closing || !state.isAlive()) {如果连接失效 break
break;
} startConnect();重点开始 此处开始连接
clientCnxnSocket.updateLastSendAndHeard();
}//代码逻辑大致是 检测授权情况 发送一个授权事件,并记录时间 如果readTimeout或者connectTimeout则抛出异常。
if (state.isConnected()) {
// determine whether we need to send an AuthFailed event.
if (zooKeeperSaslClient != null) {
boolean sendAuthEvent = false;
if (zooKeeperSaslClient.getSaslState() == ZooKeeperSaslClient.SaslState.INITIAL) {
try {
zooKeeperSaslClient.initialize(ClientCnxn.this);
} catch (SaslException e) {LOG.error("SASL authentication with Zookeeper Quorum member failed: " + e);
state = States.AUTH_FAILED;
sendAuthEvent = true;
}
}
KeeperState authState = zooKeeperSaslClient.getKeeperState();
if (authState != null) {
if (authState == KeeperState.AuthFailed) {
// An authentication error occurred during authentication with the Zookeeper Server.
state = States.AUTH_FAILED;
sendAuthEvent = true;
} else {
if (authState == KeeperState.SaslAuthenticated) {
sendAuthEvent = true;
}
}
}
if (sendAuthEvent == true) {
eventThread.queueEvent(new WatchedEvent(
Watcher.Event.EventType.None,
authState,null));
}
}
to = readTimeout - clientCnxnSocket.getIdleRecv();
} else {
to = connectTimeout - clientCnxnSocket.getIdleRecv();
}
if (to <= 0) {
String warnInfo;
warnInfo = "Client session timed out, have not heard from server in "
+ clientCnxnSocket.getIdleRecv()
+ "ms"
+ " for sessionid 0x"
+ Long.toHexString(sessionId);LOG.warn(warnInfo);
throw new SessionTimeoutException(warnInfo);
}
记录timeToNextPing 的时间
if (state.isConnected()) {
//1000(1 second) is to prevent race condition missing to send the second ping
//also make sure not to send too many pings when readTimeout is small
int timeToNextPing = readTimeout / 2 - clientCnxnSocket.getIdleSend() -
((clientCnxnSocket.getIdleSend() > 1000) ? 1000 : 0);
//send a ping request either time is due or no packet sent out within MAX_SEND_PING_INTERVAL
if (timeToNextPing <= 0 || clientCnxnSocket.getIdleSend() > MAX_SEND_PING_INTERVAL) {
sendPing();
clientCnxnSocket.updateLastSend();
} else {
if (timeToNextPing < to) {
to = timeToNextPing;
}
}
}
// If we are in read-only mode, seek for read/write server
if (state == States.CONNECTEDREADONLY) {
long now = System.currentTimeMillis();
int idlePingRwServer = (int) (now - lastPingRwServer);
if (idlePingRwServer >= pingRwTimeout) {
lastPingRwServer = now;
idlePingRwServer = 0;
pingRwTimeout =
Math.min(2*pingRwTimeout, maxPingRwTimeout);
pingRwServer();
}
to = Math.min(to, pingRwTimeout - idlePingRwServer);
} clientCnxnSocket.doTransport(to, pendingQueue, outgoingQueue, ClientCnxn.this);重点二
} catch (Throwable e) {//暂时忽略
if (closing) {
if (LOG.isDebugEnabled()) {
// closing so this is expectedLOG.debug("An exception was thrown while closing send thread for session 0x"
+ Long.toHexString(getSessionId())
+ " : " + e.getMessage());
}
break;
} else {
// this is ugly, you have a better way speak up
if (e instanceof SessionExpiredException) {LOG.info(e.getMessage() + ", closing socket connection");
} else if (e instanceof SessionTimeoutException) {LOG.info(e.getMessage() + RETRY_CONN_MSG);
} else if (e instanceof EndOfStreamException) {LOG.info(e.getMessage() + RETRY_CONN_MSG);
} else if (e instanceof RWServerFoundException) {LOG.info(e.getMessage());
} else {LOG.warn(
"Session 0x"
+ Long.toHexString(getSessionId())
+ " for server "
+ clientCnxnSocket.getRemoteSocketAddress()
+ ", unexpected error"
+ RETRY_CONN_MSG, e);
}
cleanup();
if (state.isAlive()) {
eventThread.queueEvent(new WatchedEvent(
Event.EventType.None,
Event.KeeperState.Disconnected,
null));
}
clientCnxnSocket.updateNow();
clientCnxnSocket.updateLastSendAndHeard();
}
}
}
cleanup();
clientCnxnSocket.close();
if (state.isAlive()) {
eventThread.queueEvent(new WatchedEvent(Event.EventType.None,
Event.KeeperState.Disconnected, null));
}
ZooTrace.logTraceMessage(LOG, ZooTrace.getTextTraceLevel(),
"SendThread exited loop for session: 0x"
+ Long.toHexString(getSessionId()));
}
跟踪startConnect()方法;
![47bfe209051eb0bae26adeea16692f91.png](https://i-blog.csdnimg.cn/blog_migrate/7ebae2bb5cb2469e0879f4ccceb0b632.jpeg)
![809f7e42407b5a92facf89ee8c667ee9.png](https://i-blog.csdnimg.cn/blog_migrate/ecf211725853986229fc15753554e68d.jpeg)
这里使用jdk nio的操作去连接:跟踪connect说明;
![8d4fbd6b83fbd48f1d647b4f036342f9.png](https://i-blog.csdnimg.cn/blog_migrate/b78db75e3a169a02df2f6102c7e96b60.jpeg)
大致意思如下:
如果此通道处于非阻塞模式,则对此通道的调用,方法启动非阻塞连接操作。 如果连接立即建立,就像本地连接一样,然后此方法返回<tt> true </ tt>。 否则,此方法返回 <tt> false </ tt>,稍后必须完成连接操作调用{@link #finishConnect finishConnect}方法。
如果此通道处于阻止模式,则此通道的调用方法将阻塞,直到建立连接或I / O错误发生。
既然是nio非阻塞调用,那么什么时候调用finishConnect方法呢?答案就是重点二了;
跟踪clientCnxnSocket.doTransport(to, pendingQueue, outgoingQueue, ClientCnxn.this);
![342cf114f84841791124f2736a0b0b15.png](https://i-blog.csdnimg.cn/blog_migrate/61ecab2123b519ce58d32b26f30f3276.jpeg)
看下方法说明
![e8bcfc8f0b4158c577fff360c501ab95.png](https://i-blog.csdnimg.cn/blog_migrate/04c0f648c2473608d9597c7cb61a4528.jpeg)
此时是判断socket是否真的连接;
之后执行sendThread.primeConnection();
最后执行outgoingQueue队列;
所以最前面的代码需要这样写;
![aa4b4f81d71e4c109410d35505ffa68d.png](https://i-blog.csdnimg.cn/blog_migrate/8dd6a718ab719cf0fb2312e5852aa88a.jpeg)
CountDownLatch 会防止因为网络问题连接没有生成 而去操作的异常。这也是zk创建异步的原因