以前自己的博客中转载、翻译或写过(不过自己才疏学浅,写的不好)一些 Zookeeper 方面的文章,但是都没有涉及到 Zookeeper C API 的内容,今天的这篇博客是我农历新年的第一篇技术博客,我想详细讲讲 Zookeeper C API 的使用规则和示例,算是把以前的旧帐还上吧 :-)

Zookeeper 官方页面上提供了一些编程指南和 API 文档,不过大部分都是 Java 示例,涉及 C API 的部分很少,只有在 ZooKeeper Programmer's Guide 中 ACL Permissions 一节讲了 Zookeeper C API 中设置 ACL 应该注意的事项,正是由于缺少Zookeeper C API 相关的资料,大部分 Zookeeper C/C++ 开发者只能通过阅读 Zookeeper C API 的源码来了解 C API 的使用方法,本文希望在此方面给大家提供一些便利,减少 Zookeeper 新手使用 C API 的困难和恐惧,当然我自己也是一枚新手啦 :-)。

废话不多说了,先从最基本的开始吧!

Zookeeper 伪分布式安装

首先是 Zookeeper 的安装,如果你没有足够的机器,建议在单机上通过伪分布式安装方法来模拟 Zookeeper 集群,我在这里提供给大家一个简单的安装包来降低伪分布式 Zookeeper 的安装难度,该安装包可以模拟一个 5 Zookeeper 实例的集群。下载zookeeper-3.4.0-pseudoclusters.tar.bz2,解压至 /tmp/ 目录(注意,安装包里面 dataDir 和 dataLogDir 配置已经预设为 /tmp/ 目录,如果你只是自己在 Zookeeper 上做一些小实验,建议不要更改该配置,这样在你做完了实验以后删除 /tmp/zookeeper/ 目录即可),操作方法如下:

forhappy@haiping-ict:/tmp$ pwd
/tmp
forhappy@haiping-ict:/tmp$ tar xvf zookeeper-3.4.0-pseudoclusters.tar.bz2
forhappy@haiping-ict:/tmp$ cd zookeeper/
forhappy@haiping-ict:/tmp/zookeeper$ ls
server001  server003  server005          stopZKCluster.sh
server002  server004  startZKCluster.sh

然后在 /tmp/zookeeper 目录下执行 startZKCluster.sh 启动 Zookeeper 服务,或者执行 stopZKCluster.sh 停掉 Zookeeper 服务。

启动 Zookeeper 服务:

forhappy@haiping-ict:/tmp/zookeeper$ ./startZKCluster.sh .
starting zookeeper instance server001...
JMX enabled by default
Using config: /tmp/zookeeper/server001/zookeeper-3.4.0/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED
starting zookeeper instance server002...
JMX enabled by default
Using config: /tmp/zookeeper/server002/zookeeper-3.4.0/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED
starting zookeeper instance server003...
JMX enabled by default
Using config: /tmp/zookeeper/server003/zookeeper-3.4.0/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED
starting zookeeper instance server004...
JMX enabled by default
Using config: /tmp/zookeeper/server004/zookeeper-3.4.0/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED
starting zookeeper instance server005...
JMX enabled by default
Using config: /tmp/zookeeper/server005/zookeeper-3.4.0/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED

停掉 Zookeeper 服务:

forhappy@haiping-ict:/tmp/zookeeper$ ./stopZKCluster.sh .
stopping zookeeper instance server001...
JMX enabled by default
Using config: /tmp/zookeeper/server001/zookeeper-3.4.0/bin/../conf/zoo.cfg
Stopping zookeeper ... STOPPED
stopping zookeeper instance server002...
JMX enabled by default
Using config: /tmp/zookeeper/server002/zookeeper-3.4.0/bin/../conf/zoo.cfg
Stopping zookeeper ... STOPPED
stopping zookeeper instance server003...
JMX enabled by default
Using config: /tmp/zookeeper/server003/zookeeper-3.4.0/bin/../conf/zoo.cfg
Stopping zookeeper ... STOPPED
stopping zookeeper instance server004...
JMX enabled by default
Using config: /tmp/zookeeper/server004/zookeeper-3.4.0/bin/../conf/zoo.cfg
Stopping zookeeper ... STOPPED
stopping zookeeper instance server005...
JMX enabled by default
Using config: /tmp/zookeeper/server005/zookeeper-3.4.0/bin/../conf/zoo.cfg
Stopping zookeeper ... STOPPED

Zookeeper C API 安装

Zookeeper C client 的实现在 src/c 目录下,进入到该目录安装 Zookeeper C client,步骤如下:

$ ./configure
$ make
$ sudo make install

至此,基本的准备工作已经完成,你可以通过 Zookeeper 本身提供的 Shell 来操作 Zookeeper,操作方法如下,进入 /tmp/zookeeper/server001/zookeeper-3.4.0 目录,执行bin/zkCli.sh -server 127.0.0.1:2181 进入 Zookeeper shell:

forhappy@haiping-ict:/tmp/zookeeper/server001/zookeeper-3.4.0$ bin/zkCli.sh -server 127.0.0.1:2181

[zk: 127.0.0.1:2181(CONNECTED) 2] help
ZooKeeper -server host:port cmd argsconnect host:portget path [watch]ls path [watch]set path data [version]rmr pathdelquota [-n|-b] pathquit printwatches on|offcreate [-s] [-e] path data aclstat path [watch]close ls2 path [watch]history listquota pathsetAcl path aclgetAcl pathsync pathredo cmdnoaddauth scheme authdelete path [version]setquota -n|-b val path

在 shell 中你可以完成基本的操作,如创建、获取、删除、设置某一节点,设置节点 ACL等,可以 zookeeper shell 中通过 help 获取相关命令的用法。

如果你按照上面的步骤完成了 Zookeeper 伪分布式的安装,并且想继续了解 Zookeeper C API 的使用方法,请继续阅读《Zookeeper C API 指南二(监视(Wathes), 基本常量和结构体介绍)》

接上一篇《Zookeeper C API 指南一(准备工作)》,本问将重点介绍 Zookeeper 监视(Watches),以及 Zookeeper C API 中基本的常量与结构体。

Zookeeper 监视(Watches) 简介

Zookeeper C API 的声明和描述在 include/zookeeper.h 中可以找到,另外大部分的 Zookeeper C API 常量、结构体声明也在 zookeeper.h 中,如果如果你在使用 C API 是遇到不明白的地方,最好看看 zookeeper.h,或者自己使用 doxygen 生成 Zookeeper C API 的帮助文档。

Zookeeper 中最有特色且最不容易理解的是监视(Watches)。Zookeeper 所有的读操作——getData(),getChildren(), 和 exists() 都 可以设置监视(watch),监视事件可以理解为一次性的触发器, 官方定义如下: a watch event is one-time trigger, sent to the client that set the watch, which occurs when the data for which the watch was set changes。对此需要作出如下理解:

  • (一次性触发)One-time trigger

    当设置监视的数据发生改变时,该监视事件会被发送到客户端,例如,如果客户端调用了 getData("/znode1", true) 并且稍后 /znode1 节点上的数据发生了改变或者被删除了,客户端将会获取到 /znode1 发生变化的监视事件,而如果 /znode1 再一次发生了变化,除非客户端再次对 /znode1 设置监视,否则客户端不会收到事件通知。

  • (发送至客户端)Sent to the client

    Zookeeper 客户端和服务端是通过 socket 进行通信的,由于网络存在故障,所以监视事件很有可能不会成功地到达客户端,监视事件是异步发送至监视者的,Zookeeper 本身提供了保序性(ordering guarantee):即客户端只有首先看到了监视事件后,才会感知到它所设置监视的 znode 发生了变化(a client will never see a change for which it has set a watch until it first sees the watch event). 网络延迟或者其他因素可能导致不同的客户端在不同的时刻感知某一监视事件,但是不同的客户端所看到的一切具有一致的顺序。

  • (被设置 watch 的数据)The data for which the watch was set

    这意味着 znode 节点本身具有不同的改变方式。你也可以想象 Zookeeper 维护了两条监视链表:数据监视和子节点监视(data watches and child watches) getData() and exists() 设置数据监视,getChildren() 设置子节点监视。 或者,你也可以想象 Zookeeper 设置的不同监视返回不同的数据,getData() 和 exists() 返回 znode 节点的相关信息,而 getChildren() 返回子节点列表。因此, setData() 会触发设置在某一节点上所设置的数据监视(假定数据设置成功),而一次成功的 create() 操作则会出发当前节点上所设置的数据监视以及父节点的子节点监视。一次成功的 delete() 操作将会触发当前节点的数据监视和子节点监视事件,同时也会触发该节点父节点的child watch。

Zookeeper 中的监视是轻量级的,因此容易设置、维护和分发。当客户端与 Zookeeper 服务器端失去联系时,客户端并不会收到监视事件的通知,只有当客户端重新连接后,若在必要的情况下,以前注册的监视会重新被注册并触发,对于开发人员来说 这通常是透明的。只有一种情况会导致监视事件的丢失,即:通过 exists() 设置了某个 znode 节点的监视,但是如果某个客户端在此 znode 节点被创建和删除的时间间隔内与 zookeeper 服务器失去了联系,该客户端即使稍后重新连接 zookeeper服务器后也得不到事件通知。

Zookeeper C API 常量与部分结构(struct)介绍

与 ACL 相关的结构与常量:

struct Id 结构为:

struct Id {char * scheme;char * id;
};

struct ACL 结构为:

struct ACL {int32_t perms;struct Id id;
};

struct ACL_vector 结构为:

struct ACL_vector {int32_t count;struct ACL *data;};

与 znode 访问权限有关的常量

  • const int ZOO_PERM_READ; //允许客户端读取 znode 节点的值以及子节点列表。

  • const int ZOO_PERM_WRITE;// 允许客户端设置 znode 节点的值。

  • const int ZOO_PERM_CREATE; //允许客户端在该 znode 节点下创建子节点。

  • const int ZOO_PERM_DELETE;//允许客户端删除子节点。

  • const int ZOO_PERM_ADMIN; //允许客户端执行 set_acl()。

  • const int ZOO_PERM_ALL;//允许客户端执行所有操作,等价与上述所有标志的或(OR) 。

与 ACL IDs 相关的常量

  • struct Id ZOO_ANYONE_ID_UNSAFE; //(‘world’,’anyone’)

  • struct Id ZOO_AUTH_IDS;// (‘auth’,’’)

三种标准的 ACL

  • struct ACL_vector ZOO_OPEN_ACL_UNSAFE; //(ZOO_PERM_ALL,ZOO_ANYONE_ID_UNSAFE)

  • struct ACL_vector ZOO_READ_ACL_UNSAFE;// (ZOO_PERM_READ, ZOO_ANYONE_ID_UNSAFE)

  • struct ACL_vector ZOO_CREATOR_ALL_ACL; //(ZOO_PERM_ALL,ZOO_AUTH_IDS)

与 Interest 相关的常量:ZOOKEEPER_WRITE, ZOOKEEPER_READ

这 两个常量用于标识感兴趣的事件并通知 zookeeper 发生了哪些事件。Interest 常量可以进行组合或(OR)来标识多种兴趣(multiple interests: write, read),这两个常量一般用于 zookeeper_interest() 和 zookeeper_process()两个函数中。

与节点创建相关的常量:ZOO_EPHEMERALZOO_SEQUENCE

zoo_create 函数标志,ZOO_EPHEMERAL 用来标识创建临时节点,ZOO_SEQUENCE 用来标识节点命名具有递增的后缀序号(一般是节点名称后填充 10 位字符的序号,如 /xyz0000000000, /xyz0000000001, /xyz0000000002, ...),同样地,ZOO_EPHEMERALZOO_SEQUENCE可以组合。

与连接状态 Stat 相关的常量

以下常量均与 Zookeeper 连接状态有关,他们通常用作监视器回调函数的参数。

ZOOAPI const int  ZOO_EXPIRED_SESSION_STATE
ZOOAPI const int  ZOO_AUTH_FAILED_STATE
ZOOAPI const int  ZOO_CONNECTING_STATE
ZOOAPI const int  ZOO_ASSOCIATING_STATE
ZOOAPI const int  ZOO_CONNECTED_STATE

与监视类型(Watch Types)相关的常量

以下常量标识监视事件的类型,他们通常用作监视器回调函数的第一个参数。

  • ZOO_CREATED_EVENT; // 节点被创建(此前该节点不存在),通过 zoo_exists() 设置监视。
  • ZOO_DELETED_EVENT; // 节点被删除,通过 zoo_exists() 和 zoo_get() 设置监视。
  • ZOO_CHANGED_EVENT; // 节点发生变化,通过 zoo_exists() 和 zoo_get() 设置监视。
  • ZOO_CHILD_EVENT; // 子节点事件,通过zoo_get_children() 和 zoo_get_children2()设置监视。
  • ZOO_SESSION_EVENT; // 会话丢失
  • ZOO_NOTWATCHING_EVENT; // 监视被移除。

