目录[-]
每种语言都有自己最擅长的领域,Golang 最适合的领域就是服务器端程序。
做为服务器端程序,需要考虑性能同时也要考虑与各种语言之间方便的通讯。采用http协议简单,但性能不高。采用TCP通讯,则需要考虑封包、解包、粘包等等很多因素,而且想写个高效的TCP服务,也很难。
其实,对于此类需求,采用RPC(Remote Procedure Call Protocol)编程最靠谱。使用 RPC 编程被认为是在分布式环境中运行的客户机和服务器应用程序之间进行可靠通信的最强大、最高效的方法之一。
Golang内置了对RPC支持,但只能适用于go语言程序之间调用,且貌似序列化、反序列化性能不高。如果go语言能使用Thrift开发,那么就如虎添翼了。可惜,thrift虽然很早就包含了golang的代码,但一直都存在各种问题无法正确执行,以至于GitHub上有许多大牛小牛自行实现的Thrift代码,但依然各种问题……直到0.9.1版本的发布!
是的,最近,Apache Thrift 0.9.1正式发布了。新版的Thrift终于对Golang提供了完美的支持。经过实验,服务器端、客户端已经完美支持跨语言调用,且性能、尤其是内存占用上,编译型语言的特点展现出来,比java版的实现强了很多。
下面,我们采用golang实现一个Thrift的Server端和Client端程序。
一、开发前准备
1、安装golang的Thrift包:
1
|
go get git.apache.org/thrift.git/lib/go/thrift
|
2、产生协议库:
这是我定义的测试用IDL,为检验兼容性,采用了多种数据结构:
1
2
3
4
5
6
7
8
9
10
11
12
|
RpcService.thrift
namespace
go demo.rpc
namespace
java demo.rpc
// 测试服务
service RpcService {
// 发起远程调用
list<string> funCall(1:i64 callTime, 2:string funCode, 3:map<string, string> paramMap),
}
|
3、生成开发库
下载开发库编译器 http://www.apache.org/dyn/closer.cgi?path=/thrift/0.9.1/thrift-0.9.1.exe
thrift-0.9.1.exe -gen go RpcService.thrift
自动生成出的源码结构如下:
其中 constants.go、rpc_service.go、ttypes.go 是协议库,编写程序需要用到。rpc_service-remote.go 是自动生成的例程,可以不用。
二、go语言实现
1、服务器端
下面,我们来写服务器端程序:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
package main
import (
"demo/rpc"
"fmt"
"git.apache.org/thrift.git/lib/go/thrift"
"os"
)
const
(
NetworkAddr =
"127.0.0.1:19090"
)
type RpcServiceImpl
struct
{
}
func (
this
*RpcServiceImpl) FunCall(callTime int64, funCode string, paramMap map[string]string) (r []string, err error) {
fmt.Println(
"-->FunCall:"
, callTime, funCode, paramMap)
for
k, v := range paramMap {
r = append(r, k+v)
}
return
}
func main() {
transportFactory := thrift.NewTFramedTransportFactory(thrift.NewTTransportFactory())
protocolFactory := thrift.NewTBinaryProtocolFactoryDefault()
//protocolFactory := thrift.NewTCompactProtocolFactory()
serverTransport, err := thrift.NewTServerSocket(NetworkAddr)
if
err != nil {
fmt.Println(
"Error!"
, err)
os.Exit(1)
}
handler := &RpcServiceImpl{}
processor := rpc.NewRpcServiceProcessor(handler)
server := thrift.NewTSimpleServer4(processor, serverTransport, transportFactory, protocolFactory)
fmt.Println(
"thrift server in"
, NetworkAddr)
server.Serve()
}
|
加空行也不过才43行,怎么样简单吧。
2、客户端程序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
package main
import (
"demo/rpc"
"fmt"
"git.apache.org/thrift.git/lib/go/thrift"
"net"
"os"
"time"
)
func main() {
startTime := currentTimeMillis()
transportFactory := thrift.NewTFramedTransportFactory(thrift.NewTTransportFactory())
protocolFactory := thrift.NewTBinaryProtocolFactoryDefault()
transport, err := thrift.NewTSocket(net.JoinHostPort(
"127.0.0.1"
,
"19090"
))
if
err != nil {
fmt.Fprintln(os.Stderr,
"error resolving address:"
, err)
os.Exit(1)
}
useTransport := transportFactory.GetTransport(transport)
client := rpc.NewRpcServiceClientFactory(useTransport, protocolFactory)
if
err := transport.Open(); err != nil {
fmt.Fprintln(os.Stderr,
"Error opening socket to 127.0.0.1:19090"
,
" "
, err)
os.Exit(1)
}
defer transport.Close()
for
i := 0; i < 1000; i++ {
paramMap := make(map[string]string)
paramMap[
"name"
] =
"qinerg"
paramMap[
"passwd"
] =
"123456"
r1, e1 := client.FunCall(currentTimeMillis(),
"login"
, paramMap)
fmt.Println(i,
"Call->"
, r1, e1)
}
endTime := currentTimeMillis()
fmt.Println(
"Program exit. time->"
, endTime, startTime, (endTime - startTime))
}
// 转换成毫秒
func currentTimeMillis() int64 {
return
time
.Now().UnixNano() / 1000000
}
|
分别编译,先启动服务器端,然后在执行客户端程序。可以看到控制台正确打印出了信息,说明调用通过。
-->FunCall: 1380446325199 login map[name:qinerg passwd:123456]
三、Java版实现
为了验证跨语言调用,下面我们分别再用java实现一下服务器端和客户端:
生成Java版开发库:
thrift-0.9.1.exe -gen java RpcService.thrift
1、Java服务器版
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
package
demo.rpc;
import
java.util.ArrayList;
import
java.util.List;
import
java.util.Map;
import
java.util.Map.Entry;
import
org.apache.thrift.TException;
import
org.apache.thrift.protocol.TBinaryProtocol;
import
org.apache.thrift.protocol.TBinaryProtocol.Factory;
import
org.apache.thrift.server.TNonblockingServer;
import
org.apache.thrift.server.TServer;
import
org.apache.thrift.transport.TNonblockingServerSocket;
import
org.apache.thrift.transport.TNonblockingServerTransport;
import
org.apache.thrift.transport.TTransportException;
/**
* Thrift测试服务器
*/
public
class
Server
implements
RpcService.Iface {
public
static
void
main(String[] as) {
TNonblockingServerTransport serverTransport =
null
;
try
{
serverTransport =
new
TNonblockingServerSocket(
19090
);
}
catch
(TTransportException e) {
e.printStackTrace();
}
RpcService.Processor<RpcService.Iface> processor =
new
RpcService.Processor<RpcService.Iface>(
new
Server());
Factory protFactory =
new
TBinaryProtocol.Factory(
true
,
true
);
//TCompactProtocol.Factory protFactory = new TCompactProtocol.Factory();
TNonblockingServer.Args args =
new
TNonblockingServer.Args(
serverTransport);
args.processor(processor);
args.protocolFactory(protFactory);
TServer server =
new
TNonblockingServer(args);
System.out.println(
"Start server on port 19090 ..."
);
server.serve();
}
@Override
public
List<String> funCall(
long
callTime, String funCode,
Map<String, String> paramMap)
throws
TException {
System.out.println(
"-->FunCall:"
+ callTime +
" "
+ funCode +
" "
+ paramMap);
List<String> retList =
new
ArrayList<>();
for
(Entry<String, String> entry : paramMap.entrySet()) {
retList.add(entry.getKey() + entry.getValue());
}
return
retList;
}
}
|
2、Java客户端版
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
package
demo.rpc;
import
java.util.HashMap;
import
java.util.Map;
import
org.apache.thrift.TException;
import
org.apache.thrift.protocol.TBinaryProtocol;
import
org.apache.thrift.transport.TFramedTransport;
import
org.apache.thrift.transport.TSocket;
import
org.apache.thrift.transport.TTransport;
/**
* Thrift测试客户端
*/
public
class
Client {
public
static
void
main(String[] args) {
long
startTime = System.currentTimeMillis();
try
{
TTransport transport =
new
TFramedTransport(
new
TSocket(
"localhost"
,
19090
));
TBinaryProtocol protocol =
new
TBinaryProtocol(transport);
//TCompactProtocol protocol = new TCompactProtocol(transport);
RpcService.Client client =
new
RpcService.Client(protocol);
transport.open();
Map<String, String> param =
new
HashMap<String, String>();
param.put(
"name"
,
"qinerg"
);
param.put(
"passwd"
,
"123456"
);
for
(
int
i =
0
; i <
1000
; i++) {
System.out.println(client.funCall(System.currentTimeMillis() ,
"login"
, param));
}
transport.close();
}
catch
(TException x) {
x.printStackTrace();
}
long
endTime = System.currentTimeMillis();
System.out.println(
" 本次调用完成时间:"
+ endTime +
" "
+ startTime +
" "
+ (endTime - startTime));
}
}
|
好了,现在启动java版服务器端,用golang版客户端调用,完全没有问题。启动golang版服务器端程序,用java版客户端调用,同样OK。
完美实现了跨语言调用。