ecFlow学习笔记:使用SWIG创建golang客户端
警告:依照本文方法实现的ecflow客户端有严重的内存泄漏问题,请不要在长时间运行的程序中使用。
之前一篇博文中介绍如何使用ecFlow的C++ API创建客户端(《ecFlow学习笔记04:使用C++ API》)。 本文介绍如何基于C++的客户端构建golang客户端。
golang调用c代码
golang提供CGO工具调用C代码,相关信息请参考官方文档《cgo》。
虽然cgo可以使用C++编译器编译C++代码,但必须使用C语言对C++代码进行包装才能在go代码中调用。所以对于使用c++接口的ecflow客户端来说,直接使用cgo非常繁琐。
好在,有其他工具可以帮助我们在golang中调用c++代码。
SWIG就属于这样的工具。SWIG是一款实现跨语言调用C/C++代码的工具。
下面介绍如何使用SWIG创建ecFlow的golang客户端。
简化的ecFlow c++ 客户端
ecflow-cpp-client实现了获取节点状态组成状态树的功能,在golang客户端中,只保留获取节点状态的部分,大幅减少需要的c++代码。
简化后的c++代码头文件如下所示:
#pragma once
#include <string>
#include <vector>
namespace EcflowUtil {
// 状态条目,包括节点路径和节点状态
struct NodeStatusRecord {
std::string path_;
std::string status_;
};
class EcflowClientWrapperPrivate;
class EcflowClientWrapper {
public:
EcflowClientWrapper() = delete;
// 使用ip地址和端口号创建client
EcflowClientWrapper(const std::string &host, const std::string &port);
~EcflowClientWrapper();
// 设置连接ecFlow服务器的超时时间,以秒为单位
void setConnectTimeout(int time_out);
// 从ecFlow服务器同步系统状态
int sync();
// 返回状态条目列表
std::vector<NodeStatusRecord> statusRecords() {
return status_records_;
}
std::string errorMessage();
private:
std::string host_;
std::string port_;
EcflowClientWrapperPrivate* p_;
std::vector<NodeStatusRecord> status_records_;
};
} // namespace EcflowUtil
使用ip地址和端口号创建EcflowClientWrapper
,调用sync
方法获取系统状态,最后调用statusRecords
方法获取状态记录列表。
SWIG描述文件
SWIG使用特定的语法创建描述文件,用于描述C/C++的接口。
下面代码为上面的头文件创建描述文件。
%module ecflow_client
%{
#include "ecflow_util.h"
%}
%include "std_vector.i"
%include "std_string.i"
%include "ecflow_util.h"
namespace std {
%template(NodeStatusRecordVector) vector<EcflowUtil::NodeStatusRecord>;
};
因为头文件中使用了std::vector
和std::string
,所以需要引入SWIG为标准库vector和string开发的描述文件。
同时需要显示指定使用vector的模板类vector<EcflowUtil::NodeStatusRecord>
。
生成接口文件
使用swig命令生成接口文件。
swig -go -cgo -c++ -intgosize 64 ecflow_util.i
swig会生成用于封装c++接口的ecflow_util_wrap.cxx
文件和go接口文件ecflow_client.go
。
封装c++接口
创建状态条目类型,与EcflowUtil::NodeStatusRecord
对应。
type StatusRecord struct {
Path string `json:"path"`
Status string `json:"status"`
}
创建EcflowClient封装EcflowClientWrapper
接口。
type EcflowClient struct {
ServerHost string
ServerPort string
CollectedTime time.Time
wrapper EcflowClientWrapper
}
go语言没有构造函数,创建一个函数用于构造EcflowClient
对象。
func CreateEcflowClient(host string, port string) *EcflowClient {
client := &EcflowClient{
ServerHost: host,
ServerPort: port,
}
client.wrapper = NewEcflowClientWrapper(client.ServerHost, client.ServerPort)
return client
}
封装EcflowClientWrapper
的成员函数。
func (c *EcflowClient) SetConnectTimeout(timeout int) {
c.wrapper.SetConnectTimeout(timeout)
}
func (c *EcflowClient) Sync() int {
if c.wrapper == nil {
c.wrapper = NewEcflowClientWrapper(c.ServerHost, c.ServerPort)
}
errorCode := c.wrapper.Sync()
c.CollectedTime = time.Now()
return errorCode
}
func (c *EcflowClient) StatusRecords() []StatusRecord {
var returnRecords []StatusRecord
records := c.wrapper.StatusRecords()
count := int(records.Size())
for i := 0; i < count; i++ {
record := records.Get(i)
returnRecords = append(returnRecords, StatusRecord{
Path: record.GetPath_(),
Status: record.GetStatus_(),
})
}
return returnRecords
}
func (c *EcflowClient) Close() {
DeleteEcflowClientWrapper(c.wrapper)
c.wrapper = nil
}
示例
下面的代码展示如何使用EcflowClient
获取并打印ecflow服务所有节点的路径和状态。
package main
import (
"encoding/json"
"flag"
"fmt"
"github.com/perillaroc/ecflow-client-go"
"log"
"os"
)
func main() {
// 创建命令行参数:ip地址,端口号,输出方式
ecflowHost := flag.String("host", "localhost", "ecflow server host")
ecflowPort := flag.String("port", "3141", "ecflow server port")
outputFile := flag.String("output", "", "output file, default is os.Stdout")
flag.Parse()
// 创建EcflowClient
client := ecflow_client.CreateEcflowClient(*ecflowHost, *ecflowPort)
defer client.Close()
// 如果设定outputFile,则输出到文件,否则输出到标准输出
var err error
target := os.Stdout
if *outputFile != "" {
target, err = os.Create(*outputFile)
if err != nil {
panic(err)
}
}
// 同步ecflow状态
ret := client.Sync()
if ret != 0 {
log.Fatal("sync has error")
}
// 获取状态列表并打印
records := client.StatusRecords()
for _, record := range records {
b, err := json.Marshal(record)
if err != nil {
continue
}
fmt.Fprintf(target, "%s\n", b)
}
fmt.Fprintf(target, "%d nodes\n", len(records))
}
参考
完整代码请参考: