ClientCnxn是客户端的类:该类管理zk客户端的socket io,维持一个可用服务器的列表。

//ClientCnxn类
private final LinkedList<Packet> outgoingQueue = new LinkedList<Packet>(); //待发送
private final LinkedList<Packet> pendingQueue = new LinkedList<Packet>(); //发送后等待响应
final SendThread sendThread;
final EventThread eventThread;

Packet封装了请求、响应以及回调等。

static class Packet {
//省略其他代码
RequestHeader requestHeader;
ReplyHeader replyHeader;
Record request;
Record response;
ByteBuffer bb;
String clientPath;
String serverPath;
boolean finished;
AsyncCallback cb;
Object ctx;
WatchRegistration watchRegistration;
public boolean readOnly;
Packet(RequestHeader requestHeader, ReplyHeader replyHeader,
Record request, Record response,
WatchRegistration watchRegistration) {
this(requestHeader, replyHeader, request, response,
watchRegistration, false);
}
Packet(RequestHeader requestHeader, ReplyHeader replyHeader,
Record request, Record response,
WatchRegistration watchRegistration, boolean readOnly) {
this.requestHeader = requestHeader;
this.replyHeader = replyHeader;
this.request = request;
this.response = response;
this.readOnly = readOnly;
this.watchRegistration = watchRegistration;
}
}

ClientCnxn类中有SendThread和EventThread两个线程,SendThread负责io(发送和接收),EventThread负责事件处理。

Packet在outgoingQueue和pendingQueue之间流转:

首先,调用线程把Packet放在outgoingQueue中,SendThread从outgoingQueue中取出Packet并发送,然后把该Packet放入pendingQueue中,等待响应,当响应来到时,解析响应,并从pendingQueue中删除Packet。

示例代码:

public class MyZK {
public static void main(String[] args) throws IOException, KeeperException, InterruptedException {
ZooKeeper zk = new ZooKeeper("127.0.0.1:2181", 60000000, new MyWatcher()); List<ACL> list = new ArrayList<ACL>(1);
list.add(new ACL(Perms.ALL, Ids.ANYONE_ID_UNSAFE));
//CreateResponse
String create = zk.create("/zhang/hello", "hello".getBytes(), list, CreateMode.PERSISTENT);
System.out.println(create); //GetDataResponse
// 添加了watch。删除该节点的时候,服务端会发通知
byte[] data = zk.getData("/zhang/hello", true, null);
System.out.println(new String(data)); Stat stat = zk.exists("/zhang/hello", false);
System.out.println(stat); zk.delete("/zhang/hello", -1);
} static class MyWatcher implements Watcher {
public void process(WatchedEvent event) {
System.out.println(event);
}
}
}

getData 调用栈:

// ZooKeeper.getData
public byte[] getData(final String path, Watcher watcher, Stat stat)
throws KeeperException, InterruptedException
{
final String clientPath = path;
PathUtils.validatePath(clientPath); // the watch contains the un-chroot path
WatchRegistration wcb = null;
if (watcher != null) {
wcb = new DataWatchRegistration(watcher, clientPath);
} final String serverPath = prependChroot(clientPath); RequestHeader h = new RequestHeader(); //请求头
h.setType(ZooDefs.OpCode.getData);
GetDataRequest request = new GetDataRequest(); //请求
request.setPath(serverPath);
request.setWatch(watcher != null);
GetDataResponse response = new GetDataResponse(); //响应
ReplyHeader r = cnxn.submitRequest(h, request, response, wcb); //发送请求,阻塞,等待响应
if (r.getErr() != 0) {
throw KeeperException.create(KeeperException.Code.get(r.getErr()),
clientPath);
}
if (stat != null) {
DataTree.copyStat(response.getStat(), stat);
}
return response.getData();
}

调用线程把请求放入outgoingQueue队列中,然后阻塞:

public ReplyHeader submitRequest(RequestHeader h, Record request,
Record response, WatchRegistration watchRegistration)
throws InterruptedException {
ReplyHeader r = new ReplyHeader();
//创建Packet,并放入outgoingQueue中
Packet packet = queuePacket(h, r, request, response, null, null, null,
null, watchRegistration);
//先获取锁
synchronized (packet) {
while (!packet.finished) {
//等待,并释放锁
packet.wait();
}
}
return r;
}

SendThread负责io操作,发送请求,接收响应。

发送:把outgoingQueue中的packet发送出去,然后把packet放到pendingQueue中,等待响应。

