客户端开发

更新时间:
2024-11-26
下载文档

客户端开发

本节内容介绍 VSOA 客户端使用 RPC 的方法。

开发须知

  • 客户端使用 RPC 时,需要明确 RPC 的 URL 标识与 RPC 的回调函数。
  • 对于使用 C 语言开发的 RPC 区分了异步调用和同步调用两种不同的场景,而 Java 及 JavaScript 开发语言只支持异步 RPC。

参数调用

在客户端进行 RPC 调用时,使用如下不同的参数区分 "GET/SET" 操作。

  • 在 C 中,使用 VSOA_CLIENT_RPC_METHOD_GETVSOA_CLIENT_RPC_METHOD_SET 进行区分;
  • 在 Java 中,使用 Request.VSOA_METHOD_GETRequest.VSOA_METHOD_SET 进行区分;
  • 在 JavaScript 中,使用 vsoa.method.GETvsoa.method.SET 进行区分。
  • 在 Golang 中,使用 protocol.RpcMethodGetprotocol.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 客户端的详细接口说明可参考以下手册:

异步 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位置服务功能
文档内容是否对您有所帮助?
有帮助
没帮助