Thrift 的Go与C语言实现
Thrift 是Facebook为了解决各系统间大数据量的传输通信以及系统之间语言环境不同而设计的一种传输框架。目前来看常用的主流语言Thrift都已经很好地支持,并且github上已经有很多实现,除了C语言之外。Thrift传输的程序的静态数据,即数据的数据结构必须事前固定。
Thrift原理就不介绍了,理论性东西网上很多,并且都是雷同的。下面通过实例介绍Thrift 接口在Go与C语言下的实现,以及如何在C语言中调用Go所编写的Thrift客户端。
- thrift 文件编写
#example.thrift
namespace go thrift.rpc
struct Response {
1: required string data;
}
service RpcService {
Response Test(1:string input)
}
- Go与C的thrift代码
thrift -r --gen go example.thrift
thrift -r --gen c_glib example.thrift
此时在目录下会出现gen-go与gen-c_gib两个文件夹,里边存放着Thrift自动生成的数据结构体以及函数的声明。
3. Go的server端实现
/* server.go */
package main
import (
"./gen-go/thrift/rpc"
"git.apache.org/thrift.git/lib/go/thrift"
"log"
"os"
)
const (
NetworkAddr = "localhost:9090"
)
type RpcServiceImpl struct {
}
func (this *RpcServiceImpl) Test(input string) (r *rpc.Response, err error) {
//函数具体实现
return
}
func main() {
transportFactory := thrift.NewTFramedTransportFactory(thrift.NewTTransportFactory())
protocolFactory := thrift.NewTBinaryProtocolFactoryDefault()
serverTransport, err := thrift.NewTServerSocket(NetworkAddr)
if err != nil {
log.Println("Error!", err)
os.Exit(1)
}
handler := &RpcServiceImpl{}
processor := rpc.NewRpcServiceProcessor(handler)
log.Println("thrift server in", NetworkAddr)
server.Serve()
}
4. Go的客户端实现
/* client.go */
import (
"./gen-go/thrift/rpc"
"fmt"
"git.apache.org/thrift.git/lib/go/thrift"
"net"
"os"
)
func main(){
ip := "127.0.0.1"
port := "9090"
input :=""
transportFactory := thrift.NewTFramedTransportFactory(thrift.NewTTransportFactory())
protocolFactory := thrift.NewTBinaryProtocolFactoryDefault()
tSocket, err := thrift.NewTSocket(net.JoinHostPort(ip, port))
if err != nil {
fmt.Fprintln(os.Stderr, "Error resolving address, ", err)
os.Exit(1)
}
tTransport, _ := transportFactory.GetTransport(tSocket)
client := rpc.NewRpcServiceClientFactory(tTransport, protocolFactory)
if err := tTransport.Open(); err != nil {
fmt.Fprintln(os.Stderr, (fmt.Errorf("Error opening socket to %s:%s : %v", ip, port, err)))
os.Exit(1)
}
defer tTransport.Close()
resp, _ := client.Test(input)
}
C的客户端实现
C 的客户端实现目前在github一个都没有,Thrift好像也是最近才支持的。thrift是一种面向对象的框架,C语言面向对象的实现必须依赖于gobject库,所以这里边在实现的过程中需要注意一点,对thrift文件中定义的struct,其他可以直接实例化为对象,在C中必须使用g_object_new函数进行初始化,要不然改strcut 将无法实现。在会一直出现无法找到对应结构接收server端传来的参数。
/* client.c */
#include <stdio.h>
#include <glib-object.h>
#include <string.h>
#include <thrift/c_glib/protocol/thrift_binary_protocol.h>
#include <thrift/c_glib/transport/thrift_framed_transport.h>
#include <thrift/c_glib/transport/thrift_socket.h>
#include "gen-c_glib/rpc_service.h"
struct thrift_if{
ThriftSocket *socket;
ThriftTransport *transport;
ThriftProtocol *protocol;
RpcServiceIf *client;
};
void if_open (struct thrift_if* if_instance, gchar *hostname, gint32 port, GError **error){
#if (!GLIB_CHECK_VERSION (2, 36, 0))
g_type_init ();
#endif
if_instance->socket = g_object_new (THRIFT_TYPE_SOCKET,
"hostname", hostname,
"port", port,
NULL);
if_instance->transport = g_object_new (THRIFT_TYPE_FRAMED_TRANSPORT,
"transport", if_instance->socket,
NULL);
if_instance->protocol = g_object_new (THRIFT_TYPE_BINARY_PROTOCOL,
"transport", if_instance->transport,
NULL);
thrift_transport_open (if_instance->transport, error);
if(!error){
return;
}
if_instance->client = g_object_new (TYPE_RPC_SERVICE_CLIENT,
"input_protocol", if_instance->protocol,
"output_protocol", if_instance->protocol,
NULL);
}
void if_close (struct thrift_if *if_instance, GError **error){
g_clear_error (error);
thrift_transport_close (if_instance->transport, NULL);
g_object_unref (if_instance->client);
g_object_unref (if_instance->protocol);
g_object_unref (if_instance->transport);
g_object_unref (if_instance->socket);
}
int main(){
gchar *hostname = "127.0.0.1";
gint32 port = 9090;
gchar *input = ""
struct thrift_if if_instance;
GError *error = NULL;
if_open(&if_instance, hostname, port, &error);
gchar *data;
Response *Res;
Res = g_object_new(TYPE_RESPONSE,NULL);
if (!error && rpc_service_if_test(if_instance.client,&Res,input,&error)){
g_object_get (Res, "data", &data, NULL);
}
if_close(&if_instance, &error);
return 0;
}
编译:
gcc client.c gen-c_glib/rpc_service.c gen-c_glib/sven_types.c -o client -lthrift_c_glib -lgobject-2.0
5. C代码中调用Go的客户端
由于C的客户端编译依赖于thrift_c_glib与 gobject 动态库,并且thrift_c_glib动态库中对linux的一些系统库又进行了引用,所以动态库的依赖关系复杂,不利于在客户端稳定、无依赖的部署。
可以采用使用Go编写客户端,然后编译为.so文件,供C程序调用。因为Go采用静态源码编译方式,可以无依赖的移植到各个服务器中,在过程中需要注意C与Go基本数据结构之间的转化。代码与go client的实现基本相同,只是go 与 C直接不允许 struct的传递,所以只能传递基本数据类型
/*client.go */
package main
import (
"./gen-go/thrift/rpc"
"C"
"fmt"
"git.apache.org/thrift.git/lib/go/thrift"
"net"
"os"
)
/* !!!务必写上"//export Test", 这不是注释 !!!*/
//export Test
func Test (input string, ip string, port string) *C.char {
transportFactory := thrift.NewTFramedTransportFactory(thrift.NewTTransportFactory())
protocolFactory := thrift.NewTBinaryProtocolFactoryDefault()
tSocket, err := thrift.NewTSocket(net.JoinHostPort(ip, port))
if err != nil {
fmt.Fprintln(os.Stderr, "Error resolving address, ", err)
os.Exit(1)
}
tTransport, _ := transportFactory.GetTransport(tSocket)
client := rpc.NewRpcServiceClientFactory(tTransport, protocolFactory)
if err := tTransport.Open(); err != nil {
fmt.Fprintln(os.Stderr, (fmt.Errorf("Error opening socket to %s:%s : %v", ip, port, err)))
os.Exit(1)
}
defer tTransport.Close()
resp, _ := client.Test(input)
return C.CString(resp.Data)
}
编译为动态库,执行下面命令会生成libclient.h 与 libclient.so两个文件。
go build -buildmode=c-shared -o libclient.so client.go
C 语言调用该 Go生成的动态库:
#include <stdio.h>
#include "libclient.h"
int main()
{
GoString input = {(char*)"test", 4};
GoString ip = {(char*)"127.0.0.1", 9};
GoString port = {(char*)"9090", 4};
char *res = NULL;
res = Test(input, ip, port);
if (res != NULL)
{
printf("%s\n", res);
}
return 0;
}