Zookeeper C API 错误码介绍 ZOO_ERRORS

ZOK 

正常返回

ZSYSTEMERROR 

系统或服务器端错误(System and server-side errors),服务器不会抛出该错误,该错误也只是用来标识错误范围的,即大于该错误值,且小于 ZAPIERROR 都是系统错误。

ZRUNTIMEINCONSISTENCY 

运行时非一致性错误。

ZDATAINCONSISTENCY 

数据非一致性错误。

ZCONNECTIONLOSS 

Zookeeper 客户端与服务器端失去连接

ZMARSHALLINGERROR 

在 marshalling 和unmarshalling 数据时出现错误(Error while marshalling or unmarshalling data)

ZUNIMPLEMENTED 

该操作未实现(Operation is unimplemented)

ZOPERATIONTIMEOUT 

该操作超时(Operation timeout)

ZBADARGUMENTS 

非法参数错误(Invalid arguments)

ZINVALIDSTATE 

非法句柄状态(Invliad zhandle state)

ZAPIERROR 

API 错误(API errors),服务器不会抛出该错误,该错误也只是用来标识错误范围的,错误值大于该值的标识 API 错误,而小于该值的标识 ZSYSTEMERROR。

ZNONODE 

节点不存在(Node does not exist)

ZNOAUTH 

没有经过授权(Not authenticated)

ZBADVERSION 

版本冲突(Version conflict)

ZNOCHILDRENFOREPHEMERALS 

临时节点不能拥有子节点(Ephemeral nodes may not have children)

ZNODEEXISTS 

节点已经存在(The node already exists)

ZNOTEMPTY 

该节点具有自身的子节点(The node has children)

ZSESSIONEXPIRED 

会话过期(The session has been expired by the server)

ZINVALIDCALLBACK 

非法的回调函数(Invalid callback specified)

ZINVALIDACL 

非法的ACL(Invalid ACL specified)

ZAUTHFAILED 

客户端授权失败(Client authentication failed)

ZCLOSING 

Zookeeper 连接关闭(ZooKeeper is closing)

ZNOTHING 

并非错误,客户端不需要处理服务器的响应(not error, no server responses to process)

ZSESSIONMOVED 

会话转移至其他服务器,所以操作被忽略(session moved to another server, so operation is ignored)

至此,Zookeeper C API 中的大部分的常量和结构体均已介绍完毕,下一节《Zookeeper C API 指南三(回调函数)》将介绍 Zookeeper C API 中的回调函数。

接上一篇《Zookeeper C API 指南二(监视(Wathes), 基本常量和结构体介绍)》,本文重点介绍 Zookeeper C API 中的各种回调函数。

Zookeeper C API 中各种回调函数简介

在具体介绍 Zookeeper C API 之前,首先介绍一下 Zookeeper C API 中的各种回调函数的原型:

监视函数(watch function)原型

typedef void (*watcher_fn)(zhandle_t *zh, int type, int state, const char *path,void *watcherCtx);

监视函数原型的各个参数解释如下:

zh zookeeper 句柄(handle)
type 事件类型(event type). *_EVENT 常量之一.
state 连接状态(connection state). 状态值为 *_STATE 常量之一.
path 触发监视事件的 znode 节点的路径,若为 NULL,则事件类型为 ZOO_SESSION_EVENT
watcherCtx 监视器上下文(watcher context).

其他回调函数的原型

Zookeeper 中还有几种在异步 API(一般以 zoo_a*开头的函数) 中使用的回调函数,根据回调函数处理异步函数返回值类型的不同分为以下几类:处理返回 void 类型的回调函数,处理返回 Stat 结构的回调函数,处理返回字符串的回调函数,处理返回数据的回调函数,处理返回字符串列表(a list of string)的回调函数,同时处理返回字符串列表(a list of string)和 Stat 结构的回调函数,以及处理返回 ACL 信息的回调函数,它们分别如下:

// 处理返回 void 类型的回调函数
typedef void(* void_completion_t)(int rc, const void *data);// 处理返回 Stat 结构的回调函数
typedef void(* stat_completion_t)(int rc, const struct Stat *stat, const void *data); // 处理返回字符串的回调函数
typedef void(* string_completion_t)(int rc, const char *value, const void *data); // 处理返回数据的回调函数
typedef void(* data_completion_t)(int rc, const char *value, int value_len, const struct Stat *stat, const void *data); // 处理返回字符串列表(a list of string)的回调函数
typedef void(* strings_completion_t)(int rc, const struct String_vector *strings, const void *data); // 同时处理返回字符串列表(a list of string)和 Stat 结构的回调函数
typedef void(* strings_stat_completion_t)(int rc, const struct String_vector *strings, const struct Stat *stat, const void *data); // 处理以及返回 ACL 信息的回调函数
typedef void(* acl_completion_t)(int rc, struct ACL_vector *acl, struct Stat *stat, const void *data);

下面一一介绍上述几种回调函数的用法:

  • 处理返回 void 类型的回调函数
typedef void(* void_completion_t)(int rc, const void *data)

该回调函数一般在异步 API 调用结束或 Zookeeper  客户端失去连接时被调用。

rc 异步函数调用返回的错误码,连接丢失/超时将触发该原型函数(此处指具有该函数原型的回调函数,下同)的调用,并且错误码为 ZCONNECTIONLOSS --  Zookeeper 客户端与服务器端的连接丢失,或者 ZOPERATIONTIMEOUT -- 连接超时;而与数据相关的事件也会触发该原型函数的调用,同时置相应的错误码,具体见后文(0 代异步函数调用成功)
data 由调用者传入指针,调用者可以通过该指针向回调函数传入自定义的参数,开发人员应负责此指针所指向内存的释放。
  • 处理返回 Stat 结构的回调函数
typedef void(* stat_completion_t)(int rc, const struct Stat *stat, const void *data)

该函数一般在异步 API 调用结束或 Zookeeper  客户端失去连接时被调用。

rc 异步函数调用返回的错误码,连接丢失/超时将触发该原型函数(此处指具有该函数原型的回调函数,下同)的调用,并且错误码为 ZCONNECTIONLOSS --  Zookeeper 客户端与服务器端的连接丢失,或者 ZOPERATIONTIMEOUT -- 连接超时;而与数据相关的事件也会触发该原型函数的调用,同时置相应的错误码,具体见后文(0 代异步函数调用成功)
stat 指向与该 znode 节点相关的 Stat 信息,如果返回非 0 值(即异步调用函数出错),stat 所指向的区域是未定义的,开发者不负责释放 stat 所指向的内存空间。
data 由调用者传入指针,调用者可以通过该指针向回调函数传入自定义的参数,开发人员应负责此指针所指向内存的释放。
  • 处理返回字符串的回调函数
typedef void(* string_completion_t)(int rc, const char *value, const void *data)

该函数一般在异步 API 调用结束或 Zookeeper  客户端失去连接时被调用。

rc 异步函数调用返回的错误码,连接丢失/超时将触发该原型函数(此处指具有该函数原型的回调函数,下同)的调用,并且错误码为 ZCONNECTIONLOSS --  Zookeeper 客户端与服务器端的连接丢失,或者 ZOPERATIONTIMEOUT -- 连接超时;而与数据相关的事件也会触发该原型函数的调用,同时置相应的错误码,具体见后文(0 代异步函数调用成功)
value 返回的字符串
data 由调用者传入指针,调用者可以通过该指针向回调函数传入自定义的参数,开发人员应负责此指针所指向内存的释放。
  • 处理返回数据的回调函数
typedef void(* data_completion_t)(int rc, const char *value, int value_len, const struct Stat *stat, const void *data)

该函数一般在异步 API 调用结束或 Zookeeper  客户端失去连接时被调用。

rc 异步函数调用返回的错误码,连接丢失/超时将触发该原型函数(此处指具有该函数原型的回调函数,下同)的调用,并且错误码为 ZCONNECTIONLOSS --  Zookeeper 客户端与服务器端的连接丢失,或者 ZOPERATIONTIMEOUT -- 连接超时;而与数据相关的事件也会触发该原型函数的调用,同时置相应的错误码,具体见后文(0 代异步函数调用成功)
value 异步调用的返回值,如果返回非 0 值(即异步调用函数出错),value 所指向的区域是未定义的,开发者不负责释放 value 所指向的内存空间。
value_len 返回 value 数据字节数(bytes)
stat 指向与该 znode 节点相关的 Stat 信息,如果返回非 0 值(即异步调用函数出错),stat 所指向的区域是未定义的,开发者不负责释放 stat 所指向的内存空间。
data 由调用者传入指针,调用者可以通过该指针向回调函数传入自定义的参数,开发人员应负责此指针所指向内存的释放。
  • 处理返回字符串列表(a list of string)的回调函数
typedef void(* strings_completion_t)(int rc, const struct String_vector *strings, const void *data)

该函数一般在异步 API 调用结束或 Zookeeper  客户端失去连接时被调用。

rc 异步函数调用返回的错误码,连接丢失/超时将触发该原型函数(此处指具有该函数原型的回调函数,下同)的调用,并且错误码为 ZCONNECTIONLOSS --  Zookeeper 客户端与服务器端的连接丢失,或者 ZOPERATIONTIMEOUT -- 连接超时;而与数据相关的事件也会触发该原型函数的调用,同时置相应的错误码,具体见后文(0 代异步函数调用成功)
strings 指向包含了某 znode 节点的所有子节点名称列表的结构,如果返回非 0 值(即异步调用函数出错),strings 所指向的区域是未定义的,开发者不负责释放strings 所指向的内存空间。
data 由调用者传入指针,调用者可以通过该指针向回调函数传入自定义的参数,开发人员应负责此指针所指向内存的释放。
  • 同时处理返回字符串列表(a list of string)和 Stat 结构的回调函数
typedef void(* strings_stat_completion_t)(int rc, const struct String_vector *strings, const struct Stat *stat, const void *data)

该函数一般在异步 API 调用结束或 Zookeeper  客户端失去连接时被调用。

rc 异步函数调用返回的错误码,连接丢失/超时将触发该原型函数(此处指具有该函数原型的回调函数,下同)的调用,并且错误码为 ZCONNECTIONLOSS --  Zookeeper 客户端与服务器端的连接丢失,或者 ZOPERATIONTIMEOUT -- 连接超时;而与数据相关的事件也会触发该原型函数的调用,同时置相应的错误码,具体见后文(0 代异步函数调用成功)
strings 指向包含了某 znode 节点的所有子节点名称列表的结构,如果返回非 0 值(即异步调用函数出错),strings 所指向的区域是未定义的,开发者不负责释放strings 所指向的内存空间。
stat 指向与该 znode 节点相关的 Stat 信息,如果返回非 0 值(即异步调用函数出错),stat 所指向的区域是未定义的,开发者不负责释放 stat 所指向的内存空间。
data 由调用者传入的指针,调用者可以通过该指针向回调函数传入自定义的参数,开发人员应负责此指针所指向内存的释放。
  • 处理返回 ACL 信息的回调函数
typedef void(* acl_completion_t)(int rc, struct ACL_vector *acl, struct Stat *stat, const void *data)

该函数一般在异步 API 调用结束或 Zookeeper  客户端失去连接时被调用。

rc 异步函数调用返回的错误码,连接丢失/超时将触发该原型函数(此处指具有该函数原型的回调函数,下同)的调用,并且错误码为 ZCONNECTIONLOSS --  Zookeeper 客户端与服务器端的连接丢失,或者 ZOPERATIONTIMEOUT -- 连接超时;而与数据相关的事件也会触发该原型函数的调用,同时置相应的错误码,具体见后文(0 代异步函数调用成功)
acl 指向包含某 znode 节点 ACL 信息的指针,如果返回非 0 值(即异步调用函数出错),acl 所指向的区域是未定义的,开发者不负责释放 acl 所指向的内存空间。
stat 指向与该 znode 节点相关的 Stat 信息,如果返回非 0 值(即异步调用函数出错),stat 所指向的区域是未定义的,开发者不负责释放 stat 所指向的内存空间。
data 由调用者传入的指针,调用者可以通过该指针向回调函数传入自定义的参数,开发人员应负责此指针所指向内存的释放。

至此,所有的回调函数均已介绍完毕,下一节将介绍 Zookeeper C API 分类和基本 API 的使用,见第四讲《Zookeeper C API 指南四(C API 概览)》