接收:解析响应,从pendingQueue中删除packet。

// ClientCnxnSocketNIO.doIO
void doIO(List<Packet> pendingQueue, LinkedList<Packet> outgoingQueue, ClientCnxn cnxn)
throws InterruptedException, IOException {
SocketChannel sock = (SocketChannel) sockKey.channel();
if (sock == null) {
throw new IOException("Socket is null!");
}
if (sockKey.isReadable()) {
//读取响应
int rc = sock.read(incomingBuffer);
if (rc < 0) {
throw new EndOfStreamException(
"Unable to read additional data from server sessionid 0x"
+ Long.toHexString(sessionId)
+ ", likely server has closed socket");
}
if (!incomingBuffer.hasRemaining()) {
incomingBuffer.flip();
if (incomingBuffer == lenBuffer) {
recvCount++;
readLength();
} else if (!initialized) {
readConnectResult();
enableRead();
if (findSendablePacket(outgoingQueue,
cnxn.sendThread.clientTunneledAuthenticationInProgress()) != null) {
enableWrite();
}
lenBuffer.clear();
incomingBuffer = lenBuffer;
updateLastHeard();
initialized = true;
} else {
//解析响应
sendThread.readResponse(incomingBuffer);
lenBuffer.clear();
incomingBuffer = lenBuffer;
updateLastHeard();
}
}
}
if (sockKey.isWritable()) {
//发送请求
synchronized(outgoingQueue) {
Packet p = findSendablePacket(outgoingQueue,
cnxn.sendThread.clientTunneledAuthenticationInProgress()); if (p != null) {
updateLastSend();
// If we already started writing p, p.bb will already exist
if (p.bb == null) {
if ((p.requestHeader != null) &&
(p.requestHeader.getType() != OpCode.ping) &&
(p.requestHeader.getType() != OpCode.auth)) {
p.requestHeader.setXid(cnxn.getXid());
}
p.createBB();
}
sock.write(p.bb);
if (!p.bb.hasRemaining()) {
sentCount++;
//从outgoingQueue中删除packet
outgoingQueue.removeFirstOccurrence(p);
if (p.requestHeader != null
&& p.requestHeader.getType() != OpCode.ping
&& p.requestHeader.getType() != OpCode.auth) {
synchronized (pendingQueue) {
//把packet加入到pendingQueue中
pendingQueue.add(p);
}
}
}
}
if (outgoingQueue.isEmpty()) {
disableWrite();
} else if (!initialized && p != null && !p.bb.hasRemaining()) {
disableWrite();
} else {
// Just in case
enableWrite();
}
}
}
}

解析响应:

//SendThread类
void readResponse(ByteBuffer incomingBuffer) throws IOException {
//省略其他代码
ByteBufferInputStream bbis = new ByteBufferInputStream(incomingBuffer);
BinaryInputArchive bbia = BinaryInputArchive.getArchive(bbis);
ReplyHeader replyHdr = new ReplyHeader();
replyHdr.deserialize(bbia, "header"); // -1表示notification
if (replyHdr.getXid() == -1) {
//反序列化,设置WatcherEvent对象
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);
//放入eventThread的队列
eventThread.queueEvent(we);
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.");
} 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) {
//反序列化设置response内容
packet.response.deserialize(bbia, "response");
}
} finally {
finishPacket(packet);
}
}

调用线程现在还阻塞着呢,需要有人唤醒:

private void finishPacket(Packet p) {
if (p.watchRegistration != null) {
p.watchRegistration.register(p.replyHeader.getErr());
} if (p.cb == null) {
synchronized (p) {
p.finished = true;
//唤醒调用线程
p.notifyAll();
}
} else {
p.finished = true;
eventThread.queuePacket(p);
}
}

问题:

SendThread是按顺序发送outgoingQueue中的Packet,然后放入pendingQueue中,但是接收到的响应未必是按顺序的(网络环境中先发的数据包未必先到达吧),

zk就这么自信收到的响应和pendingQueue中的顺序一致?

