Zookeeper C API学习笔记(ubuntu)

目录

一、zookeeper安装(包括C库的安装)

二、需要的头文件

三、需要的动态库

四、C API

1.连接到zookeeper服务器

2.创建节点

3.订阅节点

4.订阅子节点

5.获取子节点列表

6.获取节点数据

附录


一、zookeeper安装(包括C库安装)

1.下载zookeeper包

下载链接:http://archive.apache.org/dist/zookeeper/

进入要下载的版本的目录,选择.tar.gz文件下载

目前使用的是3.5.4

2.将zookeeper包拷贝到系统/home/pdds/目录下,使用tar -zxf zookeeper-3.5.4-beta.tar.gz命令解压

3.配置

在主目录下创建data和logs两个目录用于存储数据和日志:

cd /home/pdds/zookeeper-3.5.4-beta

mkdir data

mkdir logs

在/home/pdds/zookeeper-3.5.4-beta/conf目录下新建zoo.cfg文件,写入以下内容保存:

tickTime=2000

dataDir=/home/pdds/zookeeper-3.5.4-beta/data

dataLogDir=/home/pdds/zookeeper-3.5.4-beta/logs

clientPort=2181

4.安装jdk

https://blog.csdn.net/smile_from_2015/article/details/80056297

5.启动zookeeper

cd /home/pdds/zookeeper-3.5.4-beta/bin

./zkServer.sh start

启动成功则zookeeper安装完毕

6.安装zookeeper的C库

cd /home/pdds/zookeeper-3.5.4-beta/src/c/

./configure

make

sudo make install

二、需要的头文件

#include <zookeeper/zookeeper.h>
#include <zookeeper/zookeeper_log.h>

编译时需要制定这两个头文件位置:-I/usr/local/include/zookeeper

三、需要的动态库

zookeeper有两个动态库,libzookeeper_st.so和libzookeeper_mt.so,分别用于单线程和多线程模式,目前主要使用多线程模式。

编译使需要链接多线程库-L/usr/local/lib/ -lzookeeper_mt,其中/usr/local/lib/为源码安装后libzookeeper_mt.so的默认路径

如果运行时出现error while loading shared libraries: libzookeeper_mt.so.2: cannot open shared object file: No such file or directory

说明程序运行时寻找动态库的路径是/usr/lib/i386-linux-gnu/(32位系统)/或/usr/lib/x86_64-linux-gnu(64位系统),只需要把

/usr/local/lib/路径下的 libzookeeper_mt.so.2复制到/usr/lib/i386-linux-gnu//或/usr/lib/x86_64-linux-gnu即可

四、C API

参考:

https://blog.csdn.net/yangzhen92/article/details/53248294

https://cailin.iteye.com/blog/2014486/

https://www.cnblogs.com/caosiyang/archive/2012/11/09/2763190.html

https://www.cnblogs.com/xiohao/p/5541093.html

1.连接到zookeeper服务器

我们的程序启动之后,首先要连接到zookeeper服务器,然后才能进行创建节点和订阅等操作。以下为实现连接到zookeeper服务器的代码。

#define ZK_SERVER_IP   "192.168.1.200"
#define ZK_SERVER_PORT 2181

zhandle_t* zkhandle = NULL;

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("Connected to zookeeper service successfully!\n");
		}
		else if (state == ZOO_EXPIRED_SESSION_STATE) {
			printf("Zookeeper session expired!\n");
		}
	}
}

int main()
{
    //连接到zk服务器
    char zk_server[25];
    memset(zk_server, 0, 25);
    sprintf(zk_server, "%s:%d", ZK_SERVER_IP, ZK_SERVER_PORT);
    const char* host = zk_server;
    int timeout = 30000;
    zoo_set_debug_level(ZOO_LOG_LEVEL_WARN);

    zkhandle = zookeeper_init(host, QueryServer_watcher_g, timeout, 0,(void*)"hellozookeeper.", 0);
    if (zkhandle == NULL) {
        printf("Error when connecting to zookeeper servers...\n");
    }
        
    zookeeper_close(zkhandle);
    return;
}

运行代码后如果屏幕打印Connected to zookeeper service successfully!则说明程序成功连接到了zookeeper服务器。

zoo_set_debug_level用于设置zookeeper日志级别,ZOO_LOG_LEVEL_DEBUG为调试级别。

这里使用到了zookeeper_init接口,原型如下:

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

参数说明:

(1)host:zookkeeper服务器的地址,格式IP:PORT,注意一定是const char *类型的,传参时需要注意const char *类型的赋值方式