上一节《Zookeeper C API 指南三(回调函数)》重点讲了 Zookeeper C API 中各种回调函数的原型,本节将切入正题,正式讲解 Zookeeper C API。相信大家读完本文后应该对 Zookeeper C API 的使用有一个比较清晰的认识。

Zookeeper C API 概览

Zookeeper C API 很规范,接口很容易记忆,大部分接口均以 zoo_ 开头,只有少量接口以 zookeeper_ 开头,所有的 API 汇总如下:

void zoo_create_op_init(zoo_op_t * op, const char *path, const char *value,int valuelen, const struct ACL_vector *acl,int flags, char *path_buffer, int path_buffer_len);void zoo_delete_op_init(zoo_op_t * op, const char *path, int version);void zoo_set_op_init(zoo_op_t * op, const char *path,const char *buffer, int buflen, int version,struct Stat *stat);void zoo_check_op_init(zoo_op_t * op, const char *path, int version);ZOOAPI zhandle_t *zookeeper_init(const char *host, watcher_fn fn,int recv_timeout,const clientid_t * clientid,void *context, int flags);ZOOAPI int zookeeper_close(zhandle_t * zh);ZOOAPI const clientid_t *zoo_client_id(zhandle_t * zh);ZOOAPI int zoo_recv_timeout(zhandle_t * zh);ZOOAPI const void *zoo_get_context(zhandle_t * zh);ZOOAPI void zoo_set_context(zhandle_t * zh, void *context);ZOOAPI watcher_fn zoo_set_watcher(zhandle_t * zh, watcher_fn newFn);ZOOAPI struct sockaddr *zookeeper_get_connected_host(zhandle_t * zh, struct sockaddr*addr,socklen_t * addr_len);ZOOAPI int zookeeper_interest(zhandle_t * zh, int *fd, int *interest,struct timeval *tv);ZOOAPI int zookeeper_process(zhandle_t * zh, int events);ZOOAPI int zoo_state(zhandle_t * zh);ZOOAPI int zoo_acreate(zhandle_t * zh, const char *path,const char *value, int valuelen,const struct ACL_vector *acl, int flags,string_completion_t completion, const void *data);ZOOAPI int zoo_adelete(zhandle_t * zh, const char *path, int version,void_completion_t completion, const void *data);ZOOAPI int zoo_aexists(zhandle_t * zh, const char *path, int watch,stat_completion_t completion, const void *data);ZOOAPI int zoo_awexists(zhandle_t * zh, const char *path,watcher_fn watcher, void *watcherCtx,stat_completion_t completion, const void *data);ZOOAPI int zoo_aget(zhandle_t * zh, const char *path, int watch,data_completion_t completion, const void *data);ZOOAPI int zoo_awget(zhandle_t * zh, const char *path,watcher_fn watcher, void *watcherCtx,data_completion_t completion, const void *data);ZOOAPI int zoo_aset(zhandle_t * zh, const char *path,const char *buffer, int buflen, int version,stat_completion_t completion, const void *data);ZOOAPI int zoo_aget_children(zhandle_t * zh, const char *path,int watch,strings_completion_t completion,const void *data);ZOOAPI int zoo_awget_children(zhandle_t * zh, const char *path,watcher_fn watcher, void *watcherCtx,strings_completion_t completion,const void *data);ZOOAPI int zoo_aget_children2(zhandle_t * zh, const char *path,int watch,strings_stat_completion_t completion,const void *data);ZOOAPI int zoo_awget_children2(zhandle_t * zh, const char *path,watcher_fn watcher, void *watcherCtx,strings_stat_completion_t completion,const void *data);ZOOAPI int zoo_async(zhandle_t * zh, const char *path,string_completion_t completion, const void *data);ZOOAPI int zoo_aget_acl(zhandle_t * zh, const char *path,acl_completion_t completion, const void *data);ZOOAPI int zoo_aset_acl(zhandle_t * zh, const char *path, int version,struct ACL_vector *acl, void_completion_t,const void *data);ZOOAPI int zoo_amulti(zhandle_t * zh, int count, const zoo_op_t * ops,zoo_op_result_t * results, void_completion_t,const void *data);ZOOAPI const char *zerror(int c);ZOOAPI int zoo_add_auth(zhandle_t * zh, const char *scheme,const char *cert, int certLen,void_completion_t completion, const void *data);ZOOAPI int is_unrecoverable(zhandle_t * zh);ZOOAPI void zoo_set_debug_level(ZooLogLevel logLevel);ZOOAPI void zoo_set_log_stream(FILE * logStream);ZOOAPI void zoo_deterministic_conn_order(int yesOrNo);ZOOAPI int zoo_create(zhandle_t * zh, const char *path,const char *value, int valuelen,const struct ACL_vector *acl, int flags,char *path_buffer, int path_buffer_len);ZOOAPI int zoo_delete(zhandle_t * zh, const char *path, int version);ZOOAPI int zoo_exists(zhandle_t * zh, const char *path, int watch,struct Stat *stat);ZOOAPI int zoo_wexists(zhandle_t * zh, const char *path,watcher_fn watcher, void *watcherCtx,struct Stat *stat);ZOOAPI int zoo_get(zhandle_t * zh, const char *path, int watch,char *buffer, int *buffer_len, struct Stat *stat);ZOOAPI int zoo_wget(zhandle_t * zh, const char *path,watcher_fn watcher, void *watcherCtx,char *buffer, int *buffer_len, struct Stat *stat);ZOOAPI int zoo_set(zhandle_t * zh, const char *path,const char *buffer, int buflen, int version);ZOOAPI int zoo_set2(zhandle_t * zh, const char *path,const char *buffer, int buflen, int version,struct Stat *stat);ZOOAPI int zoo_get_children(zhandle_t * zh, const char *path,int watch, struct String_vector *strings);ZOOAPI int zoo_wget_children(zhandle_t * zh, const char *path,watcher_fn watcher, void *watcherCtx,struct String_vector *strings);ZOOAPI int zoo_get_children2(zhandle_t * zh, const char *path,int watch, struct String_vector *strings,struct Stat *stat);ZOOAPI int zoo_wget_children2(zhandle_t * zh, const char *path,watcher_fn watcher, void *watcherCtx,struct String_vector *strings,struct Stat *stat);ZOOAPI int zoo_get_acl(zhandle_t * zh, const char *path,struct ACL_vector *acl, struct Stat *stat);ZOOAPI int zoo_set_acl(zhandle_t * zh, const char *path, int version,const struct ACL_vector *acl);ZOOAPI int zoo_multi(zhandle_t * zh, int count, const zoo_op_t * ops,zoo_op_result_t * results);

除了基本的初始化、销毁 Zookeeper 句柄(zhandle),设置日志等级、日志流以及一些具有辅助功能 API(zerror(), zoo_state()等) 外,Zookeeper C API 大部分接口可以根据同步和异步特性分为两类,同步接口以 zoo_* 开头,异步接口以则以 zoo_a* 开头。并且除了 zookeeper_init() 以及与 zoo_multi() 或 zoo_amulti() 批量操作相关的 zoo_op_t 初始化外,其他的 API 的第一个参数均为 zhandle_t * zh, 即 Zookeeper 句柄的指针。

Zookeeper C API 分类

  • 初始化、销毁 Zookeeper 句柄
ZOOAPI zhandle_t *zookeeper_init(const char *host, watcher_fn fn,int recv_timeout,const clientid_t * clientid,void *context, int flags);ZOOAPI int zookeeper_close(zhandle_t * zh);

  • 辅助函数
// 设置日志等级、日志流
ZOOAPI void zoo_set_debug_level(ZooLogLevel logLevel);
ZOOAPI void zoo_set_log_stream(FILE * logStream);ZOOAPI const clientid_t *zoo_client_id(zhandle_t * zh);ZOOAPI int zoo_recv_timeout(zhandle_t * zh);ZOOAPI const void *zoo_get_context(zhandle_t * zh);ZOOAPI void zoo_set_context(zhandle_t * zh, void *context);ZOOAPI watcher_fn zoo_set_watcher(zhandle_t * zh, watcher_fn newFn);ZOOAPI struct sockaddr *zookeeper_get_connected_host(zhandle_t * zh, struct sockaddr*addr,socklen_t * addr_len);ZOOAPI int zookeeper_interest(zhandle_t * zh, int *fd, int *interest,struct timeval *tv);ZOOAPI int zookeeper_process(zhandle_t * zh, int events);ZOOAPI int zoo_state(zhandle_t * zh);ZOOAPI const char *zerror(int c);ZOOAPI int is_unrecoverable(zhandle_t * zh);ZOOAPI void zoo_deterministic_conn_order(int yesOrNo);

  • 与 zoo_multi() 和 zoo_amulti() 批量操作相关的 zoo_op_t 初始化
void zoo_create_op_init(zoo_op_t * op, const char *path, const char *value,int valuelen, const struct ACL_vector *acl,int flags, char *path_buffer, int path_buffer_len);void zoo_delete_op_init(zoo_op_t * op, const char *path, int version);void zoo_set_op_init(zoo_op_t * op, const char *path,const char *buffer, int buflen, int version,struct Stat *stat);void zoo_check_op_init(zoo_op_t * op, const char *path, int version);

  • Zookeeper C API 同步接口
ZOOAPI int zoo_add_auth(zhandle_t * zh, const char *scheme,const char *cert, int certLen,void_completion_t completion, const void *data);ZOOAPI int zoo_create(zhandle_t * zh, const char *path,const char *value, int valuelen,const struct ACL_vector *acl, int flags,char *path_buffer, int path_buffer_len);ZOOAPI int zoo_delete(zhandle_t * zh, const char *path, int version);ZOOAPI int zoo_exists(zhandle_t * zh, const char *path, int watch,struct Stat *stat);ZOOAPI int zoo_wexists(zhandle_t * zh, const char *path,watcher_fn watcher, void *watcherCtx,struct Stat *stat);ZOOAPI int zoo_get(zhandle_t * zh, const char *path, int watch,char *buffer, int *buffer_len, struct Stat *stat);ZOOAPI int zoo_wget(zhandle_t * zh, const char *path,watcher_fn watcher, void *watcherCtx,char *buffer, int *buffer_len, struct Stat *stat);ZOOAPI int zoo_set(zhandle_t * zh, const char *path,const char *buffer, int buflen, int version);ZOOAPI int zoo_set2(zhandle_t * zh, const char *path,const char *buffer, int buflen, int version,struct Stat *stat);ZOOAPI int zoo_get_children(zhandle_t * zh, const char *path,int watch, struct String_vector *strings);ZOOAPI int zoo_wget_children(zhandle_t * zh, const char *path,watcher_fn watcher, void *watcherCtx,struct String_vector *strings);ZOOAPI int zoo_get_children2(zhandle_t * zh, const char *path,int watch, struct String_vector *strings,struct Stat *stat);ZOOAPI int zoo_wget_children2(zhandle_t * zh, const char *path,watcher_fn watcher, void *watcherCtx,struct String_vector *strings,struct Stat *stat);ZOOAPI int zoo_get_acl(zhandle_t * zh, const char *path,struct ACL_vector *acl, struct Stat *stat);ZOOAPI int zoo_set_acl(zhandle_t * zh, const char *path, int version,const struct ACL_vector *acl);ZOOAPI int zoo_multi(zhandle_t * zh, int count, const zoo_op_t * ops,zoo_op_result_t * results);

  • Zookeeper C API 异步接口
ZOOAPI int zoo_acreate(zhandle_t * zh, const char *path,const char *value, int valuelen,const struct ACL_vector *acl, int flags,string_completion_t completion, const void *data);ZOOAPI int zoo_adelete(zhandle_t * zh, const char *path, int version,void_completion_t completion, const void *data);ZOOAPI int zoo_aexists(zhandle_t * zh, const char *path, int watch,stat_completion_t completion, const void *data);ZOOAPI int zoo_awexists(zhandle_t * zh, const char *path,watcher_fn watcher, void *watcherCtx,stat_completion_t completion, const void *data);ZOOAPI int zoo_aget(zhandle_t * zh, const char *path, int watch,data_completion_t completion, const void *data);ZOOAPI int zoo_awget(zhandle_t * zh, const char *path,watcher_fn watcher, void *watcherCtx,data_completion_t completion, const void *data);ZOOAPI int zoo_aset(zhandle_t * zh, const char *path,const char *buffer, int buflen, int version,stat_completion_t completion, const void *data);ZOOAPI int zoo_aget_children(zhandle_t * zh, const char *path,int watch,strings_completion_t completion,const void *data);ZOOAPI int zoo_awget_children(zhandle_t * zh, const char *path,watcher_fn watcher, void *watcherCtx,strings_completion_t completion,const void *data);ZOOAPI int zoo_aget_children2(zhandle_t * zh, const char *path,int watch,strings_stat_completion_t completion,const void *data);ZOOAPI int zoo_awget_children2(zhandle_t * zh, const char *path,watcher_fn watcher, void *watcherCtx,strings_stat_completion_t completion,const void *data);ZOOAPI int zoo_async(zhandle_t * zh, const char *path,string_completion_t completion, const void *data);ZOOAPI int zoo_aget_acl(zhandle_t * zh, const char *path,acl_completion_t completion, const void *data);ZOOAPI int zoo_aset_acl(zhandle_t * zh, const char *path, int version,struct ACL_vector *acl, void_completion_t,const void *data);ZOOAPI int zoo_amulti(zhandle_t * zh, int count, const zoo_op_t * ops,zoo_op_result_t * results, void_completion_t,const void *data);

