VSOA Client
VSOA客户端相关的所有 API 都在文件 vsoa_client.h
和 libvsoa-client.so
中。
Notice
下列函数为线程安全:
- vsoa_client_ping(),
- vsoa_client_subscribe(),
- vsoa_client_unsubscribe(),
- vsoa_client_multi_subscribe(),
- vsoa_client_multi_unsubscribe(),
- vsoa_client_call(),
- vsoa_client_sync_create(),
- vsoa_client_sync_delete(),
- vsoa_client_sync_call(),
- vsoa_client_datagram(),
- vsoa_client_quick_datagram(),
- vsoa_client_is_connect(),
- vsoa_client_send_timeout(),
- vsoa_client_set_custom(),
- vsoa_client_custom(),
- vsoa_client_stream_create(),
- vsoa_client_stream_close().
所有回调函数都在 vsoa_client_input_fds()
中调用,因此需要注意多线程安全。
当连接断开时,只需重新调用 vsoa_client_connect()
即可重新连接。重新连接后,您需要重新订阅以前的主题。
vsoa_client_connect()
重新连接只能在 vsoa_cclient_input_fds()
所在的事件循环中调用,而不是在其他的回调中。
在调用 vsoa_client_close()
之后,客户端对象不可再次被使用,且 vsoa_client_close()
只能在 vsoa_cclient_input_fds()
所在的事件循环中调用,而不是在其他的回调中。
快速通道用于高频数据更新通道,由于数据更新频率高,对通信可靠性的要求并不严格。
Client Create
使用以下 API 来创建一个 VSOA 客户端:
vsoa_client_t *vsoa_client_create(vsoa_client_msg_func_t onmsg, void *arg);
- 函数运行成功返回一个新的客户端对象,失败返回
NULL
; - 参数
onmsg
:订阅回调,在客户端收到订阅消息后调用; - 参数
arg
:回调函数的参数;
onmsg
函数原型:
typedef void (*vsoa_client_msg_func_t)(void *arg, vsoa_client_t *client, vsoa_url_t *url, vsoa_payload_t *payload, bool quick);
- 参数
arg
:回调函数的参数; - 参数
client
:客户端对象; - 参数
url
:URL 信息; - 参数
payload
:payload 数据; - 参数
quick
:是否是一个快速发布的消息;
注意:所有订阅的URL的消息将会进入 onmsg
回调中,所以需要匹配并判断该 URL来完成不同的工作。
建议使用的标准 URL 匹配规则:
订阅路径 | 匹配规则 |
---|---|
"/" | Catch all publish message. |
"/a/b/c" | Only catch "/a/b/c" publish message. |
"/a/b/c/" | Catch "/a/b/c" and "/a/b/c/..." all publish message. |
当客户端不再使用时,使用下列函数将其关闭:
void vsoa_client_close(vsoa_client_t *client);
Client Connect
客户端使用以下 API 来连接到指定的服务器:
bool vsoa_client_connect(vsoa_client_t *client, const struct sockaddr *server, socklen_t namelen, const struct timespec *timeout, const char *passwd, char *info, size_t sz_info);
- 函数成功返回
true
,失败返回false
; - 参数
client
:客户端对象; - 参数
server
:目标服务器; - 参数
namelen
:目标服务器长度; - 参数
timeout
:连接到服务器的超时时间,如果设置为NULL
代表永远等待,直到服务器响应; - 参数
passwd
:服务器的密码,如果服务器没有密码,请设置为NULL
; - 参数
info
:当连接成功时,存储服务器的信息; - 参数
sz_info
:info
缓冲区的大小;
使用示例
char info[256];
socklen_t serv_len;
vsoa_client_t *client;
struct sockaddr_in pos_addr, serv_addr;
struct timespec timeout = { 1, 0 };
/* assume the position server is in local machine */
bzero(&pos_addr, sizeof(struct sockaddr_in));
pos_addr.sin_family = AF_INET;
pos_addr.sin_port = htons(5000);
pos_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
pos_addr.sin_len = sizeof(struct sockaddr_in);
vsoa_position_lookup_server((struct sockaddr *)&pos_addr, sizeof(struct sockaddr_in));
vsoa_position_lookup(AF_INET, "vsoa.myserver.com", (struct sockaddr *)&serv_addr, &serv_len, NULL, &timeout);
/* create client and connect to server */
client = vsoa_client_create(NULL, NULL);
vsoa_client_connect(client, (struct sockaddr *)&serv_addr, sizeof(struct sockaddr_in), &timeout, "123456", info, sizeof(info));
示例使用 position 服务器(参见 VSOA Position Server
章节)来查询服务器的地址: vsoa.myserver.com
,然后连接到此服务器。此函数是一个同步函数。
使用以下 API 来检查 VSOA 客户端是否已连接到 VSOA 服务器。
bool vsoa_client_is_connect(vsoa_client_t *client);
以下 API 可用于拆分 URL,进行订阅信息的分类和处理:
char *vsoa_client_path_token(vsoa_client_t *client, vsoa_url_t *url, size_t *len)
这个函数方法类似于 C 库的 strtok
函数。
使用示例
void onmessage (void *arg, struct vsoa_client *client, vsoa_url_t *url, vsoa_payload_t *payload)
{
int i = 0;
char *seg;
size_t seg_len;
seg = vsoa_client_path_token(client, url, &seg_len);
while (seg) {
i++;
printf("[%d] %.*s\n", i, (int)seg_len, seg);
seg = vsoa_client_path_token(client, NULL, &seg_len);
}
}
以下 API 可以设置客户端发送超时时间:
bool vsoa_client_send_timeout(vsoa_client_t *client, const struct timespec *timeout);
如果 timeout
为 NULL
,则表示使用默认的发送超时时间 (100 ms)。如果连续三次发送超时,则连接将被断开。
Client Event Loop
与 VSOA 服务器一样,当 VSOA 客户端正在运行时,它应该始终使用以下两个 API进行一个循环来监视和处理所有的输入事件。
int vsoa_client_fds(vsoa_client_t *client, fd_set *rfds);
bool vsoa_client_input_fds(vsoa_client_t *client, const fd_set *rfds);
- 函数成功返回
true
,失败返回false
; - 参数
client
:客户端对象; - 参数
rfds
:需要监视的 VSOA 客户端的所有事件;
使用示例
fd_set fds;
vsoa_client_t *client;
int custom_fd, max_fd, cnt;
struct timespec timeout = { 1, 0 };
bool ret;
while (1) {
FD_ZERO(&fds);
max_fd = vsoa_client_fds(client, &fds);
/* wait event */
cnt = pselect(max_fd + 1, &fds, NULL, NULL, &timeout, NULL);
if (cnt > 0) {
/* handle event */
ret = vsoa_client_input_fds(client, &fds);
if (!ret) {
/* disconnect, do auto reconnect in the event loop */
while (1) {
ret = vsoa_client_connect(...);
if (ret) {
break;
}
sleep(1);
}
}
}
/* do other things */
}
在本例中,可以看到当 vsoa_client_input_fds
返回 false
时,表示客户端与服务器断开连接,可以很容易地在事件循环中完成自动重新连接工作。
In this example, you can see that when vsoa_client_input_fds
return false
, it indicates
Client Ping
使用 Ping/Echo 机制来检查连接是否正常,使用以下 API 实现。
bool vsoa_client_ping(vsoa_client_t *client, vsoa_client_res_func_t callback, void *arg, const struct timespec *timeout);
- 函数成功返回 true,失败返回 false;
- 参数
client
:客户端对象; - 参数
callback
:当检查成功或超时时,该回调将被调用; - 参数
arg
:回调函数参数; - 参数
timeout
:ping 到 VSOA 服务器的超时时间,如果设置为NULL
表示永远等待,直到服务器响应;
回调函数原型:
typedef void (*vsoa_client_res_func_t)(void *arg, vsoa_client_t *client, bool success);
- 参数
arg
:函数参数; - 参数
client
:客户端对象; - 参数
success
:若值为true
,则表示连接正常,若为false
,则意味着此 ping 超时,则连接丢失。
Client Subscribe
可以调用 vsoa_client_subscribe
订阅 URL 消息或调用 vsoa_client_unsubscribe
取消订阅 URL 消息。
bool vsoa_client_subscribe(vsoa_client_t *client, const vsoa_url_t *url, vsoa_client_res_func_t callback, void *arg, const struct timespec *timeout);
bool vsoa_client_unsubscribe(vsoa_client_t *client, const vsoa_url_t *url, vsoa_client_res_func_t callback, void *arg, const struct timespec *timeout);
- 函数成功返回
true
,失败返回false
; - 参数
client
:客户端对象; - 参数
url
:URL 信息; - 参数
callback
:这将在服务器成功接受 sub/unsub 请求后调用(这并不意味着消息数据已到达); - 参数
arg
:回调函数参数; - 参数
timeout
:订阅或非订阅的超时时间,如果设置为NULL
,表示永远等待,直到服务器响应;
使用示例
以下代码片段显示了如何订阅指定的 URL 消息。
vsoa_url_t url;
vsoa_client_t *client;
url.url = "/foo";
url.url_len = strlen(url.url);
vsoa_client_subscribe(client, &url, NULL, NULL, NULL);
注意:在重新连接成功后,我们需要再次执行所有的订阅。
Client Multi Subscribe
您可以调用 vsoa_client_multi_subscribe
订阅 URL 消息或调用 vsoa_client_multi_unsubscribe
取消订阅 URL 消息。
bool vsoa_client_multi_subscribe(vsoa_client_t *client, const char *const *urls, int cnt, vsoa_client_res_func_t callback, void *arg, const struct timespec *timeout);
bool vsoa_client_multi_unsubscribe(vsoa_client_t *client, const char *const *urls, int cnt, vsoa_client_res_func_t callback, void *arg, const struct timespec *timeout);
- 函数成功返回
true
,失败返回false
; - 参数
client
:客户端对象; - 参数
urls
:URL 信息数组; - 参数
cnt
:URL 数组计数; - 参数
callback
:这将在服务器成功接受 sub/unsub 请求后调用(这并不意味着消息数据已到达); - 参数
arg
:回调函数参数; - 参数
timeout
:订阅或非订阅的超时时间,如果设置为NULL
,表示永远等待,直到服务器响应;
使用示例
char *topics[] = {
"/a/b/c", "/d/e/f", "/x/y/z"
};
vsoa_client_subscribe(client, topics, sizeof(topics) / sizeof(char *), NULL, NULL, NULL);
Sub-Pub URL匹配规则:
PATH | RPC URL 匹配规则 |
---|---|
"/" | 获取所有发布消息。 |
"/a/b/c" | 仅获取 “/a/b/c” 路径的发布消息。 |
"/a/b/c/" | 获取 “/a/b/c” 和 “/a/b/c/...” 下所有路径的发布消息。 |
注意:URL匹配:URL使用 '/' 作为分隔符,例如:'/a/b/c' 。如果客户端订阅'/a/' ,服务器发布'/a' 、'/a/b' 或'/a/b/c' 消息,则客户端都将收到。 |
Client RPC
使用以下 API 可以执行一次RPC调用,VSOA RPC 方法包括 VSOA_CLIENT_RPC_METHOD_GET
和 VSOA_CLIENT_RPC_METHOD_SET
:
VSOA_CLIENT_RPC_METHOD_GET
this method is to get data from server.VSOA_CLIENT_RPC_METHOD_SET
this method is to set data to server.
bool vsoa_client_call(vsoa_client_t *client, int method, const vsoa_url_t *url, const vsoa_payload_t *payload, vsoa_client_rpc_func_t callback, void *arg, const struct timespec *timeout);
- 函数成功返回
true
,失败返回false
; - 参数
client
:客户端对象; - 参数
method
:RPC 方法; - 参数
url
:URL 信息; - 参数
payload
:RPC payload,此 payload 将被发送到VSOA服务器; - 参数
callback
:RPC 回调函数; - 参数
arg
:回调函数参数; - 参数
timeout
:RPC 的超时时间,如果设置为 NULL,这意味着永远等待,直到服务器响应;
RPC 回调原型:
typedef void (*vsoa_client_rpc_func_t)(void *arg, vsoa_client_t *client, vsoa_header_t *vsoa_hdr, vsoa_payload_t *payload);
当回调函数返回时,vsoa_hdr
和 payload
所指向的内存将失效。
- 参数
client
:客户端对象; - 参数
vsoa_hdr
:数据包头部; - 参数
payload
:payload 数据;
如果需要同步 RPC 调用,则可以使用 RPC 调用同步扩展接口。
使用示例
下面的代码显示了如何对指定的 URL 执行 RPC GET
操作。
extern void get_foo(void *arg, struct vsoa_client *client, vsoa_header_t *vsoa_hdr, vsoa_payload_t *payload);
vsoa_url_t url;
vsoa_client_t *client;
url.url = "/foo";
url.url_len = strlen(url.url);
vsoa_client_call(client, VSOA_CLIENT_RPC_METHOD_GET, &url, NULL, get_foo, NULL, &timeout);
注意:RPC是为异步模式而设计的,因此可以连续并行地执行RPC调用。
Client Datagram
使用以下 API 同服务器接收/发送数据报:
void vsoa_client_on_datagram(vsoa_client_t *client, vsoa_client_dat_func_t callback, void *arg);
bool vsoa_client_datagram(vsoa_client_t *client, const vsoa_url_t *url, const vsoa_payload_t *payload);
这与服务器数据报相同。
数据报回调原型:
typedef void (*vsoa_client_dat_func_t)(void *arg, vsoa_client_t *client, vsoa_url_t *url, vsoa_payload_t *payload, bool quick);
可以使用以下 API 通过快速通道向服务器发送DATAGRAM(数据报):
bool vsoa_client_quick_datagram(vsoa_client_t *client, const vsoa_url_t *url, const vsoa_payload_t *payload);
使用示例
下面的代码显示了如何同指定的 URL 传输数据报。
vsoa_client_t *client;
/* VSOA client set on datagram callback */
vsoa_client_on_datagram(client, on_datagram, NULL);
static void on_datagram (void *arg, vsoa_client_t *client, vsoa_url_t *url, vsoa_payload_t *payload, bool quick)
{
/* send datagram to server (echo) */
vsoa_client_datagram(client, url, payload);
}
Client Stream
客户端使用以下 API 创建一个 VSOA 流,如果成功,此函数将返回新的连接 fd
,否则将返回 -1
。
int vsoa_client_stream_create(vsoa_client_t *client, uint16_t tunid, const struct timespec *timeout, int keepalive);
- 参数
client
:客户端对象; - 参数
tunid
:新的 TCP 通道,客户端使用该通道连接到服务器; - 参数
timeout
:客户端连接到服务器的超时时间,如果设置为NULL
,表示永远等待,直到服务器响应; - 参数
keepalive
:设置新的连接 KEEPALIVE 值;
注意:客户端流必须在服务器流创建后创建,所以我们通常使用 RPC 握手在客户端和服务器之间创建流。
客户端使用以下 API 来关闭一个 VSOA 流。
void vsoa_client_stream_close(int stream);
- 参数
stream
:由vsoa_client_stream_create()
创建的流 fd;
使用示例
下面的代码展示了如何创建一个 VSOA 流:
/* server side code RPC callback */
void command_read (void *arg, vsoa_server_t *server, vsoa_cli_id_t cid, vsoa_header_t *vsoa_hdr, vsoa_url_t *url, vsoa_payload_t *payload)
{
uint32_t seqno = vsoa_parser_get_seqno(vsoa_hdr);
vsoa_server_stream_t *stream;
/* do other things */
vsoa_server_stream_create(server, stream);
/* reply tunid to client */
vsoa_server_cli_reply(server, cid, 0, seqno, stream->tunid, NULL);
}
/* server side accept the connect of client and do transfer in a new thread */
{
vsoa_server_stream_accept(...);
send();
recv();
}
/* client side code RPC callback */
void on_rpc_read (void *arg, struct vsoa_client *client, vsoa_header_t *vsoa_hdr, vsoa_url_t *url, vsoa_payload_t *payload)
{
int stream;
struct timespec timeout = { 1, 0 };
tunid = vsoa_parser_get_tunid(vsoa_hdr);
/* do other things */
/* create a new thread with tunid for stream*/
}
/* client create a new stream and do transfer in a new thread */
{
/* tunid from server stream */
stream = vsoa_client_stream_create(client, tunid, &timeout, 5)
send();
recv();
}
更多详情,请参考 example/c/stream.c
。
Synchronous RPC Calls
VSOA C 客户端提供同步 RPC 调用支持。
vsoa_client_sync_call_t *vsoa_client_sync_create(bool dynamic);
VSOA 客户端创建一个 RPC 调用同步器,同步器只能同时处理一个 RPC 同步调用,并且不允许使用多线程。
如果参数 dynamic
值为 true
,这意味着每次调用收到来自服务器的回复时都动态地应用包内存。当值为 false
时,表示只静态应用一次。
在 MATRIX653 系统中,不允许在 NORMAL
操作模式下创建 RPC 调用同步器,开发人员必须在初始化期间为每个任务创建 RPC 调用同步器。
bool vsoa_client_sync_delete(vsoa_client_sync_call_t *sync);
VSOA 客户端可以销毁一个 RPC 调用同步器,但该同步器只能在使用完成后销毁,不允许在调用期间销毁。
在 MATRIX653 系统中,不允许删除 RPC 调用同步器。
bool vsoa_client_sync_call(vsoa_client_t *client, int method, const vsoa_url_t *url, const vsoa_payload_t *payload,
vsoa_client_sync_call_t *sync, vsoa_header_t **vsoa_hdr_reply, const struct timespec *timeout);
VSOA 客户端执行同步 RPC 调用,此函数将阻塞,直到调用完成、发生错误或超时,因此该函数不能在 VSOA 主事件循环线程中使用。
函数返回 true
表示调用成功,vsoa_hdr_reply
有效则表示服务器已回复(否则表示命令超时),当调用成功时,您可以检查 vsoa_hdr_reply
获得服务器回复,您可以使用 vsoa_parser_get_payload()
获取回复的 payload。
当进行下一次远程调用或删除同步器时,vsoa_hdr_reply
指向的数据包内存将无效。
建议为将使用同步 RPC 调用的每个线程创建一个同步器,然后在相应的线程中按顺序使用它。
使用示例
下面的代码展示了如何使用同步 RPC 调用。
void *sync_call_thread (void *arg)
{
bool ret;
int cnt = 0;
char param[32];
vsoa_url_t url;
vsoa_header_t *vsoa_hdr;
vsoa_payload_t reply, send;
vsoa_client_sync_call_t *sync;
sync = vsoa_client_sync_create(true);
url.url = "/echo";
url.url_len = strlen(url.url);
while (true) {
ret = vsoa_client_sync_call(client, VSOA_CLIENT_RPC_METHOD_GET,
&url, &send, sync, &vsoa_hdr, NULL);
if (ret) {
if (vsoa_hdr) {
vsoa_parser_get_payload(vsoa_hdr, &reply);
printf("Server /echo reply: %.*s\n", (int)reply.param_len, reply.param);
} else {
fprintf(stderr, "Server not reply!\n");
}
} else {
fprintf(stderr, "Synchronous RPC call error!\n");
break;
}
sleep(1);
}
vsoa_client_sync_delete(sync);
return (NULL);
}
Custom Data Bind
void vsoa_client_set_custom(vsoa_client_t *client, void *custom);
void *vsoa_client_custom(vsoa_client_t *client);
您可以使用上面的两个api来绑定和扩展自定义的客户端框架。