客户端开发
本节内容介绍 VSOA 客户端使用 RPC 的方法。
开发须知
- 客户端使用 RPC 时,需要明确 RPC 的 URL 标识与 RPC 的回调函数。
- 对于使用 C 语言开发的 RPC 区分了异步调用和同步调用两种不同的场景,而 Java 及 JavaScript 开发语言只支持异步 RPC。
参数调用
在客户端进行 RPC 调用时,使用如下不同的参数区分 "GET/SET" 操作。
- 在 C 中,使用
VSOA_CLIENT_RPC_METHOD_GET
和VSOA_CLIENT_RPC_METHOD_SET
进行区分; - 在 Java 中,使用
Request.VSOA_METHOD_GET
和Request.VSOA_METHOD_SET
进行区分; - 在 JavaScript 中,使用
vsoa.method.GET
和vsoa.method.SET
进行区分。 - 在 Golang 中,使用
protocol.RpcMethodGet
和protocol.RpcMethodSet
进行区分。
常用接口
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);
vsoa_client_sync_call_t *vsoa_client_sync_create(bool dynamic);
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);
bool vsoa_client_sync_delete(vsoa_client_sync_call_t *sync);
client.call(url[, opt[, payload]][, callback[, timeout]]);
boolean call(String url, int method, Payload payload, CBCall cbCall, int timeout);
func (client *Client) Go(URL string, mt protocol.MessageType, flags any, req *protocol.Message, reply *protocol.Message, done chan *Call) *Call
func (client *Client) Call(URL string, mt protocol.MessageType, flags any, req *protocol.Message) (*protocol.Message, error)
client.call(url: str, method: str | int = 0, payload: vsoa.Payload | dict = None, callback: callable = None, timeout: float = 60.0) -> bool
说明:
RPC 客户端的详细接口说明可参考以下手册:
- C 版本可参考 VSOA C 语言编程手册。
- JavaScript 版本可参考 VSOA JavaScript 编程手册,也可参考 VSOA Single Remote Call 使用 fetch 的方式进行 RPC 调用。
- Java 版本可参考 VSOA Java 编程手册。
- Golang 版本可参考 VSOA Golang 编程手册。
- Python 版本可参考 VSOA Pyhon 编程手册。
异步 RPC
客户端调用后无需等待执行结果返回,但依然可以通过回调通知等方式获取返回结果。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#ifdef SYLIXOS
#include <sys/vproc.h>
#endif
#include "vsoa_cliauto.h"
/* My server password */
#define MY_SERVER_PASSWD "123456"
/* My client */
static vsoa_client_t *client;
/* My client auto */
static vsoa_client_auto_t *cliauto;
/* If client is connected */
static bool connected = false;
static void onconnect (void *arg, vsoa_client_auto_t *cliauto, bool connect, const char *info)
{
printf("On connect, connect: %s, info: %s\n",
(connect == true) ? "connected" : "disconnected", info);
connected = connect;
}
static void on_command_light (void *arg, struct vsoa_client *client, vsoa_header_t *vsoa_hdr, vsoa_payload_t *payload)
{
if (vsoa_hdr) {
printf("On asynchronous RPC reply, payload: %.*s\n", (int)payload->param_len, payload->param);
} else {
fprintf(stderr, "VSOA server /light reply timeout!\n");
}
}
/*
* main function
*/
int main (int argc, char **argv)
{
bool ret;
vsoa_url_t url;
#ifdef SYLIXOS
vprocExitModeSet(getpid(), LW_VPROC_EXIT_FORCE);
#endif
/*
* Create client auto robot
*/
cliauto = vsoa_client_auto_create(NULL, NULL);
client = vsoa_client_auto_handle(cliauto);
if (!vsoa_client_auto_setup(cliauto, onconnect, NULL)) {
vsoa_client_auto_delete(cliauto);
fprintf(stderr, "Cannot register connect callback: %s (%d)\n", strerror(errno), errno);
return -1;
}
/*
* Client auto robot start
* The robot will automatically connect to the specified server and maintain the connection.
* At this time, the developer only needs to focus on the business.
*/
vsoa_client_auto_start(cliauto, "vsoa://light_server", MY_SERVER_PASSWD,
NULL, 0, 1000, 1000, 1000);
while (true) {
/*
* Wait for connection established
*/
if (!connected) {
fprintf(stderr, "Cannot connected to server now!\n");
usleep(1000);
continue;
}
/*
* Developers can still use asynchronous RPC calls,
* and the server response callback function will be called in the client auto robot event loop thread context.
*/
url.url = "/light";
url.url_len = strlen(url.url);
ret = vsoa_client_call(client, VSOA_CLIENT_RPC_METHOD_GET, &url, NULL, on_command_light, NULL, NULL);
if (!ret) {
fprintf(stderr, "Asynchronous RPC call error (not connected to server)!\n");
}
sleep(1);
}
}
/* Server name to connect */
var vsoa = require('vsoa');
/* Server name to connect */
const SERVER_NAME = 'light_server';
/* Client Option */
var option = { passwd: '123456' };
/* Client */
var client = new vsoa.Client(option);
/*
* Listen connect event
*/
client.on('connect', function(info) {
console.info('Connected with server:', JSON.stringify(info));
client.call('/light', function (error, payload) {
if (error) {
console.error('Command /light error:', error, 'status:', error.status)
} else {
console.info('Command /light reply:', JSON.stringify(payload))
client.close()
}
}, 2000)
});
client.connect(`vsoa://${SERVER_NAME}`, error => {
if (error) {
console.warn('Connect error:', error.message);
}
});
/*
* Event loop
*/
require('iosched').forever();
import java.net.InetSocketAddress;
import com.acoinfo.vsoa.CBCall;
import com.acoinfo.vsoa.Client;
import com.acoinfo.vsoa.ClientOption;
import com.acoinfo.vsoa.Constant;
import com.acoinfo.vsoa.Error;
import com.acoinfo.vsoa.Payload;
import com.acoinfo.vsoa.Position;
import com.acoinfo.vsoa.Request;
import com.acoinfo.vsoa.VsoaSocketAddress;
public class client_test {
private static String SERVER_NAME = "light_server";
private static String PASSWORD = "123456";
public static Client client;
public static void main(String[] args) {
/*
* Initialize client
*/
client = new Client(new ClientOption(PASSWORD, 6000, 4000, 3, false)) {
@Override
public void onError(Error error) {
System.out.println("Client error:" + error.message);
}
@Override
public void onConnected(String info) {
System.out.println("Connected with server:" + info);
}
};
if (!client.connect("vsoa://" + SERVER_NAME, null, Constant.VSOA_DEF_CONN_TIMEOUT)) {
System.out.println("Connected with server failed");
return;
}
/*
* call /light
*/
client.call("/light", Request.VSOA_METHOD_GET, null, new CBCall() {
@Override
public void callback(Error error, Payload payload, int tunid) {
if (error != null) {
System.out.println("Command /light error:" + error.message);
} else {
System.out.println("Command /light reply:" + payload.param);
}
}
}, 2000);
while(true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
package main
import (
"encoding/json"
"errors"
"fmt"
"gitee.com/sylixos/go-vsoa/client"
"gitee.com/sylixos/go-vsoa/protocol"
)
type RpcLightParam struct {
LightStatus int `json:"light"`
}
var lightstatus = 0
func VsoaRpcCall() {
/*
* Initialize client
*/
clientOption := client.Option{
Password: "123456",
}
c := client.NewClient(clientOption)
_, err := c.Connect("vsoa", "localhost:3001")
if err != nil {
fmt.Println(err)
return
}
defer c.Close()
req := protocol.NewMessage()
/*
* call /light
*/
reply, err := c.Call("/light", protocol.TypeRPC, protocol.RpcMethodGet, req)
if err != nil {
if err == errors.New(protocol.StatusText(protocol.StatusInvalidUrl)) {
fmt.Println("Invalid URL")
} else {
fmt.Println("Command /light error:", err)
}
return
} else {
DstParam := new(RpcLightParam)
json.Unmarshal(reply.Param, DstParam)
lightstatus = DstParam.LightStatus
fmt.Println("Command /light reply:", DstParam.LightStatus)
}
}
func main() {
VsoaRpcCall()
}
from vsoa.client import Client
import vsoa, sys
# Server name to connect
SERVER_NAME = 'light_server'
# Password
PASSWD = '123456'
# Create client
client = Client()
# Listen connect event
def onreply(client: Client, header: vsoa.Header, payload: vsoa.Payload):
if header.status != vsoa.parser.VSOA_STATUS_SUCCESS:
print('Command /light error: {}'.format(header.status))
else:
print('Command /light reply: {}'.format(str(payload.param)))
def onconnect(client: Client, conn: bool, info: str | dict | list):
print('Connected with server: {}'.format(str(info)))
client.onconnect = onconnect
if err := client.connect('vsoa://{}'.format(SERVER_NAME), PASSWD):
print('Connect error: {}'.format(err))
sys.exit(-1)
timer = vsoa.Timer()
def timer_callback():
client.call(url='/light', callback=onreply, timeout=2000)
timer.start(1, timer_callback, 1)
# Event loop
client.run()
说明:
- 在 Node.js 环境中,不需要事件循环。
- 在该范例中,因为使用了独立的位置服务或 ECSM 集成的位置服务,所以客户端可以通过 URL 定位到指定的服务,不需要通过 IP 和端口信息访问服务。
同步 RPC
同步 RPC 即客户端等待调用执行完成并通过 vsoa_parser_get_payload
接口获取到执行结果。
同步 RPC 前首先需要使用 vsoa_client_sync_create
创建 RPC 同步器,其次使用 vsoa_client_sync_call
进行同步的 RPC 调用,调用结束后,再用 vsoa_client_sync_delete
删除 RPC 同步器。
说明:
- 一个 RPC 同步器在同一时刻只能执行一个 RPC 同步调用。
- 多线程同时使用同一个 RPC 同步器是不允许的。
- 建议为每一个线程创建一个同步器,线程内调用的所有同步命令都可以使用本线程的同步器。
以下为 C 语言的同步 RPC 范例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#ifdef SYLIXOS
#include <sys/vproc.h>
#endif
#include "vsoa_cliauto.h"
/* My server password */
#define MY_SERVER_PASSWD "123456"
/* My client */
static vsoa_client_t *client;
/* My client auto */
static vsoa_client_auto_t *cliauto;
/* If client is connected */
static bool connected = false;
static void onconnect (void *arg, vsoa_client_auto_t *cliauto, bool connect, const char *info)
{
printf("On connect, connect: %s, info: %s\n",
(connect == true) ? "connected" : "disconnected", info);
connected = connect;
}
/*
* main function
*/
int main (int argc, char **argv)
{
bool ret;
vsoa_url_t url;
vsoa_header_t *vsoa_hdr;
vsoa_payload_t reply;
/*
* If you want to use synchronous RPC calls,
* you need to create a synchronizer for the current thread.
*/
vsoa_client_sync_call_t *sync = vsoa_client_sync_create(true);
#ifdef SYLIXOS
vprocExitModeSet(getpid(), LW_VPROC_EXIT_FORCE);
#endif
/*
* Create client auto robot
*/
cliauto = vsoa_client_auto_create(NULL, NULL);
client = vsoa_client_auto_handle(cliauto);
if (!vsoa_client_auto_setup(cliauto, onconnect, NULL)) {
vsoa_client_auto_delete(cliauto);
fprintf(stderr, "Cannot register connect callback: %s (%d)\n", strerror(errno), errno);
return -1;
}
/*
* Client auto robot start
* The robot will automatically connect to the specified server and maintain the connection.
* At this time, the developer only needs to focus on the business.
*/
vsoa_client_auto_start(cliauto, "vsoa://light_server", MY_SERVER_PASSWD,
NULL, 0, 1000, 1000, 1000);
while (true) {
/*
* Wait for connection established!
*/
if (!connected) {
fprintf(stderr, "Cannot connected to server now!\n");
usleep(1000);
continue;
}
/*
* Execute RPC synchronous calls.
* no need to consider the connection status,
* the connection with the server and the event loop are maintained by client auto robot.
*/
url.url = "/light";
url.url_len = strlen(url.url);
ret = vsoa_client_sync_call(client, VSOA_CLIENT_RPC_METHOD_GET, &url, NULL, sync, &vsoa_hdr, NULL);
if (ret) {
if (vsoa_hdr) {
vsoa_parser_get_payload(vsoa_hdr, &reply);
printf("Server /light reply: %.*s\n", (int)reply.param_len, reply.param);
} else {
fprintf(stderr, "Server not reply!\n");
}
} else {
fprintf(stderr, "Synchronous RPC call error (not connected to server)!\n");
}
sleep(1);
}
}
说明:
在该范例中,因为使用了独立的位置服务或 ECSM 集成的位置服务,所以客户端可以通过 URL 定位到指定的服务,不需要通过 IP 和端口信息访问服务。
注意事项
C/C++ 客户端编译时需链接如下表所示的 VSOA 动态库,在 RealEvo-IDE 中配置时请参考 C/C++ 环境验证,Linux 下开发请参考 搭建 Linux 运行环境 提供的 C 语言范例进行配置。
库名称 | 功能 |
---|---|
libvsoa-json.so | 提供 JSON 功能 |
libvsoa-client.so | 提供客户端功能 |
libvsoa-parser.so | 提供参数解析功能 |
libvsoa-position.so | 位置服务功能 |