Zookeeper C API 初体验

有了上面的介绍,下面我们来看一看如何使简单地使用 Zookeeper C API 吧。

在使用 Zookeeper C API 时应注意:

  • 头文件包含 #include <zookeeper/zookeeper.h>
  • 如果你需要编译多线程版本客户端程序,请添加编译选项 -DTHREADED,同时链接时应链接 zookeeper_mt 库;如果你需要编译单线程客户端程序,请不要添加编译选项 -DTHREADED,同时链接时应链接 zookeeper_st 库。

一个基本的程序如下(更详细的例子可以参看 src/c/src/cli.c):

/** =============================================================================**       Filename:  zktest.c**    Description:  zookeeper api testcase.**        Created:  02/15/2013 08:48:49 PM**         Author:  Fu Haiping (forhappy), haipingf@gmail.com*        Company:  ICT ( Institute Of Computing Technology, CAS )** =============================================================================*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <zookeeper/zookeeper.h>
#include <zookeeper/zookeeper_log.h>void zktest_watcher_g(zhandle_t* zh, int type, int state,const char* path, void* watcherCtx)
{printf("Something happened.\n");printf("type: %d\n", type);printf("state: %d\n", state);printf("path: %s\n", path);printf("watcherCtx: %s\n", (char *)watcherCtx);
}void zktest_dump_stat(const struct Stat *stat)
{char tctimes[40];char tmtimes[40];time_t tctime;time_t tmtime;if (!stat) {fprintf(stderr,"null\n");return;}tctime = stat->ctime/1000;tmtime = stat->mtime/1000;ctime_r(&tmtime, tmtimes);ctime_r(&tctime, tctimes);fprintf(stderr, "\tctime = %s\tczxid=%llx\n""\tmtime=%s\tmzxid=%llx\n""\tversion=%x\taversion=%x\n""\tephemeralOwner = %llx\n",tctimes, stat->czxid,tmtimes, stat->mzxid,(unsigned int)stat->version, (unsigned int)stat->aversion,stat->ephemeralOwner);
}void zktest_stat_completion(int rc, const struct Stat *stat, const void *data)
{fprintf(stderr, "%s: rc = %d Stat:\n", (char*)data, rc);zktest_dump_stat(stat);
}void zktest_void_completion(int rc, const void *data)
{fprintf(stderr, "[%s]: rc = %d\n", (char*)(data==0?"null":data), rc);
}void zktest_string_completion(int rc, const char *name, const void *data)
{fprintf(stderr, "[%s]: rc = %d\n", (char*)(data==0?"null":data), rc);if (!rc) {fprintf(stderr, "\tname = %s\n", name);}
}int main(int argc, const char *argv[])
{const char* host = "127.0.0.1:2181,127.0.0.1:2182,""127.0.0.1:2183,127.0.0.1:2184,127.0.0.1:2185";int timeout = 30000;zoo_set_debug_level(ZOO_LOG_LEVEL_WARN);zhandle_t* zkhandle = zookeeper_init(host,zktest_watcher_g, timeout, 0, "hello zookeeper.", 0);if (zkhandle == NULL) {fprintf(stderr, "Error when connecting to zookeeper servers...\n");exit(EXIT_FAILURE);}// struct ACL ALL_ACL[] = {{ZOO_PERM_ALL, ZOO_ANYONE_ID_UNSAFE}};// struct ACL_vector ALL_PERMS = {1, ALL_ACL};int ret = zoo_acreate(zkhandle, "/xyz", "hello", 5,&ZOO_OPEN_ACL_UNSAFE, 0 /* ZOO_SEQUENCE */,zktest_string_completion, "acreate");if (ret) {fprintf(stderr, "Error %d for %s\n", ret, "acreate");exit(EXIT_FAILURE);}ret = 0;ret = zoo_aexists(zkhandle, "/xyz", 1, zktest_stat_completion, "aexists");if (ret) {fprintf(stderr, "Error %d for %s\n", ret, "aexists");exit(EXIT_FAILURE);}ret = 0;// Wait for asynchronous zookeeper call done.
    getchar();ret = zoo_adelete(zkhandle, "/xyz", -1, zktest_void_completion, "adelete");if (ret) {fprintf(stderr, "Error %d for %s\n", ret, "adelete");exit(EXIT_FAILURE);}// Wait for asynchronous zookeeper call done.
    getchar();zookeeper_close(zkhandle);
}

下面简单讲讲这个程序的结构:

  • 首先声明 host, timeout 变量。
const char* host = "127.0.0.1:2181,127.0.0.1:2182,""127.0.0.1:2183,127.0.0.1:2184,127.0.0.1:2185";
int timeout = 30000;

其中 host 字符串格式为逗号隔开的 IP:PORT对,可以是 Zookeeper 集群中的全部或部分 Zookeeper 实例的 IP:PORT对,我们在第一讲《准备工作》中介绍了如何部署一个伪分布式的集群,上述的 host 就是这些 zookeeper 实例的IP:PORT对。

另外 timeout 是 Zookeeper 客户端连接服务器的超时时间,单位为毫秒,timeout = 30000 说明如果 30 秒内客户端没有连接上 Zookeeper 服务则表示连接超时。

  • 设置日志等级。
zoo_set_debug_level(ZOO_LOG_LEVEL_WARN);

  • 初始化 Zookeeper 句柄(zhandle_t)。
    zhandle_t* zkhandle = zookeeper_init(host,zktest_watcher_g, timeout, 0, "hello zookeeper.", 0);if (zkhandle == NULL) {fprintf(stderr, "Error when connecting to zookeeper servers...\n");exit(EXIT_FAILURE);}

初始化 Zookeeper 句柄 zookeeper_init() 函数原型如下:

ZOOAPI zhandle_t *zookeeper_init(const char *host, watcher_fn fn,int recv_timeout,const clientid_t * clientid,void *context, int flags);

各个参数解释如下:

host 逗号隔开的 host:port 对, 每个代表一个 zk server, 例如: "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002"
fn 全局的监视器回调函数,当发生事件通知时,该函数会被调用。
clientid 客户端尝试重连的先前会话的ID,如果不需要重连先前的会话,则设置为 0。客户端可以通过调用 zoo_client_id来访问一个已经连接上的并且有效的会话ID,如果clientid对应的会话超时,或者由于某种原因 clientid变为无效了,那么zookeeper_init 将返回一个非法的zhandle_t, 通过 zhandle_t 的状态可以获知zookeeper_init 调用失败的原因。 (通常为 ZOO_EXPIRED_SESSION_STATE).
context 与 zhandle_t 实例相关联的“上下文对象”(可以通过该参数为 zhandle_t 传入自定义类型的数据),应用程序可以通过 zoo_get_context访问它(例如在监视器回调函数中),当然 zookeeper 内部没有用到该参数,所以 context 可以设置为 NULL。
flags 目前为保留参数,设置为 0。
  • 创建一个 znode 节点。
    // struct ACL ALL_ACL[] = {{ZOO_PERM_ALL, ZOO_ANYONE_ID_UNSAFE}};// struct ACL_vector ALL_PERMS = {1, ALL_ACL};int ret = zoo_acreate(zkhandle, "/xyz", "hello", 5,&ZOO_OPEN_ACL_UNSAFE, 0 /* ZOO_SEQUENCE */,zktest_string_completion, "acreate");if (ret) {fprintf(stderr, "Error %d for %s\n", ret, "acreate");exit(EXIT_FAILURE);}

这里采用异步方式创建 znode 节点,zoo_acreate() 函数原型为:

ZOOAPI int zoo_acreate(zhandle_t * zh, const char *path,const char *value, int valuelen,const struct ACL_vector *acl, int flags,string_completion_t completion, const void *data);

各个参数解释如下:

zh zookeeper_init() 返回的 zookeeper 句柄。
path 节点路径。
value 该节点保存的数据。
valuelen 该节点保存数据的大小。
acl 该节点初始 ACL,ACL 不能为null 或空。
flags 该参数可以设置为 0,或者创建标识符 ZOO_EPHEMERALZOO_SEQUENCE 的组合或(OR)。
completion 当创建节点请求完成时会调用该函数,该函数原型详见第三讲《回调函数》一节。同时传递给completion的rc参数为: ZOK 操作完成;ZNONODE 父节点不存在;ZNODEEXISTS 节点已存在;ZNOAUTH 客户端没有权限创建节点。ZNOCHILDRENFOREPHEMERALS 临时节点不能创建子节点。
data completino函数被调用时,传递给 completion 的数据。
  • 调用 exists() 函数,设置监视器。
    ret = zoo_aexists(zkhandle, "/xyz", 1, zktest_stat_completion, "aexists");if (ret) {fprintf(stderr, "Error %d for %s\n", ret, "aexists");exit(EXIT_FAILURE);}

  • 调用 delete 函数,删除 znode 节点。
    ret = zoo_adelete(zkhandle, "/xyz", -1, zktest_void_completion, "adelete");if (ret) {fprintf(stderr, "Error %d for %s\n", ret, "adelete");exit(EXIT_FAILURE);}

  • 销毁 zookeeper 句柄
zookeeper_close(zkhandle);

好了,至此本文大致讲完了 Zookeeper C API 的分类和几个基本函数的用法。之所以为 Zookeeper C API 分类是方便记忆,开发者可以迅速找到自己需要的 API;另外,本文还讲了几个基本函数的使用方法,包括 zookeeper_init(),zoo_acreate(), zoo_aexists(), zoo_adelete() 以及 zookeeper_close()。相信大家对 Zookeeper C API 也有了一个大致的了解,第五讲我会给大家介绍 Zookeeper C API 中的同步调用的函数(即以 zoo_* 开头的函数),然后第六讲给大家介绍 Zookeeper C API 中的异步调用的函数(即以 zoo_a* 开头的函数)。

上一讲《Zookeeper C API 指南四(C API 概览)》讲了Zookeeper C API 的分类和几个基本函数的用法,相信大家对 Zookeeper C API 也有了一个大致的了解,本文我会给大家介绍 Zookeeper C API 中的同步调用的函数(即以 zoo_* 开头的函数)。

Zookeeper C API 中与访问 Zookeeper 服务相关(比如创建、删除 znode 节点,获取子节点,设置 znode 数据等)的同步 API 如下:

ZOOAPI int zoo_add_auth(zhandle_t * zh, const char *scheme,const char *cert, int certLen,void_completion_t completion, const void *data);ZOOAPI int zoo_create(zhandle_t * zh, const char *path,const char *value, int valuelen,const struct ACL_vector *acl, int flags,char *path_buffer, int path_buffer_len);ZOOAPI int zoo_delete(zhandle_t * zh, const char *path, int version);ZOOAPI int zoo_exists(zhandle_t * zh, const char *path, int watch,struct Stat *stat);ZOOAPI int zoo_wexists(zhandle_t * zh, const char *path,watcher_fn watcher, void *watcherCtx,struct Stat *stat);ZOOAPI int zoo_get(zhandle_t * zh, const char *path, int watch,char *buffer, int *buffer_len, struct Stat *stat);ZOOAPI int zoo_wget(zhandle_t * zh, const char *path,watcher_fn watcher, void *watcherCtx,char *buffer, int *buffer_len, struct Stat *stat);ZOOAPI int zoo_set(zhandle_t * zh, const char *path,const char *buffer, int buflen, int version);ZOOAPI int zoo_set2(zhandle_t * zh, const char *path,const char *buffer, int buflen, int version,struct Stat *stat);ZOOAPI int zoo_get_children(zhandle_t * zh, const char *path,int watch, struct String_vector *strings);ZOOAPI int zoo_wget_children(zhandle_t * zh, const char *path,watcher_fn watcher, void *watcherCtx,struct String_vector *strings);ZOOAPI int zoo_get_children2(zhandle_t * zh, const char *path,int watch, struct String_vector *strings,struct Stat *stat);ZOOAPI int zoo_wget_children2(zhandle_t * zh, const char *path,watcher_fn watcher, void *watcherCtx,struct String_vector *strings,struct Stat *stat);ZOOAPI int zoo_get_acl(zhandle_t * zh, const char *path,struct ACL_vector *acl, struct Stat *stat);ZOOAPI int zoo_set_acl(zhandle_t * zh, const char *path, int version,const struct ACL_vector *acl);ZOOAPI int zoo_multi(zhandle_t * zh, int count, const zoo_op_t * ops,zoo_op_result_t * results);

