客户端框架
本节介绍 VSOA 客户端框架的开发流、步骤和注意事项。
开发流程
创建客户端时,不同的开发语言在流程上略有区别,但总体都是按照如下步骤进行:
开发步骤
步骤 1:创建客户端
在使用 Java 、 JavaScript 和 Golang 的开发语言方式中,需要在创建客户端时指定其通信密码,而使用 C 的开发语言方式中是在连接服务端时指定通信密码。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef SYLIXOS
#include <sys/vproc.h>
#endif
#include <netinet/in.h>
#include <arpa/inet.h>
#include "vsoa_client.h"
static vsoa_client_t *client;
int main (int argc, char **argv)
{
#ifdef SYLIXOS
vprocExitModeSet(getpid(), LW_VPROC_EXIT_FORCE);
#endif
/*
* Initilize client
*/
client = vsoa_client_create(NULL, NULL);
if (!client) {
fprintf(stderr, "Can not create VSOA client!\n");
return (-1);
}
return (0);
}
var vsoa = require('vsoa');
/* Client Option */
var option = { passwd: '123456' };
/* Client */
var client = new vsoa.Client(option);
import java.net.InetSocketAddress;
import com.acoinfo.vsoa.Client;
import com.acoinfo.vsoa.ClientOption;
import com.acoinfo.vsoa.Position;
import com.acoinfo.vsoa.Constant;
import com.acoinfo.vsoa.Error;
import com.acoinfo.vsoa.VsoaSocketAddress;
public class client_test {
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] Client error:" + error.message);
}
};
}
}
package main
import (
"gitee.com/sylixos/go-vsoa/client"
)
func ClientTest() {
/*
* Set client options' Password
*/
clientOption := client.Option{
Password: "123456",
}
/*
* Create a new client instance
*/
c := client.NewClient(clientOption)
}
func main() {
ClientTest()
}
from vsoa.client import Client
# Create a client
client = Client()
步骤 2:连接微服务
因为 VSOA 提供了 Position 位置服务功能,所以客户端可以使用微服务名称查询微服务的地址。
需要注意的是,需要指定 Position 位置服务的地址和端口,详情见 了解 VSOA 中的 "VSOA 位置服务配置方法",也可在代码中手动指定 Position 服务的位置。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef SYLIXOS
#include <sys/vproc.h>
#endif
#include <netinet/in.h>
#include <arpa/inet.h>
#include "vsoa_client.h"
#include "vsoa_position.h"
#define MY_SERVER_PASSWD "123456"
static vsoa_client_t *client;
int main (int argc, char **argv)
{
char info[256];
struct sockaddr_in addr;
struct timespec timeout = { 1, 0 };
socklen_t serv_len = sizeof(struct sockaddr_in);
#ifdef SYLIXOS
vprocExitModeSet(getpid(), LW_VPROC_EXIT_FORCE);
#endif
/*
* Query VSOA server
*/
if (!vsoa_position_lookup(AF_INET, "light_server",
(struct sockaddr *)&addr, &serv_len, NULL, &timeout)) {
fprintf(stderr, "Can not found VSOA server!\n");
return (-1);
}
client = vsoa_client_create(NULL, NULL);
if (!client) {
fprintf(stderr, "Can not create VSOA client!\n");
return (-1);
}
/*
* Connect to server with password
*/
if (!vsoa_client_connect(client, (struct sockaddr *)&addr, sizeof(struct sockaddr_in),
&timeout, MY_SERVER_PASSWD, info, sizeof(info))) {
vsoa_client_close(client);
fprintf(stderr, "Can not connect to VSOA server!\n");
return (-1);
}
return (0);
}
/* Server name to connect */
var vsoa = require('vsoa');
var socket = require('socket');
/* Whether to manually query the Position server */
const POS_MANUALLY = false;
/* Server name to connect */
const SERVER_NAME = 'light_server';
/* Client Option */
var option = { passwd: '123456' };
/* Client */
var client = new vsoa.Client(option);
if (POS_MANUALLY) {
/*
* Set position server address
*/
vsoa.Position.server({
domain: socket.AF_INET, addr: '127.0.0.1', port: 3000
});
/*
* Query VSOA server
*/
vsoa.lookup(SERVER_NAME, function(error, saddr) {
if (error) {
console.error(`Could not find "${SERVER_NAME}" address!`);
} else {
console.log('Server address:', JSON.stringify(saddr));
/*
* Connect to server
*/
client.connect(saddr);
}
});
} else {
client.connect(`vsoa://${SERVER_NAME}`, error => {
if (error) {
console.warn('Connect error:', error.message);
}
});
}
import java.net.InetSocketAddress;
import com.acoinfo.vsoa.Client;
import com.acoinfo.vsoa.ClientOption;
import com.acoinfo.vsoa.Position;
import com.acoinfo.vsoa.Constant;
import com.acoinfo.vsoa.Error;
import com.acoinfo.vsoa.VsoaSocketAddress;
public class client_test {
private static boolean POS_MANUALLY = false;
private static String SERVER_NAME = "light_server";
private static String PASSWORD = "123456";
private static String POS_ADDRESS = "127.0.0.1";
private static int POS_PORT = 3000;
public static Client client;
public static void main(String[] args) {
/*
* Initialize client with password
*/
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 (POS_MANUALLY) {
VsoaSocketAddress address;
try {
/*
* Query VSOA server
*/
address = Position.lookup(new InetSocketAddress(POS_ADDRESS, POS_PORT), SERVER_NAME, 0);
if (address == null) {
System.out.println("Could not find " + SERVER_NAME + " address!");
return;
}
} catch (Exception e) {
System.out.println("Could not find " + SERVER_NAME + " address!");
return;
}
System.out.println("Server address:" + address.toString());
/*
* Connect to server by address
*/
if (!client.connect(address, null, Constant.VSOA_DEF_CONN_TIMEOUT)) {
System.out.println("Connected with server failed" + address.toString());
return;
}
} else {
/*
* Connect to server by URL
*/
if (!client.connect("vsoa://" + SERVER_NAME, null, Constant.VSOA_DEF_CONN_TIMEOUT)) {
System.out.println("Connected with server failed");
return;
}
}
}
}
package main
import (
"fmt"
"gitee.com/sylixos/go-vsoa/client"
)
var (
positionAddr = "localhost:3000"
vsoaServerName = "vsoa://light_server"
)
func ClientTest() {
/*
* Set client options' Password
*/
clientOption := client.Option{
Password: "123456",
}
/*
* Create a new client instance
*/
c := client.NewClient(clientOption)
/*
* Set position server address
*/
err := c.SetPosition(positionAddr)
if err != nil {
fmt.Println(err)
return
}
/*
* Query VSOA server & connect to server
*/
_, err = c.Connect(client.Type_URL, vsoaServerName)
if err != nil {
fmt.Println(err)
return
}
defer c.Close()
}
func main() {
ClientTest()
}
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 onconnect(client: Client, conn: bool, info: str | dict | list):
print('Connected with server: {}'.format(str(info)))
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)))
client.onconnect = onconnect
if err := client.connect('vsoa://{}'.format(SERVER_NAME), PASSWD):
print('Connect error: {}'.format(err))
sys.exit(-1)
说明:
在 Node.js 的环境中,socket.AF_INET 应为 vsoa.AF_INET,详情可见 https://www.npmjs.com/package/vsoa 。
步骤 3:监听事件或循环
与 VSOA 微服务建立连接成功之后,它应当始终监听所有的 VSOA 输入事件。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef SYLIXOS
#include <sys/vproc.h>
#endif
#include <sys/select.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "vsoa_client.h"
#include "vsoa_position.h"
#define MY_SERVER_PASSWD "123456"
static vsoa_client_t *client;
int main (int argc, char **argv)
{
int max_fd, cnt;
fd_set fds;
char info[256];
socklen_t serv_len = sizeof(struct sockaddr_in);
struct sockaddr_in addr;
struct timespec timeout = { 1, 0 };
#ifdef SYLIXOS
vprocExitModeSet(getpid(), LW_VPROC_EXIT_FORCE);
#endif
if (!vsoa_position_lookup(AF_INET, "light_server",
(struct sockaddr *)&addr, &serv_len, NULL, &timeout)) {
fprintf(stderr, "Can not found VSOA server!\n");
return (-1);
}
client = vsoa_client_create(NULL, NULL);
if (!client) {
fprintf(stderr, "Can not create VSOA client!\n");
return (-1);
}
if (!vsoa_client_connect(client, (struct sockaddr *)&addr, sizeof(struct sockaddr_in),
&timeout, MY_SERVER_PASSWD, info, sizeof(info))) {
vsoa_client_close(client);
fprintf(stderr, "Can not connect to VSOA server!\n");
return (-1);
}
while (1) {
FD_ZERO(&fds);
max_fd = vsoa_client_fds(client, &fds);
cnt = pselect(max_fd + 1, &fds, NULL, NULL, &timeout, NULL);
if (cnt > 0) {
if (!vsoa_client_input_fds(client, &fds)) {
vsoa_client_close(client);
fprintf(stderr, "Connection lost!\n");
return (-1);
}
}
}
return (0);
}
/* Server name to connect */
var vsoa = require('vsoa');
var socket = require('socket');
/* Whether to manually query the Position server */
const POS_MANUALLY = false;
/* Server name to connect */
const SERVER_NAME = 'light_server';
/* Client Option */
var option = { passwd: '123456' };
/* Client */
var client = new vsoa.Client(option);
if (POS_MANUALLY) {
/*
* Set position server address
*/
vsoa.Position.server({
domain: socket.AF_INET, addr: '127.0.0.1', port: 3000
});
/*
* Query VSOA server
*/
vsoa.lookup(SERVER_NAME, function(error, saddr) {
if (error) {
console.error(`Could not find "${SERVER_NAME}" address!`);
} else {
console.log('Server address:', JSON.stringify(saddr));
/*
* Connect to server
*/
client.connect(saddr);
}
});
} else {
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.Client;
import com.acoinfo.vsoa.ClientOption;
import com.acoinfo.vsoa.Position;
import com.acoinfo.vsoa.Constant;
import com.acoinfo.vsoa.Error;
import com.acoinfo.vsoa.VsoaSocketAddress;
public class client_test {
private static boolean POS_MANUALLY = true;
private static String SERVER_NAME = "light_server";
private static String PASSWORD = "123456";
private static String POS_ADDRESS = "127.0.0.1";
private static int POS_PORT = 3000;
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 (POS_MANUALLY) {
VsoaSocketAddress address;
try {
address = Position.lookup(new InetSocketAddress(POS_ADDRESS, POS_PORT), SERVER_NAME, 0);
if (address == null) {
System.out.println("Could not find " + SERVER_NAME + " address!");
return;
}
} catch (Exception e) {
System.out.println("Could not find " + SERVER_NAME + " address!");
return;
}
System.out.println("Server address:" + address.toString());
if (!client.connect(address, null, Constant.VSOA_DEF_CONN_TIMEOUT)) {
System.out.println("Connected with server failed" + address.toString());
return;
}
} else {
if (!client.connect("vsoa://" + SERVER_NAME, null, Constant.VSOA_DEF_CONN_TIMEOUT)) {
System.out.println("Connected with server failed");
return;
}
}
while(true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
package main
import (
"fmt"
"time"
"gitee.com/sylixos/go-vsoa/client"
)
var (
positionAddr = "localhost:3000"
vsoaServerName = "vsoa://light_server"
)
func ClientTest() {
/*
* Set client options' Password
*/
clientOption := client.Option{
Password: "123456",
}
/*
* Create a new client instance
*/
c := client.NewClient(clientOption)
/*
* Set position server address
*/
err := c.SetPosition(positionAddr)
if err != nil {
fmt.Println(err)
return
}
/*
* Query VSOA server & connect to server
*/
_, err = c.Connect(client.Type_URL, vsoaServerName)
if err != nil {
fmt.Println(err)
return
}
defer c.Close()
}
func main() {
ClientTest()
for {
time.Sleep(1 * time.Second)
}
}
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 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)
# Event loop
client.run()
说明:
在 Node.js 的环境中,不需要事件循环。
客户端机器人
由以上范例可见,使用 C 语言搭建 VSOA 客户端时步骤繁琐,且若服务端未启动时,客户端也将因连接服务端失败而不能正常运行。VSOA 提供了客户端机器人功能,客户端机器人可以自动连接指定的服务端并保持连接,使得开发者可以更加关注业务逻辑实现。
同样,在该范例中,因为独立的位置服务或 ECSM 的存在,不需要指定任何 IP 和端口信息。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.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;
/*
* main function
*/
int main (int argc, char **argv)
{
#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);
/*
* 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) {
sleep(1);
}
}
说明:
VSOA 客户端机器人接口说明可参考 C 扩展编程手册。
注意事项
C/C++ 客户端编译时需链接如下表所示的 VSOA 动态库,在 RealEvo-IDE 中配置时请参考 C/C++ 环境验证,Linux 下开发请参考 搭建 Linux 运行环境 提供的 C 语言范例进行配置。
库名称 | 功能 |
---|---|
libvsoa-json.so | 提供 JSON 功能 |
libvsoa-client.so | 提供客户端功能 |
libvsoa-parser.so | 提供参数解析功能 |