Zookeeper会话–zookeeper会话创建以及会话管理
之前分析了zookeeper服务器集群的启动,可以说峰回路转,算是把服务器启动的流程熟悉了一下,那么我们需要怎么连接到服务器集群开启一次客户端连接的会话呢?这次就来分析一下zookeeper的会话过程。
Zookeeper客户端:
无论我们使用zookeeper的zkclient客户端还是acutor,其实都是对zookeeper的API进行了二次封装,将原来复杂的API操作封装为可供客户端调用的API,所以,在分析连接zookeeper集群时还需要了解下zookeeper的客户端API是如何封装的,这次则使用zkclient这个zookeeper客户端API进行分析:
首先,当使用zk客户端的时候会去创建一个ZKClient,这个可以说这是一个很好的入口,基本上从客户端连接到服务器集群需要的就是通过API来连接:
这里可以看到,ZkClient继承了Watcher接口,Java其实通过事件监听的方式实现了对于Watcher的监听,不再像原生API那样需要传入一个对象了,也就是所说屏蔽了原来原生API中对Watcher的传入,这样使用Listener的方式更加贴近Java工程师的习惯。那看下其构造方法吧:
一般的我们会使用这三个构造参数来初始化ZkClient的对象,在平时也基本使用服务器地址的字符串,连接超时和会话超时三个参数,但是最后都会调用下面的构造方法:
public ZkClient(final IZkConnection zkConnection, final int connectionTimeout, final ZkSerializer zkSerializer, final long operationRetryTimeout) {
if (zkConnection == null) {
throw new NullPointerException("Zookeeper connection is null!");
}
_connection = zkConnection;
_zkSerializer = zkSerializer;
_operationRetryTimeoutInMillis = operationRetryTimeout;
_isZkSaslEnabled = isZkSaslEnabled();
connect(connectionTimeout, this);
}
可以看到上面实例化了zkConnection对象,也就是说明从开始创建到上面的初始化方法,首先要保证的是zkConnnection不能是null,可以看到最后调用了connect方法:
public void connect(final long maxMsToWaitUntilConnected, Watcher watcher) throws ZkInterruptedException, ZkTimeoutException, IllegalStateException {
boolean started = false;
acquireEventLock();
try {
setShutdownTrigger(false);
_eventThread = new ZkEventThread(_connection.getServers());
_eventThread.start();
_connection.connect(watcher);
LOG.debug("Awaiting connection to Zookeeper server");
boolean waitSuccessful = waitUntilConnected(maxMsToWaitUntilConnected, TimeUnit.MILLISECONDS);
if (!waitSuccessful) {
throw new ZkTimeoutException("Unable to connect to zookeeper server '" + _connection.getServers() + "' with timeout of " + maxMsToWaitUntilConnected + " ms");
}
started = true;
} finally {
getEventLock().unlock();
// we should close the zookeeper instance, otherwise it would keep
// on trying to connect
if (!started) {
close();
}
}
}
首先启动了一个新的守护线程(zkEentThread):
ZkEventThread(String name) {
setDaemon(true);
setName("ZkClient-EventThread-" + getId() + "-" + name);
}
然后调用ZkConnection的connect方法,开始真正的连接:
public void connect(Watcher watcher) {
_zookeeperLock.lock();
try {
if (_zk != null) {
throw new IllegalStateException("zk client has already been started");
}
try {
LOG.debug("Creating new ZookKeeper instance to connect to " + _servers + ".");
_zk = new ZooKeeper(_servers, _sessionTimeOut, watcher);
} catch (IOException e) {
throw new ZkException("Unable to connect to " + _servers, e);
}
} finally {
_zookeeperLock.unlock();
}
}
可以看到,在这里开始真正的初始化ZooKeeper的实例,追溯到上文的_connection.connect(timeconnectionout, this)可以看出,这里是把zkClient注册到ZooKeeper中,因为ZkClient实现了Watcher接口,所以这个zkclient客户端可以去监听Watcher中变化的事件。可以比较下zookeeper原生API的初始化方式:
可以看到,如果使用zkclint去创建,创建对话的过程确实容易了很多,接着看zkclient相关的内容,刚才说道创建Zooeeper的对象,那么通过其构造函数可以看下是怎么构建这个对象的:
public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher,
boolean canBeReadOnly)
throws IOException
{
LOG.info("Initiating client connection, connectString=" + connectString
+ " sessionTimeout=" + sessionTimeout + " watcher=" + watcher);
watchManager.defaultWatcher = watcher;
ConnectStringParser connectStringParser = new ConnectStringParser(
connectString);
HostProvider hostProvider = new StaticHostProvider(
connectStringParser.getServerAddresses());
cnxn = new ClientCnxn(connectStringParser.getChrootPath(),
hostProvider, sessionTimeout, this, watchManager,
getClientCnxnSocket(), canBeReadOnly);
cnxn.start();
}
可以看到最终调用的这个构造方法,那么也就明晰了,zkclient确实简化了连接zk的过程,使用方便,其实到这里才真正的去构造ZooKeeper对象去真正的连接服务器集群。
ZooKeeper创建对话:
接着上面的代码分析:首先,设置了默认的watcher,如果我们通过zkclient访问的,显然此时的watcher就是zkclient对象。之后,通过ConnectStringParser去解析传入的字符串,其实也就是解析配置的集群的地址,将这些地址保存在connectStringParser中,之后将解析的结果再次封装未HostProvider对象:
public StaticHostProvider(Collection<InetSocketAddress> serverAddresses) {
this.resolver = new Resolver() {
@Override
public InetAddress[] getAllByName(String name) throws UnknownHostException {
return InetAddress.getAllByName(name);
}
};
init(serverAddresses);
}
接着看下init方法:
private void init(Collection<InetSocketAddress> serverAddresses) {
if (serverAddresses.isEmpty()) {
throw new IllegalArgumentException(
"A HostProvider may not be empty!");
}
this.serverAddresses.addAll(serverAddresses);
Collections.shuffle(this.serverAddresses);
}
可以看到,将传入的服务器地址解析结果保存在了HostProvider对象中。之后则创建了客户端网络连接器cnxn,通过其构造参数可以看出,目前缺少socket,而调用getClientCnxnSocket方法得到一个socket,所以顺便一看下其是怎么得到通信使用的socket对象的:
private static ClientCnxnSocket getClientCnxnSocket() throws IOException {
String clientCnxnSocketName = System
.getProperty(ZOOKEEPER_CLIENT_CNXN_SOCKET);
if (clientCnxnSocketName == null) {
clientCnxnSocketName = ClientCnxnSocketNIO.class.getName();
}
try {
return (ClientCnxnSocket) Class.forName(clientCnxnSocketName).getDeclaredConstructor()
.newInstance();
} catch (Exception e) {
IOException ioe = new IOException("Couldn't instantiate "
+ clientCnxnSocketName);
ioe.initCause(e);
throw ioe;
}
}
这里可以看出,默认状态下选择的是NOI的方式,所以启动的是ClientCnxnSocketNIO对对象,如果配置了其他的客户端网络连接器,则会从属性中得到其对应的CnxnSocketNIO来实现socket的连接。
public ClientCnxn(String chrootPath, HostProvider hostProvider, int sessionTimeout, ZooKeeper zooKeeper,
ClientWatchManager watcher, ClientCnxnSocket clientCnxnSocket, boolean canBeReadOnly)
throws IOException {
this(chrootPath, hostProvider, sessionTimeout, zooKeeper, watcher,
clientCnxnSocket, 0, new byte[16], canBeReadOnly);
}
//
public ClientCnxn(String chrootPath, HostProvider hostProvider, int sessionTimeout, ZooKeeper zooKeeper,
ClientWatchManager watcher, ClientCnxnSocket clientCnxnSocket,
long sessionId, byte[] sessionPasswd, boolean canBeReadOnly) {
this.zooKeeper = zooKeeper;
this.watcher = watcher;
this.sessionId = sessionId;
this.sessionPasswd = sessionPasswd;
this.sessionTimeout = sessionTimeout;
this.hostProvider = hostProvider;
this.chrootPath = chrootPath;
connectTimeout = sessionTimeout / hostProvider.size();
readTimeout = sessionTimeout * 2 / 3;
readOnly = canBeReadOnly;
sendThread = new SendThread(clientCnxnSocket);
eventThread = new EventThread();
}
其实在这个客户端连接器中,重要的部分就是zookeeper这个对象,此时交给了客户端连接器来管理,同时新创建了SendThread和EventThread。先看下SendThread:
SendThread(ClientCnxnSocket clientCnxnSocket) {
super(makeThreadName("-SendThread()"));
state = States.CONNECTING;
this.clientCnxnSocket = clientCnxnSocket;
setDaemon(true);
}
///
EventThread() {
super(makeThreadName("-EventThread"));
setDaemon(true);
}
这里可以知道,我们之前在ZooKeeper出使化创建的连接器的socket此时被传递给了ClientCnxn,此时客户端连接器有了发送消息的通道–SendThread,同样的,EventThread线程也存在与客户端连接器中,负责监听各种变化产生的事件,如 None (-1),NodeCreated (1),NodeDeleted (2),NodeDataChanged (3),NodeChildrenChanged (4),DataWatchRemoved (5), ChildWatchRemoved (6);这样可以说socket的通信到我们的客户端连接器已经打通了。此时客户端的状态设置为CONNECTING。最后开始启动:
public void start() {
sendThread.start();
eventThread.start();
}
那分别看下run方法做了什么事情吧,首先看下SendThread线程的run方法吧:
@Override
public void run() {
// final LinkedBlockingDeque<Packet> outgoingQueue = new LinkedBlockingDeque<Packet>();
// 客户端对象构造时就会创建一个唯一的outgoingQueue
clientCnxnSocket.introduce(this, sessionId, outgoingQueue);
// Time.currentElapsedTime() 开始计时
clientCnxnSocket.updateNow();
// 初始化,还是用于计时发送时间使用的:定义如下:
// void updateLastSendAndHeard() {
// this.lastSend = now;
// this.lastHeard = now;
// }
clientCnxnSocket.updateLastSendAndHeard();
int to;
long lastPingRwServer = Time.currentElapsedTime();
final int MAX_SEND_PING_INTERVAL = 10000; //10 seconds
InetSocketAddress serverAddress = null;
//state表示ZooKeeper客户端状态,CONNECTING, ASSOCIATING, CONNECTED, CONNECTEDREADONLY,
//CLOSED, AUTH_FAILED, NOT_CONNECTED
//只要不是CLOSED和AUTH_FAILED,都表示客户端值alive的,就一直进行循环发送数据
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 {
// 开始链接时,rwServerAddress为null, 获取hostProvider中的地址
serverAddress = hostProvider.next(1000);
}
// 开始尝试连接
startConnect(serverAddress);
// 更新下时间,便于统计操作耗时
clientCnxnSocket.updateLastSendAndHeard();
}
// 如果是连接状态
if (state.isConnected()) {
// determine whether we need to send an AuthFailed event.
// 检测客户端是否是开启了sasl认证类型的客户端
if (zooKeeperSaslClient != null) {
boolean sendAuthEvent = false;
// 如果客户端为开启了sasl认证的客户端,则sasl状态设置为初始化INITIAL
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();
//无论授权成功还是失败,都需要发送authEvernt
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) {
// 向eventThread队列中,本质上接受事件的队列为:
//final LinkedBlockingQueue<Object> waitingEvents =
// new LinkedBlockingQueue<Object>();
// zookeeper 客户端支持的数据类型:
// None (-1),NodeCreated (1),NodeDeleted (2),
//NodeDataChanged (3),NodeChildrenChanged (4),
//DataWatchRemoved (5), ChildWatchRemoved (6);
eventThread.queueEvent(new WatchedEvent(
Watcher.Event.EventType.None,
authState,null));
if (state == States.AUTH_FAILED) {
eventThread.queueEventOfDeath();
}
}
}
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);
}
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) {
// 发送ping消息,检测连接是否还存在
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 = Time.currentElapsedTime();
int idlePingRwServer = (int) (now - lastPingRwServer);
if (idlePingRwServer >= pingRwTimeout) {
lastPingRwServer = now;
idlePingRwServer = 0;
pingRwTimeout =
Math.min(2*pingRwTimeout, maxPingRwTimeout);
// ping 读写权限的服务器
pingRwServer();
}
to = Math.min(to, pingRwTimeout - idlePingRwServer);
}
// doTransport--> ClientCnxnSocketNIO#doTransport--> doIO
clientCnxnSocket.doTransport(to, pendingQueue, ClientCnxn.this);
// 错误处理
} catch (Throwable e) {
if (closing) {
if (LOG.isDebugEnabled()) {
// closing so this is expected
LOG.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 if (e instanceof SocketException) {
LOG.info("Socket error occurred: {}: {}", serverAddress, e.getMessage());
} else {
LOG.warn("Session 0x{} for server {}, unexpected error{}",
Long.toHexString(getSessionId()),
serverAddress,
RETRY_CONN_MSG,
e);
}
// At this point, there might still be new packets appended to outgoingQueue.
// they will be handled in next connection or cleared up if closed.
cleanAndNotifyState();
}
}
}
// 关闭socket,并通过事件通知客户端
synchronized (state) {
// When it comes to this point, it guarantees that later queued
// packet to outgoingQueue will be notified of death.
cleanup();
}
clientCnxnSocket.close();
if (state.isAlive()) {
eventThread.queueEvent(new WatchedEvent(Event.EventType.None,
Event.KeeperState.Disconnected, null));
}
eventThread.queueEvent(new WatchedEvent(Event.EventType.None,
Event.KeeperState.Closed, null));
ZooTrace.logTraceMessage(LOG, ZooTrace.getTextTraceLevel(),
"SendThread exited loop for session: 0x"
+ Long.toHexString(getSessionId()));
}
上述的过程概括起来说就是:首先创建了sendThread和EventThread,然后启动了这两个守护线程,其中sendThread不断的向outgoingQueue中放入要发送的Packet,eventThread则是不断将watcher和event的pair放入eventQueue中。
其次,获取HostProvider的的地址,其实这个类或保留集群的信息,所以在上面的run方法中,获取地址后则会调用startConnection方法其开启连接,此时这里应用了委派模式,这里委派给了默认的zookeeper的NIO实现的客户端连接器。
private void startConnect(InetSocketAddress addr) throws IOException {
// initializing it for new connection
saslLoginFailed = false;
// 是否第一次连接,默认为true
if(!isFirstConnect){
try {
Thread.sleep(r.nextInt(1000));
} catch (InterruptedException e) {
LOG.warn("Unexpected exception", e);
}
}
// 如果不是第一次连接,可能当前客户端的状态为其他状态,如CLOSE等,此时
// 如果继续执行启动逻辑,那需要先改变当前客户端的状态
// 更改当前该客户端的状态
state = States.CONNECTING;
String hostPort = addr.getHostString() + ":" + addr.getPort();
MDC.put("myid", hostPort);
setName(getName().replaceAll("\\(.*\\)", "(" + hostPort + ")"));
if (clientConfig.isSaslClientEnabled()) {
try {
if (zooKeeperSaslClient != null) {
zooKeeperSaslClient.shutdown();
}
zooKeeperSaslClient = new ZooKeeperSaslClient(SaslServerPrincipal.getServerPrincipal(addr, clientConfig),
clientConfig);
} catch (LoginException e) {
// An authentication error occurred when the SASL client tried to initialize:
// for Kerberos this means that the client failed to authenticate with the KDC.
// This is different from an authentication error that occurs during communication
// with the Zookeeper server, which is handled below.
LOG.warn("SASL configuration failed: " + e + " Will continue connection to Zookeeper server without "
+ "SASL authentication, if Zookeeper server allows it.");
eventThread.queueEvent(new WatchedEvent(
Watcher.Event.EventType.None,
Watcher.Event.KeeperState.AuthFailed, null));
saslLoginFailed = true;
}
}
// 打印连接状态
logStartConnect(addr);
// 连接开始,打开socket
clientCnxnSocket.connect(addr);
}
最后基于初始化得到的客户端连接器的socket建立连接:
void connect(InetSocketAddress addr) throws IOException {
SocketChannel sock = createSock();
try {
// 注册和连接
registerAndConnect(sock, addr);
} catch (UnresolvedAddressException | UnsupportedAddressTypeException | SecurityException | IOException e) {
LOG.error("Unable to open socket to {}" + addr);
sock.close();
throw e;
}
initialized = false;
/*
* Reset incomingBuffer
*/
lenBuffer.clear();
incomingBuffer = lenBuffer;
}
其真正的执行方法是registerAndConnect方法,但是在其方法的逻辑中可以知道,再注册后判断了是否之已经连接了,如果连接则调用primeConnection方法开始初始化会话的session,watchers等,可以这么理解,前者的socket连接只是打通了通信通道,但是真正的会话即两个进程之间的信息通信还没建立,所以下面的方法就完成了会话的建立,正如前面所说,连接请求会被放入到outgoingQueue队列中,等待发送。
void primeConnection() throws IOException {
// 连接端口
LOG.info("Socket connection established, initiating session, client: {}, server: {}",
clientCnxnSocket.getLocalSocketAddress(),
clientCnxnSocket.getRemoteSocketAddress());
isFirstConnect = false;
//创建sessionid:之前时候有连接过的sessionid,如果有则使用上次的sessionid没有这设置为0
// 所以在第一次建立连接的时候,sessionid为0
long sessId = (seenRwServerBefore) ? sessionId : 0;
// 创建连接请求
ConnectRequest conReq = new ConnectRequest(0, lastZxid,
sessionTimeout, sessId, sessionPasswd);
// We add backwards since we are pushing into the front
// Only send if there's a pending watch
// TODO: here we have the only remaining use of zooKeeper in
// this class. It's to be eliminated!
if (!clientConfig.getBoolean(ZKClientConfig.DISABLE_AUTO_WATCH_RESET)) {
......
}
SetWatches sw = new SetWatches(setWatchesLastZxid,
dataWatchesBatch,
existWatchesBatch,
childWatchesBatch);
RequestHeader header = new RequestHeader(-8, OpCode.setWatches);
Packet packet = new Packet(header, new ReplyHeader(), sw, null, null);
outgoingQueue.addFirst(packet);
}
}
}
// 如果设置了授权信息,则还需在请求的Packet中加入授权信息
for (AuthData id : authInfo) {
// requestHeader只有xid和type,type类型保存在OpCode中
outgoingQueue.addFirst(new Packet(new RequestHeader(-4,
OpCode.auth), null, new AuthPacket(0, id.scheme,
id.data), null, null));
}
// 添加到队列的头部,如果有授权信息的话,上述的授权信息的请求则跟随在连接请求后一个等待发出
outgoingQueue.addFirst(new Packet(null, null, conReq,
null, null, readOnly));
// 读写权限设置
clientCnxnSocket.connectionPrimed();
if (LOG.isDebugEnabled()) {
LOG.debug("Session establishment request sent on "
+ clientCnxnSocket.getRemoteSocketAddress());
}
}
服务器端响应:
前面的分析已经可以实现将Packet发送给服务器端了,但是服务器端得到消息Packet处理之后,客户端如何去响应呢?首先,客户端首先对自己的状态作出判断,判断自己时候已经和初始化,如果没有完成初始化则默认为是会话请求,这时候会直接交给readConnectResult方法去处理:
void readConnectResult() throws IOException {
if (LOG.isTraceEnabled()) {
StringBuilder buf = new StringBuilder("0x[");
for (byte b : incomingBuffer.array()) {
buf.append(Integer.toHexString(b) + ",");
}
buf.append("]");
LOG.trace("readConnectResult " + incomingBuffer.remaining() + " "
+ buf.toString());
}
ByteBufferInputStream bbis = new ByteBufferInputStream(incomingBuffer);
BinaryInputArchive bbia = BinaryInputArchive.getArchive(bbis);
ConnectResponse conRsp = new ConnectResponse();
// 反序列化
conRsp.deserialize(bbia, "connect");
// read "is read-only" flag
boolean isRO = false;
try {
isRO = bbia.readBool("readOnly");
} catch (IOException e) {
// this is ok -- just a packet from an old server which
// doesn't contain readOnly field
LOG.warn("Connected to an old server; r-o mode will be unavailable");
}
this.sessionId = conRsp.getSessionId();
sendThread.onConnected(conRsp.getTimeOut(), this.sessionId,
conRsp.getPasswd(), isRO);
}
如果是普通请求则交给readResponse方法去解析Response对象。然后开始处理Response,ClientCnxnSocket会对接收到的服务器响应进行反序列化然后得到ConnectResponse对象,并获取对话的Id。
void readResponse(ByteBuffer incomingBuffer) throws IOException {
ByteBufferInputStream bbis = new ByteBufferInputStream(
incomingBuffer);
BinaryInputArchive bbia = BinaryInputArchive.getArchive(bbis);
ReplyHeader replyHdr = new ReplyHeader();
replyHdr.deserialize(bbia, "header");
if (replyHdr.getXid() == -2) {
// -2 is the xid for pings
if (LOG.isDebugEnabled()) {
LOG.debug("Got ping response for sessionid: 0x"
+ Long.toHexString(sessionId)
+ " after "
+ ((System.nanoTime() - lastPingSentNs) / 1000000)
+ "ms");
}
return;
}
if (replyHdr.getXid() == -4) {
// -4 is the xid for AuthPacket
if(replyHdr.getErr() == KeeperException.Code.AUTHFAILED.intValue()) {
state = States.AUTH_FAILED;
eventThread.queueEvent( new WatchedEvent(Watcher.Event.EventType.None,
Watcher.Event.KeeperState.AuthFailed, null) );
}
if (LOG.isDebugEnabled()) {
LOG.debug("Got auth sessionid:0x"
+ Long.toHexString(sessionId));
}
return;
}
if (replyHdr.getXid() == -1) {
// -1 means notification
if (LOG.isDebugEnabled()) {
LOG.debug("Got notification sessionid:0x"
+ Long.toHexString(sessionId));
}
WatcherEvent event = new WatcherEvent();
event.deserialize(bbia, "response");
// convert from a server path to a client path
if (chrootPath != null) {
String serverPath = event.getPath();
if(serverPath.compareTo(chrootPath)==0)
event.setPath("/");
else if (serverPath.length() > chrootPath.length())
event.setPath(serverPath.substring(chrootPath.length()));
else {
LOG.warn("Got server path " + event.getPath()
+ " which is too short for chroot path "
+ chrootPath);
}
}
WatchedEvent we = new WatchedEvent(event);
if (LOG.isDebugEnabled()) {
LOG.debug("Got " + we + " for sessionid 0x"
+ Long.toHexString(sessionId));
}
eventThread.queueEvent( we );
return;
}
// If SASL authentication is currently in progress, construct and
// send a response packet immediately, rather than queuing a
// response as with other packets.
if (clientTunneledAuthenticationInProgress()) {
GetSASLRequest request = new GetSASLRequest();
request.deserialize(bbia,"token");
zooKeeperSaslClient.respondToServer(request.getToken(),
ClientCnxn.this);
return;
}
Packet packet;
synchronized (pendingQueue) {
if (pendingQueue.size() == 0) {
throw new IOException("Nothing in the queue, but got "
+ replyHdr.getXid());
}
packet = pendingQueue.remove();
}
/*
* Since requests are processed in order, we better get a response
* to the first request!
*/
try {
if (packet.requestHeader.getXid() != replyHdr.getXid()) {
packet.replyHeader.setErr(
KeeperException.Code.CONNECTIONLOSS.intValue());
throw new IOException("Xid out of order. Got Xid "
+ replyHdr.getXid() + " with err " +
+ replyHdr.getErr() +
" expected Xid "
+ packet.requestHeader.getXid()
+ " for a packet with details: "
+ packet );
}
packet.replyHeader.setXid(replyHdr.getXid());
packet.replyHeader.setErr(replyHdr.getErr());
packet.replyHeader.setZxid(replyHdr.getZxid());
if (replyHdr.getZxid() > 0) {
lastZxid = replyHdr.getZxid();
}
if (packet.response != null && replyHdr.getErr() == 0) {
packet.response.deserialize(bbia, "response");
}
if (LOG.isDebugEnabled()) {
LOG.debug("Reading reply sessionid:0x"
+ Long.toHexString(sessionId) + ", packet:: " + packet);
}
} finally {
finishPacket(packet);
}
}
根据对话的id去连接对话,如果此时对话连接成功需要做两件事:
- 通知SendThread线程进一步对会话参数进行设置,例如readTimeOut和connectTimeout,并再次更新客户端的状态。
- 需要通知地址管理器HostProvider已经成功连接的地址。
之后则会创建事件SyncConnected-None,用来通知上层应用会话创建成功,并将这个事件传递给EventThread线程。同样的为了将这个事件放入waitingEvent中,需要查询监听这个事件的watcher,然后将其组装成一个pair放入waitingEvent队列中,finishpacket会完成这些操作。之后,EventThread不断的从上述队列中取出待处理的watcher对象,然后直接调用process方法,达到触发watcher的目的。到这里客户端连接以及创建会话的过程全部完成了。
@Override
public void process(WatchedEvent event) {
LOG.debug("Received event: " + event);
_zookeeperEventThread = Thread.currentThread();
boolean stateChanged = event.getPath() == null;
boolean znodeChanged = event.getPath() != null;
boolean dataChanged = event.getType() == EventType.NodeDataChanged || event.getType() == EventType.NodeDeleted || event.getType() == EventType.NodeCreated
|| event.getType() == EventType.NodeChildrenChanged;
getEventLock().lock();
try {
// We might have to install child change event listener if a new node was created
if (getShutdownTrigger()) {
LOG.debug("ignoring event '{" + event.getType() + " | " + event.getPath() + "}' since shutdown triggered");
return;
}
if (stateChanged) {
processStateChanged(event);
}
if (dataChanged) {
processDataOrChildChange(event);
}
} finally {
if (stateChanged) {
getEventLock().getStateChangedCondition().signalAll();
// If the session expired we have to signal all conditions, because watches might have been removed and
// there is no guarantee that those
// conditions will be signaled at all after an Expired event
// TODO PVo write a test for this
if (event.getState() == KeeperState.Expired) {
getEventLock().getZNodeEventCondition().signalAll();
getEventLock().getDataChangedCondition().signalAll();
// We also have to notify all listeners that something might have changed
fireAllEvents();
}
}
if (znodeChanged) {
getEventLock().getZNodeEventCondition().signalAll();
}
if (dataChanged) {
getEventLock().getDataChangedCondition().signalAll();
}
getEventLock().unlock();
LOG.debug("Leaving process event");
}
}