本文将以上同步 API 在此细分为一下几类:(1). 创建、删除 znode 节点,(2). 可设置 watch 的 API,(3). 访问、设置节点 ACL 的 API,(4). 批处理 API

  • 创建、删除 znode 节点(两个)
ZOOAPI int zoo_create(zhandle_t * zh, const char *path,const char *value, int valuelen,const struct ACL_vector *acl, int flags,char *path_buffer, int path_buffer_len);ZOOAPI int zoo_delete(zhandle_t * zh, const char *path, int version);

  • 创建 znode 节点:
ZOOAPI int zoo_create(zhandle_t * zh, const char *path,const char *value, int valuelen,const struct ACL_vector *acl, int flags,char *path_buffer, int path_buffer_len);

zh zookeeper_init() 返回的 zookeeper 句柄。
path 节点路径。
value 该节点保存的数据。
valuelen 该节点保存数据的大小。如果 value 被设置为 NULL(该 znode 节点不包含数据),则 valuelen 应该设置为 -1。
acl 该节点初始 ACL,ACL 不能为null 或空。
flags 该参数可以设置为 0,或者创建标识符ZOO_EPHEMERALZOO_SEQUENCE 的组合或(OR)。
path_buffer 用于保存返回节点新路径(因为设置了ZOO_SEQUENCE 后 zoo_create 所创建的节点名称与参数 path 提供的名称不同,新的节点名称后面填充了序号),path 字符串以 NULL 结束。path_buffer 可以设置为 NULL,此时 path_buffer_len 等于 0。
path_buffer_len path_buffer 的长度,如果新节点名称的长度大于path_buffer_len,则节点名称将会被截断,而服务器端该节点的名称不会截断。
  • 删除 znode 节点:
ZOOAPI int zoo_delete(zhandle_t * zh, const char *path, int version);

zh zookeeper_init() 返回的 zookeeper 句柄。
path 节点路径。
version 节点的版本号,如果该 znode 节点的实际版本号与该参数提供的版本号不一值,则删除节点失败,如果 version 为 -1,则不作版本检查。
  • 可设置 watch 的 API(exists(两个) + get(两个) + get_children(四个) = 八个)
ZOOAPI int zoo_exists(zhandle_t * zh, const char *path, int watch,struct Stat *stat);ZOOAPI int zoo_wexists(zhandle_t * zh, const char *path,watcher_fn watcher, void *watcherCtx,struct Stat *stat);ZOOAPI int zoo_get(zhandle_t * zh, const char *path, int watch,char *buffer, int *buffer_len, struct Stat *stat);ZOOAPI int zoo_wget(zhandle_t * zh, const char *path,watcher_fn watcher, void *watcherCtx,char *buffer, int *buffer_len, struct Stat *stat);ZOOAPI int zoo_get_children(zhandle_t * zh, const char *path,int watch, struct String_vector *strings);ZOOAPI int zoo_wget_children(zhandle_t * zh, const char *path,watcher_fn watcher, void *watcherCtx,struct String_vector *strings);ZOOAPI int zoo_get_children2(zhandle_t * zh, const char *path,int watch, struct String_vector *strings,struct Stat *stat);ZOOAPI int zoo_wget_children2(zhandle_t * zh, const char *path,watcher_fn watcher, void *watcherCtx,struct String_vector *strings,struct Stat *stat);

  • 检查节点状态 exists(两个,分别是 zoo_exists() 和 zoo_wexists(),区别是后者可以指定单独的 watcher_fn(监视器回调函数),而前者只能用 zookeeper_init() 设置的全局监视器回调函数,同时 get 和 get_children两族函数也一样,带有zoo_w* 的函数可以指定单独的 watcher_fn)。
ZOOAPI int zoo_exists(zhandle_t * zh, const char *path, int watch, struct Stat *stat);

zh zookeeper_init() 返回的 zookeeper 句柄。
path 节点路径。
watch 如果非 0,则在服务器端设置监视,当节点发生变化时客户端会得到通知,即使当前指定的节点不存在也会设置监视,这样该节点被创建时,客户端也可以得到通知。
stat 返回的 Stat 信息。
ZOOAPI int zoo_wexists(zhandle_t * zh, const char *path, watcher_fn watcher, void *watcherCtx,struct Stat *stat);

zh zookeeper_init() 返回的 zookeeper 句柄。
path 节点路径。
watcher 如果不为 NULL 则会在服务器端设置监视,当节点发生变化时客户端会得到通知,即使当前指定的节点不存在也会设置监视,这样该节点被创建时,客户端也可以得到通知。
watcherCtx 用户指定的数据,将被传入到监视器回调函数中,与由 zookeeper_init() 设置的全局监视器上下文不同,该函数设置的监视器上下文只与当前的监视器相关联。
stat 返回的 Stat 信息。
  • 获取节点数据 get(两个)
ZOOAPI int zoo_get(zhandle_t * zh, const char *path, int watch,char *buffer, int *buffer_len, struct Stat *stat);

zh zookeeper_init() 返回的 zookeeper 句柄。
path 节点路径。
watch 如果非0,则在服务器端设置监视,当节点发生变化时客户端会得到通知。
buffer 用于保存从 zookeeper 服务器获取的节点数据。
buffer_len buffer 大小,一旦成功返回该值将会被设置为节点数据的实际大小,如果节点的数据为空,则数据大小为 -1,buffer_len 也为 -1。
stat 如果非空,stat 指向的结构将会保存该节点的 Stat 信息。
ZOOAPI int zoo_wget(zhandle_t * zh, const char *path,watcher_fn watcher, void *watcherCtx,char *buffer, int *buffer_len, struct Stat *stat);

zh zookeeper_init() 返回的 zookeeper 句柄。
path 节点路径。
watcher 如果不为 NULL 则会在服务器端设置监视,当节点发生变化时客户端会得到通知
watcherCtx 用户指定的数据,将被传入到监视器回调函数中,与由 zookeeper_init() 设置的全局监视器上下文不同,该函数设置的监视器上下文只与当前的监视器相关联。
buffer 用于保存从 zookeeper 服务器获取的节点数据。
buffer_len buffer 大小,一旦成功返回该值将会被设置为节点数据的实际大小,如果节点的数据为空,则数据大小为 -1,buffer_len 也为 -1。
stat 如果非空,stat 指向的结构将会保存该节点的 Stat 信息。
  • 获取子节点列表 get_children(四个)
ZOOAPI int zoo_get_children(zhandle_t * zh, const char *path,int watch, struct String_vector *strings);

zh zookeeper_init() 返回的 zookeeper 句柄。
path 节点路径。
watch 如果非0,则在服务器端设置监视,当节点发生变化时客户端会得到通知
strings 返回各个子节点路径
ZOOAPI int zoo_wget_children(zhandle_t * zh, const char *path, watcher_fn watcher, void *watcherCtx,struct String_vector *strings);

zh zookeeper_init() 返回的 zookeeper 句柄。
path 节点路径。
watcher 如果不为 NULL 则会在服务器端设置监视,当节点发生变化时客户端会得到通知
watcherCtx 用户指定的数据,将被传入到监视器回调函数中,与由 zookeeper_init() 设置的全局监视器上下文不同,该函数设置的监视器上下文只与当前的监视器相关联。
strings 回各个子节点路径
ZOOAPI int zoo_get_children2(zhandle_t * zh, const char *path, int watch, struct String_vector *strings,struct Stat *stat);

该函数最早出现在 Zookeeper 3.3.0中,该函数功能与 zoo_get_children() 基本一致,但同时还会返回指定节点的 Stat 信息。

zh zookeeper_init() 返回的 zookeeper 句柄。
path 节点路径。
watch 如果非0,则在服务器端设置监视,当节点发生变化时客户端会得到通知
strings 返回各个子节点路径。
stat 返回指定节点的 Stat 信息。
ZOOAPI int zoo_wget_children2(zhandle_t * zh, const char *path,watcher_fn watcher, void *watcherCtx,struct String_vector *strings,struct Stat *stat);

该函数最早出现在 Zookeeper 3.3.0中,该函数功能与 zoo_wget_children() 基本一致,但同时还会返回指定节点的 Stat 信息。

zh zookeeper_init() 返回的 zookeeper 句柄。
path 节点路径。
watcher 如果不为 NULL 则会在服务器端设置监视,当节点发生变化时客户端会得到通知
watcherCtx 用户指定的数据,将被传入到监视器回调函数中,与由 zookeeper_init() 设置的全局监视器上下文不同,该函数设置的监视器上下文只与当前的监视器相关联。
strings 返回各个子节点路径。
stat 返回指定节点的 Stat 信息。
  • 访问、设置节点 ACL (两个)
ZOOAPI int zoo_get_acl(zhandle_t * zh, const char *path, struct ACL_vector *acl, struct Stat *stat);ZOOAPI int zoo_set_acl(zhandle_t * zh, const char *path, int version, const struct ACL_vector *acl);

  • 获取节点 ACL 信息
ZOOAPI int zoo_get_acl(zhandle_t * zh, const char *path, struct ACL_vector *acl, struct Stat *stat);

zh zookeeper_init() 返回的 zookeeper 句柄。
path 节点路径。
acl 该函数所返回的指定节点的 ACL 信息。
stat 返回指定节点的 Stat 信息。
  • 设置节点 ACL
ZOOAPI int zoo_set_acl(zhandle_t * zh, const char *path, int version, const struct ACL_vector *acl);

zh zookeeper_init() 返回的 zookeeper 句柄。
path 节点路径。
version 节点的版本号。
acl 需要设置的 ACL 信息。
  • 批处理,即原子性地一次提交多个 Zookeeper 操作。
ZOOAPI int zoo_multi(zhandle_t * zh, int count, const zoo_op_t * ops, zoo_op_result_t * results);

zh zookeeper_init() 返回的 zookeeper 句柄。
count 提交操作的个数。
ops 包含所提交操作数组。
results 包含操作所返回结果的数组。

其中 zoo_op_t 是各种操作(创建、删除节点,设置节点数据和检查节点状态四种操作)一个封装(联合体),定义如下:

