這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
- Thrift 的Go與C語言實現
- thrift 檔案編寫
- Go與C的thrift代碼
- Go的server端實現
- Go的用戶端實現
- C的用戶端實現
- C代碼中調用Go的用戶端
Thrift 的Go與C語言實現
Thrift 是Facebook為瞭解決各系統間大資料量的傳輸通訊以及系統之間語言環境不同而設計的一種傳輸架構。目前來看常用的主流語言Thrift都已經很好地支援,並且github上已經有很多實現,除了C語言之外。Thrift傳輸的程式的待用資料,即資料的資料結構必須事前固定。Thrift原理就不介紹了,理論性東西網上很多,並且都是雷同的。下面通過執行個體介紹Thrift 介面在Go與C語言下的實現,以及如何在C語言中調用Go所編寫的Thrift用戶端。
1. thrift 檔案編寫
#example.thrift namespace go thrift.rpc struct Response { 1: required string data; }service RpcService { Response Test(1:string input)}
2. Go與C的thrift代碼
thrift -r --gen go example.thriftthrift -r --gen c_glib example.thrift
此時在目錄下會出現gen-go與gen-c_gib兩個檔案夾,裡邊存放著Thrift自動產生的資料結構體以及函數的聲明。
3. Go的server端實現
/* server.go */package mainimport ( "./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的傳遞,所以只能傳遞基礎資料型別 (Elementary Data Type)
/*client.go */ package mainimport ( "./gen-go/thrift/rpc" "C" "fmt" "git.apache.org/thrift.git/lib/go/thrift" "net" "os")/* !!!務必寫上"//export Test", 這不是注釋 !!!*///export Testfunc 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;}