zk客户端的ClientCnxn类的更多相关文章

  1. ZK客户端脚本的简单使用

    sh zkCli.sh [-server ip:port] :连接节点zk客户端[-server ip:port 用于连接集群中指定节点的客户端] 1.创建节点 create [-s] [-e] pa ...

  2. 利用zk客户端删除solr shard

    进入zk客户端 ./bin/zkCli.sh -server ip:2181 显示所有的内容: ls / 删除数据: rmr /filename path

  3. 第4章 ZK基本特性与基于Linux的ZK客户端命令行学习

    第4章 ZK基本特性与基于Linux的ZK客户端命令行学习 4-1 zookeeper常用命令行操作 4-2 session的基本原理与create命令的使用

  4. zk客户端及锁的使用

    1.生成zk客户端对象 private CuratorFramework buildClient() { logger.info("zookeeper registry center ini ...

  5. ZK客户端

    说明:本文为读<从Paxos到Zookeeper 分布式一致性原理与实践>读书笔记 shell操作 Java客户端 原始API pom文件: <dependency> < ...

  6. 即时聊天IM之四 Android客户端IM帮助类编写

    图文无关一起娱乐: 这一篇我们开始写Android端的Smack版主类,后面Android的IM功能都是通过这个帮助类实现的 引用类库: 因为我用的是IDE是Android Studio,所以我通过g ...

  7. socket系列之客户端socket——Socket类

    假设TCP套接字服务器端已经建立好并正在监听客户端的连接了,那么客户端就可以通过Socket类来发起连接.客户端发起一个连接请求后,就被动地在等待服务器的响应.这个类同样位于java.net包中,包含 ...

  8. dubbo使用的zk客户端

    在使用dubbo的过程中,当注册中心的数据修改后,新的配置是怎样刷到consumer和provider的?本文以consumer为例,进行分析. dubbo使用的是zkclient的jar,而zkcl ...

  9. 第4章 ZK基本特性与基于Linux的ZK客户端命令行学习 4-2 session的基本原理与create命令的使用

    客户端与服务端之间存在的连接,那么这样的一个连接我们就称之为会话,也就是session.其实就相当于是我们在做JSP或者说是Service的时候,那么服务端是Servlet,客户端使用的是浏览器.浏览 ...

随机推荐

  1. Mysql 更改编码方式

    Mysql 更改编码方式 --查看编码方式 show variables like 'char%'; --设置编码方式 set character_set_server=utf8;

  2. Python3 tesseract加载chi_sim异常停止工作

    Python3 tesseract加载chi_sim异常停止工作 原因: chi_sim.traineddata 和 tesseract3.0.2 版本不一致: 解决方案: 下载tesseract3. ...

  3. Python3 爬取微信好友基本信息,并进行数据清洗

    Python3 爬取微信好友基本信息,并进行数据清洗 1,登录获取好友基础信息: 好友的获取方法为get_friends,将会返回完整的好友列表. 其中每个好友为一个字典 列表的第一项为本人的账号信息 ...

  4. Java第一次实验 20145104张家明

    Java第一次实验 实验报告 实验要求: 1.使用JDK编译.运行简单的Java程序 2.使用IDEA 编辑.编译.运行.调试Java程序 实验内容: 1.使用JDK编译.运行简单的Java程序: 2 ...

  5. 20145324王嘉澜 《网络对抗技术》 MAL_逆向与Bof基础

    实践目标 •本次实践的对象是一个名为pwn1的linux可执行文件. •该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串. •该程序同时包含另一个代码片段,get ...

  6. Inversion of Control Containers and the Dependency Injection pattern

    https://martinfowler.com/articles/injection.html One of the entertaining things about the enterprise ...

  7. LOJ#2632. 「BalticOI 2011 Day1」打开灯泡 Switch the Lamp On

    题目描述 译自 BalticOI 2011 Day1 T3「Switch the Lamp On」有一种正方形的电路元件,在它的两组相对顶点中,有一组会用导线连接起来,另一组则不会.有 N×M 个这样 ...

  8. BZOJ5281: [Usaco2018 Open]Talent Show 01分数规划+01背包

    Description FarmerJohn要带着他的N头奶牛,方便起见编号为1…N,到农业展览会上去,参加每年的达牛秀!他的第i头奶牛重 量为wi,才艺水平为ti,两者都是整数.在到达时,Farme ...

  9. about MySQL Workbench的基本使用及运用操作

    http://blog.csdn.net/dongdong9223/article/details/48318877   <大神整理的更好!(评论里还有其他整理的) ↑ 使用MySQL Work ...

  10. Unity3D学习笔记(二十三):事件接口、虚拟摇杆、层级管理和背包系统

    事件接口 IDragHandler(常用):鼠标按下拖动时执行(只要鼠标在拖动就一直执行) IDropHandler:对象拖动结束时,如果鼠标在物体的范围内,执行一次(依赖于IDragHandler存 ...