typedef struct zoo_op {int type;union {// CREATEstruct {const char *path;const char *data;int datalen;char *buf;int buflen;const struct ACL_vector *acl;int flags;} create_op;// DELETE struct {const char *path;int version;} delete_op;// SETstruct {const char *path;const char *data;int datalen;int version;struct Stat *stat;} set_op;// CHECKstruct {const char *path;int version;} check_op;};
} zoo_op_t;

zoo_op_t 一般由以下四个函数初始化:

void zoo_create_op_init(zoo_op_t * op, const char *path, const char *value,int valuelen, const struct ACL_vector *acl,int flags, char *path_buffer, int path_buffer_len);void zoo_delete_op_init(zoo_op_t * op, const char *path, int version);void zoo_set_op_init(zoo_op_t * op, const char *path,const char *buffer, int buflen, int version,struct Stat *stat);void zoo_check_op_init(zoo_op_t * op, const char *path, int version);

zoo_op_result_t 用于保存 zoo_multi 或者 zoo_amulti 返回的其中一个结果,定义如下:

typedef struct zoo_op_result {int err;char *value;int valuelen;struct Stat *stat;
} zoo_op_result_t; 

以上内容是 Zookeeper  C API 中同步 API 的介绍,如有错误请留下您的想法和意见,本人会尽快更正;同时,我也将在后面的文章中列举一些示例来说明上述 API 的用法,如有兴趣请继续关注,如果您需要了解 Zookeeper 异步 API,请移步第六讲《Zookeeper C API 指南六(异步 API 介绍)》。

上一讲《Zookeeper C API 指南五(同步 API 介绍)》讲了Zookeeper 同步 API 的分类和相关解释,相信大家对 Zookeeper 同步 API 也有了一个大致的了解,本文我会给大家介绍 Zookeeper C API 中的异步调用的函数(即以 zoo_a* 开头的函数),本文大致结构与《Zookeeper C API 指南五(同步 API 介绍)》,先汇总 API,然后再分类,并对每个 API 作出解释。

在具体讲解 Zookeeper 异步 API 之前,首先回顾一下《Zookeeper C API 指南三(回调函数)》,除了监视器回调函数以外,还有其他 7 种回调函数,他们通常在异步 API 调用结束或 Zookeeper  客户端失去连接时被调用。根据回调函数返回参数(即函数的输出参数)的类型不同分为以下几类:返回 void 类型的回调函数,返回 Stat 结构的回调函数,返回字符串的回调函数,返回数据的回调函数,返回字符串列表(a list of string)的回调函数,同时返回字符串列表(a list of string)和 Stat 结构的回调函数,以及返回 ACL 信息的回调函数,7 中回调函数原型声明如下:

// 返回 void 类型的回调函数
typedef void(* void_completion_t)(int rc, const void *data);// 返回 Stat 结构的回调函数
typedef void(* stat_completion_t)(int rc, const struct Stat *stat, const void *data); // 返回字符串的回调函数
typedef void(* string_completion_t)(int rc, const char *value, const void *data); // 返回数据的回调函数
typedef void(* data_completion_t)(int rc, const char *value, int value_len, const struct Stat *stat, const void *data); // 返回字符串列表(a list of string)的回调函数
typedef void(* strings_completion_t)(int rc, const struct String_vector *strings, const void *data); // 同时返回字符串列表(a list of string)和 Stat 结构的回调函数
typedef void(* strings_stat_completion_t)(int rc, const struct String_vector *strings, const struct Stat *stat, const void *data); // 以及返回 ACL 信息的回调函数
typedef void(* acl_completion_t)(int rc, struct ACL_vector *acl, struct Stat *stat, const void *data);

可能这么说还不是很理解,那么我们以异步创建 znode 节点(zoo_acreate())为例解释一下:

zoo_acreate函数原型如下:

ZOOAPI int zoo_acreate(zhandle_t * zh, const char *path,const char *value, int valuelen,const struct ACL_vector *acl, int flags,string_completion_t completion, const void *data);

其中参数 string_completion_t completion 即返回字符串的回调函数,那么当 zoo_acreate 调用结束时将会触发 completion 回调函数的调用,同时传递给 completion 的rc 参数为: ZOK 操作完成;ZNONODE 父节点不存在;ZNODEEXISTS 节点已存在;ZNOAUTH 客户端没有权限创建节点。ZNOCHILDRENFOREPHEMERALS 临时节点不能创建子节点。而string_completion_t completion 中 const char *value 参数即新节点的路径名(注:如果 zoo_acreate 设置了ZOO_EPHEMERAL,则创建节点成功后,节点名称并不是 zoo_acreate 中 path 参数所指定的名称,而是类似与 /xyz0000000001,/xyz0000000002... 的名称)。另外,string_completion_t completion 中 const void *data 参数即为 zoo_acreate 中的 const void *data。

一般来说,zoo_acreate 函数可以按照以下方式调用:

    int ret = zoo_acreate(zkhandle, "/xyz", "hello", 5,&ZOO_OPEN_ACL_UNSAFE, 0 /* ZOO_SEQUENCE */,zktest_string_completion, "acreate");if (ret) {fprintf(stderr, "Error %d for %s\n", ret, "acreate");exit(EXIT_FAILURE);}

其中 zktest_string_completion 功能很简单,就是把创建成功后的节点名称打印出来,函数定义如下:

void zktest_string_completion(int rc, const char *name, const void *data)
{fprintf(stderr, "[%s]: rc = %d\n", (char*)(data==0?"null":data), rc);if (!rc) {fprintf(stderr, "\tname = %s\n", name);}
}

好了,有了上面的基础,我们接下来再来讲讲 Zookeeper 异步 API 吧 :-)

Zookeeper C API 中与访问 Zookeeper 服务相关(比如创建、删除 znode 节点,获取子节点,设置 znode 数据等)的异步 API 如下:

ZOOAPI int zoo_acreate(zhandle_t * zh, const char *path,const char *value, int valuelen,const struct ACL_vector *acl, int flags,string_completion_t completion, const void *data);ZOOAPI int zoo_adelete(zhandle_t * zh, const char *path, int version,void_completion_t completion, const void *data);ZOOAPI int zoo_aexists(zhandle_t * zh, const char *path, int watch,stat_completion_t completion, const void *data);ZOOAPI int zoo_awexists(zhandle_t * zh, const char *path,watcher_fn watcher, void *watcherCtx,stat_completion_t completion, const void *data);ZOOAPI int zoo_aget(zhandle_t * zh, const char *path, int watch,data_completion_t completion, const void *data);ZOOAPI int zoo_awget(zhandle_t * zh, const char *path,watcher_fn watcher, void *watcherCtx,data_completion_t completion, const void *data);ZOOAPI int zoo_aset(zhandle_t * zh, const char *path,const char *buffer, int buflen, int version,stat_completion_t completion, const void *data);ZOOAPI int zoo_aget_children(zhandle_t * zh, const char *path,int watch,strings_completion_t completion,const void *data);ZOOAPI int zoo_awget_children(zhandle_t * zh, const char *path,watcher_fn watcher, void *watcherCtx,strings_completion_t completion,const void *data);ZOOAPI int zoo_aget_children2(zhandle_t * zh, const char *path,int watch,strings_stat_completion_t completion,const void *data);ZOOAPI int zoo_awget_children2(zhandle_t * zh, const char *path,watcher_fn watcher, void *watcherCtx,strings_stat_completion_t completion,const void *data);ZOOAPI int zoo_async(zhandle_t * zh, const char *path,string_completion_t completion, const void *data);ZOOAPI int zoo_aget_acl(zhandle_t * zh, const char *path,acl_completion_t completion, const void *data);ZOOAPI int zoo_aset_acl(zhandle_t * zh, const char *path, int version,struct ACL_vector *acl, void_completion_t,const void *data);ZOOAPI int zoo_amulti(zhandle_t * zh, int count, const zoo_op_t * ops,zoo_op_result_t * results, void_completion_t,const void *data);

本文将以上异步 API 细分为以下几类:(1). 创建、删除 znode 节点,(2). 可设置 watch 的 API,(3). 访问、设置节点 ACL 的 API,(4). 异步批处理 API。

  • 创建、删除 znode 节点
ZOOAPI int zoo_acreate(zhandle_t * zh, const char *path,const char *value, int valuelen,const struct ACL_vector *acl, int flags,string_completion_t completion, const void *data);ZOOAPI int zoo_adelete(zhandle_t * zh, const char *path, int version,void_completion_t completion, const void *data);

  • 创建 znode 节点
ZOOAPI int zoo_acreate(zhandle_t * zh, const char *path,const char *value, int valuelen,const struct ACL_vector *acl, int flags,string_completion_t completion, const void *data);

zh zookeeper_init() 返回的 zookeeper 句柄。
path 节点路径。
value 该节点保存的数据。
valuelen 该节点保存数据的大小。
acl 该节点初始 ACL,ACL 不能为null 或空。
flags 该参数可以设置为 0,或者创建标识符 ZOO_EPHEMERALZOO_SEQUENCE 的组合或(OR)。
completion 当创建节点请求完成时会调用该函数,该函数原型详见第三讲《回调函数》一节。同时传递给completion的rc参数为: ZOK 操作完成;ZNONODE 父节点不存在;ZNODEEXISTS 节点已存在;ZNOAUTH 客户端没有权限创建节点。ZNOCHILDRENFOREPHEMERALS 临时节点不能创建子节点。
data completion 函数被调用时,传递给 completion 的数据。
  • 删除 znode 节点
ZOOAPI int zoo_adelete(zhandle_t * zh, const char *path, int version,void_completion_t completion, const void *data);

zh zookeeper_init() 返回的 zookeeper 句柄。
path 节点路径。
version 期望的节点版本号,如果真实的版本号与期望的版本号不同则 zoo_delete() 调用失败,-1 表示不不检查版本号。
completion 当删除节点请求完成时会调用该函数,该函数原型详见第三讲《回调函数》一节。同时传递给completion的rc参数为: ZOK 操作完成;ZNONODE 节点不存在;ZNOAUTH 客户端没有权限删除节点;ZBADVERSION 版包号不匹配;ZNOTEMPTY 当前节点存在子节点,不能被删除。
data completion 函数被调用时,传递给 completion 的数据。
  • 可设置 watch 的 API(exists(两个) + get(两个) + get_children(四个) = 八个)
ZOOAPI int zoo_aexists(zhandle_t * zh, const char *path, int watch,stat_completion_t completion, const void *data);ZOOAPI int zoo_awexists(zhandle_t * zh, const char *path,watcher_fn watcher, void *watcherCtx,stat_completion_t completion, const void *data);ZOOAPI int zoo_aget(zhandle_t * zh, const char *path, int watch,data_completion_t completion, const void *data);ZOOAPI int zoo_awget(zhandle_t * zh, const char *path,watcher_fn watcher, void *watcherCtx,data_completion_t completion, const void *data);ZOOAPI int zoo_aget_children(zhandle_t * zh, const char *path,int watch,strings_completion_t completion,const void *data);ZOOAPI int zoo_awget_children(zhandle_t * zh, const char *path,watcher_fn watcher, void *watcherCtx,strings_completion_t completion,const void *data);ZOOAPI int zoo_aget_children2(zhandle_t * zh, const char *path,int watch,strings_stat_completion_t completion,const void *data);ZOOAPI int zoo_awget_children2(zhandle_t * zh, const char *path,watcher_fn watcher, void *watcherCtx,strings_stat_completion_t completion,const void *data);

  • 检查节点状态 exists(两个,分别是 zoo_aexists() 和 zoo_awexists(),区别是后者可以指定单独的 watcher_fn(监视器回调函数),而前者只能用 zookeeper_init() 设置的全局监视器回调函数,同时 aget 和 aget_children两族函数也一样,带有zoo_w* 的函数可以指定单独的 watcher_fn)。)
ZOOAPI int zoo_aexists(zhandle_t * zh, const char *path, int watch,stat_completion_t completion, const void *data);

zh zookeeper_init() 返回的 zookeeper 句柄。
path 节点路径。
watch 如果非 0,则在服务器端设置监视,当节点发生变化时客户端会得到通知,即使当前指定的节点不存在也会设置监视,这样该节点被创建时,客户端也可以得到通知。
completion 当 zoo_aexists 请求完成时会调用该函数,该函数原型详见第三讲《回调函数》一节。同时传递给completion的rc参数为: ZOK 操作完成;ZNONODE 节点不存在;ZNOAUTH 客户端没有权限删除节点。
data completion 函数被调用时,传递给 completion 的数据。
ZOOAPI int zoo_awexists(zhandle_t * zh, const char *path,watcher_fn watcher, void *watcherCtx,stat_completion_t completion, const void *data);

zh zookeeper_init() 返回的 zookeeper 句柄。
path 节点路径。
watcher 如果非 0,则在服务器端设置监视,当节点发生变化时客户端会得到通知,即使当前指定的节点不存在也会设置监视,这样该节点被创建时,客户端也可以得到通知。
watcherCtx 用户指定的数据,将被传入到监视器回调函数中,与由 zookeeper_init() 设置的全局监视器上下文不同,该函数设置的监视器上下文只与当前的监视器相关联。
completion zoo_awexists 请求完成时会调用该函数,该函数原型详见第三讲《回调函数》一节。同时传递给completion的rc参数为: ZOK 操作完成;ZNONODE 节点不存在;ZNOAUTH 客户端没有权限删除节点。
data completion 函数被调用时,传递给 completion 的数据。
  • 获取节点数据 aget(两个)
ZOOAPI int zoo_aget(zhandle_t * zh, const char *path, int watch,data_completion_t completion, const void *data);

zh zookeeper_init() 返回的 zookeeper 句柄。
path 节点路径。
watch 如果非 0,则在服务器端设置监视,当节点发生变化时客户端会得到通知。
completion zoo_aget 请求完成时会调用该函数,该函数原型详见第三讲《回调函数》一节。同时传递给completion的rc参数为: ZOK 操作完成;ZNONODE 节点不存在;ZNOAUTH 客户端没有权限删除节点。
data completion 函数被调用时,传递给 completion 的数据。
ZOOAPI int zoo_awget(zhandle_t * zh, const char *path,watcher_fn watcher, void *watcherCtx,data_completion_t completion, const void *data);

zh zookeeper_init() 返回的 zookeeper 句柄。
path 节点路径。
watcher 如果非 0,则在服务器端设置监视,当节点发生变化时客户端会得到通知,即使当前指定的节点不存在也会设置监视,这样该节点被创建时,客户端也可以得到通知。
watcherCtx 用户指定的数据,将被传入到监视器回调函数中,与由 zookeeper_init() 设置的全局监视器上下文不同,该函数设置的监视器上下文只与当前的监视器相关联。
completion zoo_awget 请求完成时会调用该函数,该函数原型详见第三讲《回调函数》一节。同时传递给completion的rc参数为: ZOK 操作完成;ZNONODE 节点不存在;ZNOAUTH 客户端没有权限删除节点。
data completion 函数被调用时,传递给 completion 的数据。
  • 获取子节点列表 aget_children (四个)
