1.通过new ZooKeeper()建立连接,通过多层的内部构造方法递进,最终锁定下图中的方法:
public ZooKeeper(
String connectString,
int sessionTimeout,
Watcher watcher,
boolean canBeReadOnly,
HostProvider hostProvider,
ZKClientConfig clientConfig
) throws IOException {
......
cnxn = createConnection(
connectStringParser.getChrootPath(),
hostProvider,
sessionTimeout,
this.clientConfig,
watcher,
getClientCnxnSocket(),
canBeReadOnly);
cnxn.start();
}
聚焦最下面cnxn = createConnection()方法,返回的是ClientCnxn类的实例(ClientCnxn是zk客户端的核心类,Zookeeper类算是个入口和门面类),下面分析ClientCnxn的start()方法。
2.ClientCnxn类的start()方法:
public void start() {
sendThread.start();
eventThread.start();
}
sendThread和eventThread都是ClientCnxn类中的两个内部类的实例,并且两个类都是采用单开线程进行while(true)循环的工作方式。
SendThread:理解为负责c和s端通信的相关工作内容。
EventThread:理解为对产生事件和callback的单线程异步回调的工作。
特此说明:
通过对SendThread和EventThread的阅读,能够看出异步模型采用的是生产者消费者模式实现。
3.接下来我们先进入SendThread的run()中看下如何与server建立连接(run() 方法很长,我们挑重点说)
public void run() {
......
//state的在new ClientCnxn()中已经由NOT_CONNECTED重置为CONNECTING状态,所以定会进入循环
while (state.isAlive()) {
try {
if (!clientCnxnSocket.isConnected()) {
// don't re-establish connection if we are closing
if (closing) {
break;
}
if (rwServerAddress != null) {
serverAddress = rwServerAddress;
rwServerAddress = null;
} else {
serverAddress = hostProvider.next(1000);
}
onConnecting(serverAddress);
//重点一:看到没有,此处开始建立连接
startConnect(serverAddress);
clientCnxnSocket.updateLastSendAndHeard();
}
if (state.isConnected()) {......} else {
......
}
......
//重点二:doTransprot()方法 ,此处 clientCnxnSocket的实现类为ClientCnxnSocketNIO
clientCnxnSocket.doTransport(to, pendingQueue, ClientCnxn.this);
} catch (Throwable e) {......}
}
......
}
通过上面代码注释中‘的重点一’和‘重点二’,这两处是通过NIO的selector模型建立通信。
4.重点一(看注释):
startConnect(serverAddress):
private void startConnect(InetSocketAddress addr) throws IOException {
......//以上忽略,是一些辅助判断和获取address的逻辑
//clientCnxnSocketNIO的connect(addr)
clientCnxnSocket.connect(addr);
}
clientCnxnSocketNIO的connect方法:
void connect(InetSocketAddress addr) throws IOException {
//look!!!,终于开始通过jdk NIO 的socket建立与server的连接了
SocketChannel sock = createSock();
try {
//把SocketChannel注册到selector上,并关注OP_CONNECT事件,由于是非阻塞的,不会等到连接建立完成
registerAndConnect(sock, addr);
} catch (UnresolvedAddressException | UnsupportedAddressTypeException | SecurityException | IOException e) {
......
}
......
}
5.重点二(看注释):
clientCnxnSocketNIO的doTransport方法:
void doTransport(
int waitTimeOut,
Queue<Packet> pendingQueue,
ClientCnxn cnxn) throws IOException, InterruptedException {
selector.select(waitTimeOut);
Set<SelectionKey> selected;
//获取有可操作的socket对象
synchronized (this) {
selected = selector.selectedKeys();
}
......
for (SelectionKey k : selected) {
SocketChannel sc = ((SocketChannel) k.channel());
//建立连接的流程看此处
if ((k.readyOps() & SelectionKey.OP_CONNECT) != 0) {
if (sc.finishConnect()) {
......
//建立了主链接后的相关初始化操作,向server端发送第一个packet包,并执行sockKey.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
sendThread.primeConnection();
}
} else if ((k.readyOps() & (SelectionKey.OP_READ | SelectionKey.OP_WRITE)) != 0) {
//此方法也很重要,发送操作命令和接收server反馈时关注此方法,后面文章中跟踪
doIO(pendingQueue, cnxn);
}
}
if (sendThread.getZkState().isConnected()) {
if (findSendablePacket(outgoingQueue, sendThread.tunnelAuthInProgress()) != null) {
enableWrite();
}
}
selected.clear();
}