(2)fn:全局的监视器回调函数,当发生事件通知时,该函数会被调用,watcher_fn函数原型如下:

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

其中type为事件类型,state为状态类型,path为触发监视事件zonode节点的路径,如果为NULL,则事件类型为ZOO_SESSION_EVENT,watcherCtx为监视器上下文,type和state的具体取值见附录,后两个参数目前我没有用到过

(3)timeout:超时时间,我没试过,我理解单位应该是秒,超时后会收到ZOO_EXPIRED_SESSION_STATE

(4)clientid:客户端尝试重连的先前会话的ID,如果不需要重连先前的会话,则设置为0。

(5)context:与zhandle_t实例相关联的“上下文对象”(可以通过该参数为 zhandle_t 传入自定义类型的数据),应用程序可以通过 zoo_get_context 访问它(例如在监视器回调函数中),当然 zookeeper 内部没有用到该参数,所以 context 可以设置为 NULL。
 

(6)flags:一般设置为0。

2.创建节点(服务注册)

连接zookeeper服务器成功后,我们通常需要建立一个代表自己的节点。

zookeeper创建节点的代码如下。

    int ret = zoo_acreate(zkhandle, "/DISP/DISP2", "1", 1, &ZOO_OPEN_ACL_UNSAFE, 0, NULL, NULL);
    if (ret) {
        printf("Error zoo_acreate\n");
    }

这里用到了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);
参数说明:

(1)path:创建的节点路径,如果该路径已经存在,则该函数不生效

(2)value:该节点保存的数据

(3)valuelen:数据长度

(4)acl: 该节点初始 ACL,ACL 不能为null 或空。zookeeper使用ACL(Access Control List)控制对节点的访问。

(5)flags:该参数可以设置为 0,或者创建标识符 ZOO_EPHEMERAL(临时节点), ZOO_SEQUENCE (顺序节点),如果设置ZOO_EPHEMERAL,客户端会话失效,节点将自动删除;如果设置ZOO_SEQUENCE,一个唯一的自动增加的序列号附加到路径名,序列号宽度是10个数字的宽度,不足用0填充

tip1:如果要设置临时顺序节点,这里应填ZOO_SEQUENCE | ZOO_EPHEMERAL

tip2:如果flags填0或ZOO_EPHEMERAL,path应为节点名,如/PATH/PATH1,如果flags填ZOO_SEQUENCE,path应为路径,如/PATH/,区别在于最后是否加/

tip3:如果创建的是顺序节点,比如已经创建了000,001,如果这时001被删除了,再创建的节点是002,即节点号一直递增,不管前面是否连续

(6)completion:创建节点请求完成后调用的函数,不使用时填为NULL

我在使用的时候一开始这里填了NULL,结果执行完zoo_acreate之后,无论是sleep还是其他方式想让程序阻塞住都会导致程序崩溃,后来使用了这个函数,函数里只有一行打印,程序就不会崩溃了,具体原因还没有想清楚

string_completion_t 原型为typedef void(* string_completion_t)(int rc, const char *value, const void *data);其中data为传入的数据,rc是异步返回的错误码,value是返回的字符串

(7)data:completion 函数被调用时,传递给 completion 的数据,不使用时填NULL

创建节点完成之后,可以在系统使用命令行查看刚刚创建的节点以及节点的内容

cd /home/pdds/zookeeper-3.5.4-beta/bin

./zkCli.sh -server 192.168.1.200:2181

使用ls命令可以列出节点

使用get命令可以获取节点数据

3.订阅节点(服务发现)

连接zookeeper服务器成功之后,程序可以订阅其他节点,这样就可以在其他节点状态改变之后得到通知。

为了实现订阅功能,封装了一下几个函数

void QueryServerd_awexists1(zhandle_t *zh)  
{
	int ret = zoo_awexists(zh, "/MSC/MSC1", QueryServerd_watcher_awexists1, (void*)"QueryServerd_awexists.", QueryServerd_stat_completion, "zoo_awexists");
	if (ret) {
		printf("Error zoo_awexists!\n");
	}
}

这个函数用于订阅,里面调用了zoo_awexists接口,原型如下:

ZOOAPI int zoo_awexists(zhandle_t * zh, const char *path,
                        watcher_fn watcher, void *watcherCtx,
                        stat_completion_t completion, const void *data);
参数说明:

(1)path:订阅的节点路径

(2)watcher:节点状态发生变化时的回调函数

(3)watcherCtx:传入watcher函数的参数