ZOOAPI int zoo_aget_children(zhandle_t * zh, const char *path,int watch,strings_completion_t completion,const void *data);

zh zookeeper_init() 返回的 zookeeper 句柄。
path 节点路径。
watch 如果非 0,则在服务器端设置监视,当节点发生变化时客户端会得到通知。
completion 当 zoo_aget_children 请求完成时会调用该函数,该函数原型详见第三讲《回调函数》一节。同时传递给completion的rc参数为: ZOK 操作完成;ZNONODE 节点不存在;ZNOAUTH 客户端没有权限删除节点。
data completion 函数被调用时,传递给 completion 的数据。
ZOOAPI int zoo_awget_children(zhandle_t * zh, const char *path,watcher_fn watcher, void *watcherCtx,strings_completion_t completion,const void *data);

zh zookeeper_init() 返回的 zookeeper 句柄。
path 节点路径。
watcher 如果非 0,则在服务器端设置监视,当节点发生变化时客户端会得到通知,即使当前指定的节点不存在也会设置监视,这样该节点被创建时,客户端也可以得到通知。
watcherCtx 用户指定的数据,将被传入到监视器回调函数中,与由 zookeeper_init() 设置的全局监视器上下文不同,该函数设置的监视器上下文只与当前的监视器相关联。
completion 当zoo_awget_children 请求完成时会调用该函数,该函数原型详见第三讲《回调函数》一节。同时传递给completion的rc参数为: ZOK 操作完成;ZNONODE 节点不存在;ZNOAUTH 客户端没有权限删除节点。
data completion 函数被调用时,传递给 completion 的数据。
ZOOAPI int zoo_aget_children2(zhandle_t * zh, const char *path,int watch,strings_stat_completion_t completion,const void *data);

zh zookeeper_init() 返回的 zookeeper 句柄。
path 节点路径。
watch 如果非 0,则在服务器端设置监视,当节点发生变化时客户端会得到通知。
completion 当 zoo_aget_children2 请求完成时会调用该函数,该函数原型详见第三讲《回调函数》一节。同时传递给completion的rc参数为: ZOK 操作完成;ZNONODE 节点不存在;ZNOAUTH 客户端没有权限删除节点。
data completion 函数被调用时,传递给 completion 的数据。
ZOOAPI int zoo_awget_children2(zhandle_t * zh, const char *path,watcher_fn watcher, void *watcherCtx,strings_stat_completion_t completion,const void *data);

zh zookeeper_init() 返回的 zookeeper 句柄。
path 节点路径。
watcher 如果非 0,则在服务器端设置监视,当节点发生变化时客户端会得到通知,即使当前指定的节点不存在也会设置监视,这样该节点被创建时,客户端也可以得到通知。
watcherCtx 用户指定的数据,将被传入到监视器回调函数中,与由 zookeeper_init() 设置的全局监视器上下文不同,该函数设置的监视器上下文只与当前的监视器相关联。
completion 当zoo_awget_children2 请求完成时会调用该函数,该函数原型详见第三讲《回调函数》一节。同时传递给completion的rc参数为: ZOK 操作完成;ZNONODE 节点不存在;ZNOAUTH 客户端没有权限删除节点。
data completion 函数被调用时,传递给 completion 的数据。
  • 访问、设置节点 ACL 的 API
ZOOAPI int zoo_aget_acl(zhandle_t * zh, const char *path,acl_completion_t completion, const void *data);ZOOAPI int zoo_aset_acl(zhandle_t * zh, const char *path, int version,struct ACL_vector *acl, void_completion_t,const void *data);

  • 访问节点 ACL
ZOOAPI int zoo_aget_acl(zhandle_t * zh, const char *path,acl_completion_t completion, const void *data);

zh zookeeper_init() 返回的 zookeeper 句柄。
path 节点路径。
completion 当 zoo_aget_acl 请求完成时会调用该函数,该函数原型详见第三讲《回调函数》一节。同时传递给completion的rc参数为: ZOK 操作完成;ZNONODE 节点不存在;ZNOAUTH 客户端没有权限删除节点。
data completion 函数被调用时,传递给 completion 的数据。
  • 设置节点 ACL
ZOOAPI int zoo_aset_acl(zhandle_t * zh, const char *path, int version,struct ACL_vector *acl, void_completion_t,const void *data);

zh zookeeper_init() 返回的 zookeeper 句柄。
path 节点路径。
buffer 保存需要设置的 ACL。
buflen buffer 的长度。
completion 当 zoo_aset_acl 请求完成时会调用该函数,该函数原型详见第三讲《回调函数》一节。同时传递给completion的rc参数为: ZOK 操作完成;ZNONODE 节点不存在;ZNOAUTH 客户端没有权限删除节点;ZINVALIDACL 非法 ACL;ZBADVERSION 版本号不匹配。
data completion 函数被调用时,传递给 completion 的数据。
  • 异步批处理 API
ZOOAPI int zoo_amulti(zhandle_t * zh, int count, const zoo_op_t * ops,zoo_op_result_t * results, void_completion_t,const void *data);

异步批处理与同步批处理方式类似见《Zookeeper C API 指南五(同步 API 介绍)》,只是需要额外设置一个 void_completion_t 回调函数,在此不再赘述。

以上内容是 Zookeeper  C API 中异步 API 的介绍,如有错误请留下您的想法和意见,我会尽快更正;同时,我也将在后面的文章中列举一些示例来说明上述 API 的用法,如有兴趣请继续关注。

在前面的文章中我们讲了大部分 Zookeeper  C API,相信大家已经知道怎样使用 Zookeeper  C API了吧。我曾在《Zookeeper C API 指南》系列文章的第四篇《Zookeeper C API 指南四(C API 概览)》中也 Zookeeper C API 的分为了 5 类,他们分别是:(1)、初始化、销毁 Zookeeper 句柄,(2)、与 zoo_multi() 和 zoo_amulti() 批量操作相关的 zoo_op_t 初始化函数,(3)、同步 API,(4)、异步 API,(5)、辅助函数。其中“(1)、初始化、销毁 Zookeeper 句柄”已经在《Zookeeper C API 指南四(C API 概览)》中介绍过了,并且在《Zookeeper C API 指南五(同步 API 介绍)》和《Zookeeper C API 指南六(异步 API 介绍)》中我们又分别讲了“同步 API” 和 “异步 API”(其中也简单地介绍了一下与 zoo_multi() 和 zoo_amulti() 批量操作相关的 zoo_op_t 初始化函数),所以接下来我们再来讲讲 Zookeeper C API 中的 “辅助函数”。

我们分类的 Zookeeper C API 中辅助函数如下:

ZOOAPI void zoo_set_debug_level(ZooLogLevel logLevel);ZOOAPI void zoo_set_log_stream(FILE * logStream);ZOOAPI const clientid_t *zoo_client_id(zhandle_t * zh);ZOOAPI int zoo_recv_timeout(zhandle_t * zh);ZOOAPI const void *zoo_get_context(zhandle_t * zh);ZOOAPI void zoo_set_context(zhandle_t * zh, void *context);ZOOAPI watcher_fn zoo_set_watcher(zhandle_t * zh, watcher_fn newFn);ZOOAPI struct sockaddr *zookeeper_get_connected_host(zhandle_t * zh, struct sockaddr*addr,socklen_t * addr_len);ZOOAPI int zookeeper_interest(zhandle_t * zh, int *fd, int *interest,struct timeval *tv);ZOOAPI int zookeeper_process(zhandle_t * zh, int events);ZOOAPI int zoo_state(zhandle_t * zh);ZOOAPI const char *zerror(int c);ZOOAPI int is_unrecoverable(zhandle_t * zh);ZOOAPI void zoo_deterministic_conn_order(int yesOrNo);

下面我们来讲讲其中比较常用的几个函数吧 :-)

  • 设置日志等级
ZOOAPI void zoo_set_debug_level(ZooLogLevel logLevel);

其中 logLevel 可以是ZOO_LOG_LEVEL_ERROR, ZOO_LOG_LEVEL_WARN,ZOO_LOG_LEVEL_INFO, ZOO_LOG_LEVEL_DEBUG四个取值。

  • 设置日志流
ZOOAPI void zoo_set_log_stream(FILE * logStream);

Zookeeper C API 默认的日志流是标准输出,可以通过该函数设置 Zookeeper C API的日志流为文件。

  • 获取客户端的 session id,只有在客户端的当前连接状态有效时才可以。
ZOOAPI const clientid_t *zoo_client_id(zhandle_t * zh);

  • 返回当前会话的超时时间,只有在客户端的当前连接状态有效时才可以。
ZOOAPI int zoo_recv_timeout(zhandle_t * zh);

  • 获取 Zookeeper 句柄的上下文。
ZOOAPI const void *zoo_get_context(zhandle_t * zh);

  • 设置 Zookeeper 句柄的上下文。
ZOOAPI void zoo_set_context(zhandle_t * zh, void *context);

  • 设置 Zookeeper 句柄的全局监视器回调函数,该函数返回全局监视器的旧回调函数。
ZOOAPI watcher_fn zoo_set_watcher(zhandle_t * zh, watcher_fn newFn);

  • 返回当前 Zookeeper 连接的套接字地址。
ZOOAPI struct sockaddr *zookeeper_get_connected_host(zhandle_t * zh, struct sockaddr*addr,socklen_t * addr_len);

  • 获取当前 Zookeeper 连接状态。
ZOOAPI int zoo_state(zhandle_t * zh);

  • 返回某一错误码的字符串表示。
ZOOAPI const char *zerror(int c);

  • 检查当前 Zookeeper 连接是否为不可恢复的,如果不可恢复,则客户端需要关闭连接,然后重连。
ZOOAPI int is_unrecoverable(zhandle_t * zh);

好了,Zookeeper 大部分的辅助函数就介绍到这里了,大家可以更多文档可以在 zookeeper.h 中找到。:-)

前面七讲我们基本上介绍完了 Zookeeper C API 的所有内容,本文将结合一个小例子讲讲如何在你的实际项目中使用 Zookeeper 服务。

设想如下场景:

假设程序 A 需要 7* 24 小时在线对外提供服务,但是 A 程序在生产环境下总是不稳定,时常崩溃,不过幸运的是解决方案很简单,在 A 程序崩溃以后只需要重启它就可以了。当然如此简单的问题你可以提出多种解决方案,比方说自己实现一个服务程序,每隔一定时间去轮询 A 的状态,如果发现 A 崩溃了,立即重启它,并向管理人员报告问题。不过我们并不打算这么做,毕竟本文主题是讲 Zookeeper C API 的应用,所以我们采用 Zookeeper 服务来解决该问题。

若采用 Zookeeper 服务可以按照如下方案解决问题,程序 A 在启动时创建一个临时(ZOO_EPHEMERAL) znode 节点 /A,然后按照正常流程对外提供服务。另外监控程序对 /A 节点设置监视,当 /A 节点消失(说明 A 程序已经崩溃)时,重启 A 程序。假设 A 的名称是 QueryServer,即对外提供查询服务的程序,具体提供什么查询服务由应用自身决定,我们这里只是简单地模拟一下。QueryServer 在启动时创建一个 /QueryServer 的临时节点(ZOO_EPHEMERAL),然后,程序 QueryServerd 监控 /QueryServer 节点,当 /QueryServer 节点消失(说明 A 程序已经崩溃)时,重启 QueryServer 程序。

下面是 QueryServer 的实现代码:

