作项目中发现本身对不少东西的理解有误差,因此决定作个总结,也分享给须要的朋友。若有错误和遗漏,欢迎沟通交流。git
GitHub示例源码github
本文介绍了Go中原生和第三方RPC使用方法,环境搭建方法并提供了材料。golang
远程过程调用(Remote Procedure Call),通俗的说,RPC能够实现跨机器、跨语言调用其余计算机的程序。举个例子,我在机器A上用C语言封装了某个功能的函数,我能够经过RPC在机器B上用GO语言调用机器A上的指定函数。 RPC为C/S模型,一般使用TCP或http协议。shell
go RPC能够利用tcp或http来传递数据,能够对要传递的数据使用多种类型的编解码方式。json
Golang官方的net/rpc库能够经过tcp或http传递数据,但net/rpc库使用encoding/gob进行编解码,支持tcp或http数据传输方式,因为其余语言不支持gob编解码方式,因此使用net/rpc库实现的RPC方法没办法进行跨语言调用。服务器
package main import ( "net/rpc" "net" "fmt" "net/http" ) type Chen struct { } //rcp方法 //func (t *T) MethodName(argType T1, replyType *T2) error func (this *Chen) GetAdd(data int, sum *int) error { *sum = data + 100 return nil } func main() { //1.对象实例化 pd := new(Chen) //2. rpc注册 rpc.Register(pd) //3. rpc网络 rpc.HandleHTTP() //4. 监听网络 ln, err := net.Listen("tcp", "127.0.0.1:12306") if err != nil { fmt.Println("net.Listen error:", err) return } //5. 等待链接 http.Serve(ln, nil) } 复制代码
package main import ( "net/rpc" "fmt" ) func main() { //1. 链接服务器 cln, err := rpc.DialHTTP("tcp", "127.0.0.1:12306") if err != nil { fmt.Println("rpc.Dial error:", err) return } defer cln.Close() //2. 调用服务器函数 var data int err = cln.Call("Chen.GetAdd",10, &data) if err != nil { fmt.Println("cln.Call error:", err) return } //3. 打印输出 fmt.Println("计算结果为:", data) } 复制代码
客户端输出:计算结果为: 110
markdown
Go官方还提供了使用json编解码的rpc库:net/rpc/jsonrpc,可是使用tcp传递数据,不能用http。网络
package main import ( "net/rpc" "net" "fmt" "net/rpc/jsonrpc" ) type Chen struct { } //rcp方法 //func (t *T) MethodName(argType T1, replyType *T2) error func (this *Chen) GetAdd(data int, sum *int) error { *sum = data + 100 return nil } func main() { //1.对象实例化 pd := new(Chen) //2. rpc注册 rpc.Register(pd) //3. 监听网络 ln, err := net.Listen("tcp", "127.0.0.1:12306") if err != nil { fmt.Println("net.Listen error:", err) return } //4. 处理客户端请求 for { conn, err := ln.Accept() if err != nil { fmt.Println("Accept error:", err) continue } go func(conn net.Conn) { jsonrpc.ServeConn(conn) }(conn) } } 复制代码
package main import ( "fmt" "net/rpc/jsonrpc" ) func main() { //1. 链接服务器 cln, err := jsonrpc.Dial("tcp", "127.0.0.1:12306") if err != nil { fmt.Println("jsonrpc.Dial error:", err) return } defer cln.Close() //2. 调用服务器函数 var data int err = cln.Call("Chen.GetAdd",10, &data) if err != nil { fmt.Println("cln.Call error:", err) return } //3. 打印输出 fmt.Println("计算结果为:", data) } 复制代码
客户端输出:计算结果为: 110
tcp
因此为了真正实现跨主机、跨语言的远程调用,须要使用第三方的RPC库,推荐使用谷歌开源的gRPC。gRPC基于HTTP/2,采用protobuf进行数据编解码,压缩和传输效率更高。能够参考本人的Go语言protobuf入门了解Go语言protobuf的环境搭建和使用。函数
因为不能直接访问golang官网,因此安装gPRC和go扩展包比较麻烦,能够从本人gRPC环境包安装中获取压缩包。
unzip x.zip -d /$GOPATH/src/golang.org/x
unzip google.golang.org.zip -d /$GOPATH/src/google.golang.org
复制代码
启动服务器端,
$ cd $GOPATH/src/google.golang.org/grpc/examples/helloworld/greeter_server $ go run main.go 复制代码
启动客户端,
$ cd $GOPATH/src/google.golang.org/grpc/examples/helloworld/greeter_client $ go run main.go 复制代码
若是客户端打印出2019/06/10 15:26:12 Greeting: Hello world
字样即表示gRPC环境正常。
//版本 syntax = "proto3"; //包名 package myproto; //服务 service Hello { //这儿注释才有效 rpc GetAdd(In)returns(Out);//这儿注释无效 } //传入 message In { //此处1不是赋值,而是指参数序号 int64 num = 1; } //传出 message Out { //此处1不是赋值,而是指参数序号 int64 size = 1; } 复制代码
在.proto文件所在目录执行下面的指令,
protoc --go_out=plugins=grpc:./ *.proto
复制代码
生成go代码时要指定plugins=grpc表示生成的是gRPC代码。
package main import ( "fmt" pb "gRPC/myproto" //给package起别名 "context" "net" "google.golang.org/grpc" ) //1. 结构体 type Chen struct { } //2. 该结构体实现HelloServer interface的方法 func (this *Chen)GetAdd(ctx context.Context, In *pb.In)(*pb.Out,error) { return &pb.Out{Size:In.Num+100},nil } func main() { fmt.Println("server runing...") //3. 建立网络 ln, err := net.Listen("tcp", "127.0.0.1:12345") if err != nil { fmt.Println("net.Listen error:", err) return } defer ln.Close() //4. 建立gRPC句柄 srv := grpc.NewServer() //5. 注册server pb.RegisterHelloServer(srv, &Chen{}) //6. 等待网络链接 err = srv.Serve(ln) if err != nil { fmt.Println("srv.Serve error:", err) return } } 复制代码
package main import ( "google.golang.org/grpc" "fmt" pb "gRPC/myproto" "context" ) func main() { //1 链接服务器 conn, err := grpc.Dial("127.0.0.1:12345",grpc.WithInsecure())//grpc.WithInsecure()指定后才不会报错 if err != nil { fmt.Println("grpc.Dial error:", err) return } defer conn.Close() //2 建立客户端句柄 cln := pb.NewHelloClient(conn) //3 调用服务器函数(RPC) out,err := cln.GetAdd(context.Background(), &pb.In{Num:10}) if err != nil { fmt.Println("grpc.Dial error:", err) return } //4 打印 fmt.Println("获得数据:", out.Size) } 复制代码
前后运行服务器和客户端代码,可在客户端打印输出获得数据: 110
,说明已经成功在客户端调用服务端程序。