(4)completion:zoo_awexists执行完毕之后执行的函数

(5)data:传入completion的参数

需要注意的是zoo_awexists是一次性的,即订阅的节点发生状态变化调用回调函数之后,订阅就失效了,所以回调函数最后要再建立起订阅

void QueryServerd_stat_completion(int rc, const struct Stat *stat, const void *data)
{
    printf("Zoo_awexists complete!\n");
}

这是zoo_awexists执行完毕之后会调用的函数,这里只打印了zoo_awexists成功

void QueryServerd_watcher_awexists1(zhandle_t *zh, int type, int state, const char *path, void *watcherCtx)
{
	if (state == ZOO_CONNECTED_STATE) {
		if (type == ZOO_DELETED_EVENT) {
			printf("MSC has gone, need to restart it\n");
		}
		else if (type == ZOO_CREATED_EVENT) {
			printf("MSC started...\n");
		}
	}
    QueryServerd_awexists1(zh);
}

这是订阅节点状态发生变化时会调用的回调函数,函数类型为watcher_fn,原型上文有提到过,注意这个函数最后要再调用一次订阅函数,这是因为上面提到过的订阅是一次性的

tip1:在订阅A节点之后,A节点创建或者删除都会触发回调函数,例如运行zkCli.sh,执行create A或delete A都会立即触发回调函数,但是,如果另一个进程T运行了zookeeper客户端程序并创建了A,那么需要注意两点,第一,A必须是临时节点,即创建时要使用ZOO_EPHEMERAL类型,否则进程T结束A并不会被删除,回调函数也不会触发;第二如果A是临时节点,进程T意外退出,那么只有在zookeeper服务器没有按时收到进程T发来的握手时才会删除A,回调函数才会触发,因此回调函数何时触发由zookeeper客户端和服务器的握手周期决定。在zookeeper的配置文件中(/home/pdds/zookeeper-3.5.4-beta/conf/zoo.cfg),tickTime参数代表ZK中的一个时间单元。ZK中所有时间都是以这个时间单元为基础,进行整数倍配置的,因此,调小tickTime可以减小zookeeper客户端和服务器之间的握手周期,但是同样也会使zookeeper的CPU占用率增加。tickTime默认为2000,此时握手周期为20多秒,zookeeper的CPU占用为0.5%,将tickTime调为20,握手周期小于1秒,即进程T退出后,能在1秒之内触发回调函数,但此时zookeeper的CPU占用率增长到了1.5%。

tip2:zoo_awexists只能订阅节点创建(ZOO_CREATED_EVENT)、节点删除(ZOO_DELETED_EVENT)和节点内容改变(ZOO_CHANGED_EVENT),不能订阅节点的子节点列表信息(ZOO_CHILD_EVENT)

4.订阅子节点

由于zoo_awexists不能订阅子节点信息,因此想要获取子节点信息时需要使用另一个接口,zoo_awget_children,这个接口只能订阅子节点列表信息,包括子节点的添加和删除,不能订阅子节点的内容变化,也不能订阅本节点的增加删除和内容变化。

使用zoo_awget_children的方法与zoo_awexists类似,也是封装了三个函数

void QueryServerd_awexists1_child(zhandle_t *zh)  
{
	int ret = zoo_awget_children(zkhandle, "/MSC", QueryServerd_watcher_awexists_child, NULL, QueryServerd_stat_completion_child, NULL);
	if (ret) {
		printf("Error QueryServerd_awexists1_child!\n");
	}
}

void QueryServerd_watcher_awexists_child(zhandle_t *zh, int type, int state, const char *path, void *watcherCtx)
{
	if (state == ZOO_CONNECTED_STATE) //会话已建立
    {
		if(type == ZOO_CHILD_EVENT)
        {
            printf("MSC child changed...\n");
        }
	}
    else
    {
        printf("Other type...\n");
    }
    QueryServerd_awexists1_child(zh);
}

void QueryServerd_stat_completion_child(int rc, const struct String_vector *strings, const void *data)
{
    printf("Zoo_awget_children complete!\n");
}

5.获取子节点列表

通过zoo_awget_children只能获取子节点列表改变信息,当得知子节点列表改变时,想要获取最新的子节点信息,包括增加、删除了哪些子节点,就需要主动获取子节点信息。

这里没有设置watcher,所以只用了两个函数

void QueryServerd_awexists1_child_list(zhandle_t *zh)  
{
    int ret = zoo_aget_children(zkhandle, "/MSC", 0, QueryServerd_strings_completion_child, NULL);
	if (ret) {
		printf("Error QueryServerd_awexists1_child_list!\n");
	}
}