/** =============================================================================**       Filename:  QueryServer.c**    Description:  QueryServer**        Created:  02/15/2013 08:48:49 PM**         Author:  Fu Haiping (forhappy), haipingf@gmail.com*        Company:  ICT ( Institute Of Computing Technology, CAS )** =============================================================================*/
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <zookeeper/zookeeper.h>
#include <zookeeper/zookeeper_log.h>void QueryServer_watcher_g(zhandle_t* zh, int type, int state,const char* path, void* watcherCtx)
{if (type == ZOO_SESSION_EVENT) {if (state == ZOO_CONNECTED_STATE) {printf("[[[QueryServer]]] Connected to zookeeper service successfully!\n");} else if (state == ZOO_EXPIRED_SESSION_STATE) { printf("Zookeeper session expired!\n");}}
}void QueryServer_string_completion(int rc, const char *name, const void *data)
{fprintf(stderr, "[%s]: rc = %d\n", (char*)(data==0?"null":data), rc);if (!rc) {fprintf(stderr, "\tname = %s\n", name);}
}void QueryServer_accept_query()
{printf("QueryServer is running...\n");
}int main(int argc, const char *argv[])
{const char* host = "127.0.0.1:2181,127.0.0.1:2182,""127.0.0.1:2183,127.0.0.1:2184,127.0.0.1:2185";int timeout = 30000;zoo_set_debug_level(ZOO_LOG_LEVEL_WARN);zhandle_t* zkhandle = zookeeper_init(host,QueryServer_watcher_g, timeout, 0, "hello zookeeper.", 0);if (zkhandle == NULL) {fprintf(stderr, "Error when connecting to zookeeper servers...\n");exit(EXIT_FAILURE);}// struct ACL ALL_ACL[] = {{ZOO_PERM_ALL, ZOO_ANYONE_ID_UNSAFE}};// struct ACL_vector ALL_PERMS = {1, ALL_ACL};int ret = zoo_acreate(zkhandle, "/QueryServer", "alive", 5,&ZOO_OPEN_ACL_UNSAFE, ZOO_EPHEMERAL,QueryServer_string_completion, "zoo_acreate");if (ret) {fprintf(stderr, "Error %d for %s\n", ret, "acreate");exit(EXIT_FAILURE);}do {// 模拟 QueryServer 对外提供服务.// 为了简单起见, 我们在此调用一个简单的函数来模拟 QueryServer.// 然后休眠 5 秒,程序主动退出(即假设此时已经崩溃).
        QueryServer_accept_query();sleep(5);} while(false);zookeeper_close(zkhandle);
}

Makefile如下:

all:QueryServerQueryServer:QueryServer.ogcc -L/usr/local/lib/ -lzookeeper_mt -o $@ $^ QueryServer.o:QueryServer.cgcc -DTHREADED -I/usr/local/include/zookeeper -o $@ -c $^.PHONY:cleanclean:rm QueryServer.o QueryServer

QueryServerd 代码如下:

/** =============================================================================**       Filename:  QueryServerd.c**    Description:  QueryServer daemon using zookeeper. **        Created:  02/15/2013 08:48:49 PM**         Author:  Fu Haiping (forhappy), haipingf@gmail.com*        Company:  ICT ( Institute Of Computing Technology, CAS )** =============================================================================*/
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <zookeeper/zookeeper.h>
#include <zookeeper/zookeeper_log.h>void QueryServerd_watcher_global(zhandle_t * zh, int type, int state,const char *path, void *watcherCtx);
static void QueryServerd_dump_stat(const struct Stat *stat);
void QueryServerd_stat_completion(int rc, const struct Stat *stat,const void *data);
void QueryServerd_watcher_awexists(zhandle_t *zh, int type, int state,const char *path, void *watcherCtx);
static void QueryServerd_awexists(zhandle_t *zh);void
QueryServerd_watcher_global(zhandle_t * zh, int type, int state,const char *path, void *watcherCtx)
{if (type == ZOO_SESSION_EVENT) {if (state == ZOO_CONNECTED_STATE) {printf("Connected to zookeeper service successfully!\n");} else if (state == ZOO_EXPIRED_SESSION_STATE) { printf("Zookeeper session expired!\n");}}
}static void
QueryServerd_dump_stat(const struct Stat *stat)
{char tctimes[40];char tmtimes[40];time_t tctime;time_t tmtime;if (!stat) {fprintf(stderr, "null\n");return;}tctime = stat->ctime / 1000;tmtime = stat->mtime / 1000;ctime_r(&tmtime, tmtimes);ctime_r(&tctime, tctimes);fprintf(stderr, "\tctime = %s\tczxid=%llx\n""\tmtime=%s\tmzxid=%llx\n""\tversion=%x\taversion=%x\n""\tephemeralOwner = %llx\n",tctimes, stat->czxid,tmtimes, stat->mzxid,(unsigned int) stat->version, (unsigned int) stat->aversion,stat->ephemeralOwner);
}void
QueryServerd_stat_completion(int rc, const struct Stat *stat,const void *data)
{// fprintf(stderr, "%s: rc = %d Stat:\n", (char *) data, rc);// QueryServerd_dump_stat(stat);
}void
QueryServerd_watcher_awexists(zhandle_t *zh, int type, int state,const char *path, void *watcherCtx)
{if (state == ZOO_CONNECTED_STATE) {if (type == ZOO_DELETED_EVENT) {printf("QueryServer gone away, restart now...\n");// re-exists and set watch on /QueryServer again.
            QueryServerd_awexists(zh);pid_t pid = fork();if (pid < 0) {fprintf(stderr, "Error when doing fork.\n");exit(EXIT_FAILURE);}if (pid == 0) { /* child process */// 重启 QueryServer 服务.execl("/tmp/QueryServer/QueryServer", "QueryServer", NULL);exit(EXIT_SUCCESS);}sleep(1); /* sleep 1 second for purpose. */} else if (type == ZOO_CREATED_EVENT) {printf("QueryServer started...\n");}}// re-exists and set watch on /QueryServer again.
    QueryServerd_awexists(zh);
}static void
QueryServerd_awexists(zhandle_t *zh)
{int ret =zoo_awexists(zh, "/QueryServer",QueryServerd_watcher_awexists,"QueryServerd_awexists.",QueryServerd_stat_completion,"zoo_awexists");if (ret) {fprintf(stderr, "Error %d for %s\n", ret, "aexists");exit(EXIT_FAILURE);}
}int
main(int argc, const char *argv[])
{const char *host = "127.0.0.1:2181,127.0.0.1:2182,""127.0.0.1:2183,127.0.0.1:2184,127.0.0.1:2185";int timeout = 30000;zoo_set_debug_level(ZOO_LOG_LEVEL_WARN);zhandle_t *zkhandle = zookeeper_init(host,QueryServerd_watcher_global,timeout,0, "QueryServerd", 0);if (zkhandle == NULL) {fprintf(stderr, "Error when connecting to zookeeper servers...\n");exit(EXIT_FAILURE);}QueryServerd_awexists(zkhandle);// Wait for asynchronous zookeeper call done.
    getchar();zookeeper_close(zkhandle);return 0;
}

Makefile 如下:

all:QueryServerdQueryServerd:QueryServerd.ogcc -L/usr/local/lib/ -lzookeeper_mt -o $@ $^ QueryServerd.o:QueryServerd.cgcc -g -DTHREADED -I/usr/local/include/zookeeper -o $@ -c $^.PHONY:cleanclean:rm QueryServerd.o QueryServerd

首先执行 QueryServerd,

forhappy@haiping-ict:/tmp/QueryServerd$ ./QueryServerd
Connected to zookeeper service successfully!

然后执行 QueryServer,

forhappy@haiping-ict:/tmp/QueryServer$ ./QueryServer
QueryServer is running...
[[[QueryServer]]] Connected to zookeeper service successfully!
[zoo_acreate]: rc = 0name = /QueryServer

可见 Queryerver 创建了 /QueryServer 节点,5 秒后 QueryServer 模拟程序崩溃而退出,那么此时在 QueryServerd 端输出如下:

Connected to zookeeper service successfully!
QueryServer started... # QueryServerd 感知到 QueryServer 已正常启动.
QueryServer gone away, restart now... # 5 秒钟后,QueryServer 崩溃,QueryServerd 准备重启 QueryServer.
QueryServer is running... # QueryServer 正在运行,以下 3 行是 QueryServer 输出结果。
[[[QueryServer]]] Connected to zookeeper service successfully!
[zoo_acreate]: rc = 0name = /QueryServer
QueryServer started... # QueryServerd 感知到 QueryServer 已正常启动.
QueryServer gone away, restart now...# 又过了 5 秒钟后,QueryServer 崩溃,QueryServerd 准备重启 QueryServer.
QueryServer is running... # QueryServer 再次运行,以下 3 行是 QueryServer 输出结果。
[[[QueryServer]]] Connected to zookeeper service successfully!
[zoo_acreate]: rc = 0name = /QueryServer
QueryServer started... # QueryServerd 再次感知到 QueryServer 已正常启动,如此反复.
QueryServer gone away, restart now...
QueryServer is running...
[[[QueryServer]]] Connected to zookeeper service successfully!
[zoo_acreate]: rc = 0name = /QueryServer
QueryServer started...

即 QueryServer 每 5 秒钟崩溃一次,然后又被 QueryServerd 重启,模拟了上面的应用场景。

好了 Zookeeper C API 的应用小示例讲完了,可能应用场景选取的不好,不过大致可以一些说明问题吧,如果你想看 Zookeeper 更贴近现实的应用场景,可以参考淘宝的一篇文章《ZooKeeper典型应用场景一览》和 IBM developerWorks 的一篇博文《分布式服务框架 Zookeeper -- 管理分布式环境中的数据》。

Zookeeper C API 指南相关推荐

  1. Zookeeper C API 指南一(转)

    Zookeeper 监视(Watches) 简介 Zookeeper C API 的声明和描述在 include/zookeeper.h 中可以找到,另外大部分的 Zookeeper C API 常量 ...

  2. Zookeeper C API 指南六(异步 API 介绍)

    转载地址:https://www.cnblogs.com/haippy/archive/2013/02/21/2920426.html 上一讲<Zookeeper C API 指南五(同步 AP ...

  3. zookeeper使用(三):项目中zookeeper的配置 和 zookeeper C API的使用

    zookeeper使用(三):项目中zookeeper的配置 和 zookeeper C API的使用 一:假定一个项目需求 假设我要启动多台proxy服务,有一个网页客户端需要连接我这台proxy服 ...

  4. zookeeper C API实例

    之前一直用zookeeper的java API,最近要用C开发,所以看了C的API及实现 使用zookeeper C API 的步骤: (1)安装zookeeper,直接下载,解压,配置文件即可 (2 ...

  5. ZooKeeper程序员指南--使用ZooKeeper开发分布式应用程序

    ZooKeeper程序员指南 开发使用ZooKeeper的分布式应用程序 介绍 ZooKeeper数据模型 ZNodes 手表 数据访问 短暂的节点 序列节点 - 唯一命名 容器节点 TTL节点 在Z ...

  6. 2021年大数据ZooKeeper(五):ZooKeeper Java API操作

    目录 ZooKeeper Java API操作 引入maven坐标 节点的操作 ZooKeeper Java API操作 这里操作Zookeeper的JavaAPI使用的是一套zookeeper客户端 ...

  7. 【ZooKeeper Notes 3】ZooKeeper Java API 使用样例

    查看PDF版本 转载请注明:@ni掌柜 nileader@gmail.com ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务框架,包含一组简单的原语集合.通过这些原语言的组合使用, ...

  8. Django REST framework API 指南(2):响应

    Django REST framework API 指南(1):请求 Django REST framework API 指南(2):响应 Django REST framework API 指南(3 ...

  9. 微信公众平台自定义菜单接口API指南

    微信公众平台开发模式自定义菜单接口API指南 开发实现方法,请查看 微信公众平台开发(58)自定义菜单 简介 开发者获取使用凭证(如何获取凭证)后,可以使用该凭证对公众账号的自定义菜单进行创建.查询和 ...

最新文章

  1. 重大里程碑!VOLO屠榜CV任务,无需额外数据,首次在ImageNet 上达到87.1%
  2. ubuntu “快捷方式”
  3. 《数据分析变革:大数据时代精准决策之道》一1.5 小结
  4. 硬件工程师电路设计必须紧记的十大要点
  5. Android之解决JsonObject里面的JsonArray数据会有斜杠问题
  6. Selenium自动化测试
  7. 手写一些js方法收集:
  8. ArcCatalog导出数据
  9. html5图标用什么格式转换,ico转png 怎么把ico格式转换成png格式
  10. 贪心法--->1.会议安排问题
  11. CNN Explainer
  12. NPN、PNP型三极管的常见问题
  13. 铝电解电容的寿命计算(纹波电流法)手把手教你
  14. Android版本+pwa,微博pwa版本下载
  15. maya腿的蒙皮旋转枢轴_python Maya烘焙枢轴
  16. spring cloud hystirx熔断器
  17. LDO与三端稳压器详解
  18. Matlab中vpa一直在忙,matlab vpa 函数是什么意思?
  19. 单片机故障排除篇--如何判定是硬件问题还是软件问题?
  20. 初中计算机学业水平考试,初中信息技术学业水平考试知识点(操作题)

热门文章

  1. Android Wifi 启动过程,AndroidP wifi启动流程
  2. SpringMVC学习03之使用注解开发SpringMVC
  3. python 列表 随机采样_Python 随机抽样
  4. 跳转路径时资源路径加/和不加/的区别
  5. 【数学和算法】初识卡尔曼滤波器(四)
  6. Learning hard C#学习笔记 孤军深入
  7. Java NIO 入门
  8. 谈谈Java反射机制
  9. LaTeX技巧24:LaTeX常用命令集锦
  10. java学习笔记3--类与对象的基础