这个函数用于向zookeeper服务器获取/MSC节点的子节点列表,其中用到了zoo_aget_children接口,原型如下:

ZOOAPI int zoo_aget_children(zhandle_t * zh, const char *path,
                             int watch,
                             strings_completion_t completion,
                             const void *data);
参数说明:

(1)watch:如果设为非0值,zookeeper服务器会启动一个watcher监听节点消息,但是这个watcher在哪执行什么我没搞清楚,所以我用的时候填成了0,之后自己再订阅。

(2)completion:zoo_aget_children执行完毕运行的函数

(3)data:传入zoo_aget_children的参数

void QueryServerd_strings_completion_child(int rc, const struct String_vector *strings, const void *data)
{
    printf("QueryServerd_strings_completion_child complete, child_num is %d!\n", (int)strings->count);
    int i = 0, n = (int)strings->count;
    for(i = 0; i < n; i++)
        printf("path%d is %s\n", i, strings->data[i]);

    //释放内存
    deallocate_String_vector(strings);
}

这是zoo_aget_children执行完毕运行的函数,原型如下:

typedef void(* strings_completion_t)(int rc, const struct String_vector *strings, const void *data);

参数说明:

strings为子节点列表查询结果,是一个String_vector类型的结构体,String_vector结构体定义如下

struct String_vector {
    int32_t count;
    char * *data;

};

其中count为子节点个数,data存储了所有节点的路径

tip:这里strings在调用api时会通过malloc分配内存空间,将子节点所有的目录存放在data字段中,需要客户端调用deallocate_String_vector(strings)做释放处理。

6.获取节点数据

zookeeper的API一般都有同步异步两个版本,同步是等到执行完再继续执行下面的代码,异步是执行之后直接运行下面的代码,等执行结果出来以后再执行回调函数。

获取节点数据的同步API是zoo_get,原型如下:

int zoo_get(zhandle_t *zh, const char *path, int watch, char *buffer, int* buffer_len, struct Stat *stat);

调用这个接口完成之后,path节点的数据会存储在buffer里,数据长度存在buffer_len里,

但是很奇怪,我调用这个接口每次得到的buffer_len都是0,之后我试了一下异步接口zoo_aget,就成功读到了数据

zoo_aget原型如下:

int zoo_aget(zhandle_t *zh, const char *path, int watch, data_completion_t completion, const void *data);

这个接口是异步的,执行成功之后会回调completion函数,data_completion_t 原型如下

typedef void(* data_completion_t)(int rc, const char *value, int value_len, const struct Stat *stat, const void *data);

其中value是节点的数据,value_len是数据长度。

//获取节点数据
zoo_aget(zkhandle, "/MSC", 0, QueryServerd_data_completion, NULL);

void QueryServerd_data_completion(int rc, const char *value, int value_len, const struct Stat *stat, const void *data)
{
    printf("QueryServerd_data_completion data is %s, len is %d!\n", value, value_len);
}

tip:在使用zoo_aget的过程中遇到过一个问题,就是把获取到的value打印出来,发现不只有节点数据,后面还跟了一个奇怪的序号,再次获取序号会+1,但是value_len是对的,就是没有算上后面那个序号的长度。后来我重新编译了程序,就没再出现这个问题,目前还没有找到具体原因,所以建议读取获取到的value时根据value_len来读取,以免数据后面跟了其他数据。

附录

1.Watcher通知的状态类型和事件类型

状态类型(state)

状态码    说明
-112    会话超时(ZOO_EXPIRED_SESSION_STATE)
-113    认证失败(ZOO_AUTH_FAILED_STATE)
1    连接建立中(ZOO_CONNECTING_STATE)
2    连接建立中(ZOO_ASSOCIATING_STATE)
3    连接已建立(ZOO_CONNECTED_STATE)
999    无连接状态
事件类型(type)

事件码    说明
1    创建节点事件(ZOO_CREATED_EVENT)
2    删除节点事件(ZOO_DELETED_EVENT)
3    更改节点事件(ZOO_CHANGED_EVENT)
4    子节点列表变化事件(ZOO_CHILD_EVENT)
-1    会话session事件(ZOO_SESSION_EVENT)
-2    监视被移除事件(ZOO_NOTWATCHING_EVENT)
 

发布了17 篇原创文章 · 获赞 22 · 访问量 3041

猜你喜欢

转载自blog.csdn.net/u013536232/article/details/88992476