golang基础-httprpc、jsonrpc
golang基础-protobuf使用
以上2篇博客是了解httprpc、jsonrpc和protobuf的简单使用,今天就结合起来学习下
RPC、Protobuf相结合的例子
在简单的学习了RPC、Protobuf的规则之后,我们通过一个demo来学习下RPC、Protobuf是如何结合使用的
首先来看下这个demo的目录结构:
先来看下rpc.proto是如何定义数据的
数据很简单就一个string类型的value值
syntax = "proto3";
package go_protoc;
message String {
string value = 1;
}
然后通过protoc ./rpc.proto --go_out=./
进行编译为rpc.pb.go
在命令行执行如下命令即可
zhiliaodeMBP:go_protoc zhiliao$ protoc ./rpc.proto --go_out=./
zhiliaodeMBP:go_protoc zhiliao$ pwd
/Users/zhiliao/zhiliao/go/src/go_protoc
zhiliaodeMBP:go_protoc zhiliao$
然后基本数据类型有了,那么就着手去写服务端的代码server.go
我们先构造一个HelloService类型,其中的Hello方法用于实现打印功能:
其中Hello方法必须满足Go语言的RPC规则:
1、方法只能有两个可序列化的参数
2、其中第二个参数是指针类型
3、并且返回一个error类型
4、同时必须是公开的方法。
package main
import (
"go_protoc"
"log"
"net"
"net/rpc"
)
type HelloService struct {}
func (p *HelloService) Hello(request *go_protoc.String, reply *go_protoc.String) error {
reply.Value = "hello:" + request.GetValue()
return nil
}
然后就可以将HelloService类型的对象注册为一个RPC服务
func main() {
rpc.RegisterName("HelloService", new(HelloService))
listener, err := net.Listen("tcp", ":1234")
if err != nil {
log.Fatal("ListenTCP error:", err)
}
conn, err := listener.Accept()
if err != nil {
log.Fatal("Accept error:", err)
}
rpc.ServeConn(conn)
}
其中rpc.Register函数调用会将对象类型中所有满足RPC规则的对象方法注册为RPC函数,所有注册的方法会放在“HelloService”服务空间之下。然后我们建立一个唯一的TCP链接,并且通过rpc.ServeConn函数在该TCP链接上为对方提供RPC服务
下面是客户端请求HelloService服务的代码server.go
:
package main
import (
"fmt"
"go_protoc"
"log"
"net/rpc"
)
func main() {
client, err := rpc.Dial("tcp", "localhost:1234")
if err != nil {
log.Fatal("dialing:", err)
}
var reply = &go_protoc.String{}
var param = &go_protoc.String{
Value:"hello",
}
err = client.Call("HelloService.Hello", ¶m, &reply)
if err != nil {
log.Fatal(err)
}
fmt.Println(reply)
}
首选是通过rpc.Dial拨号RPC服务,然后通过client.Call调用具体的RPC方法。在调用client.Call时,第一个参数是用点号链接的RPC服务名字和方法名字,第二和第三个参数分别我们定义RPC方法的两个参数。
然后我们启动server.go,然后在启动client.go
,输出结果如下:
GRPC插件由来
回顾我们之前的RPC接口部分的内容,当时我们花费了极大的力气去给RPC服务增加安全的保障。最终得到的更安全的RPC接口的代码本身就非常繁琐的使用手工维护,同时全部安全相关的代码只适用于Go语言环境!既然使用了Protobuf定义的输入和输出参数,那么RPC服务接口是否也可以通过Protobuf定义呢?其实用Protobuf定义语言无关的RPC服务接口才是它真正的价值所在!
下面看下rpc.proto文件,通过Protobuf来定义HelloService服务:
syntax = "proto3";
package go_protoc;
message String {
string value = 1;
}
service HelloService {
rpc Hello (String) returns (String);
}
但是重新生成的Go代码并没有发生变化。这是因为世界上的RPC实现有千万种,protoc编译器并不知道该如何为HelloService服务生成代码。
不过在protoc-gen-go内部已经集成了一个叫grpc的插件,可以针对grpc生成代码
$ protoc --go_out=plugins=grpc:. hello.proto
在生成的代码中多了一些类似HelloServiceServer、HelloServiceClient的新类型。这些类型是为grpc服务的,并不符合我们的RPC要求。
// HelloServiceServer is the server API for HelloService service.
type HelloServiceServer interface {
Hello(context.Context, *String) (*String, error)
}
func RegisterHelloServiceServer(s *grpc.Server, srv HelloServiceServer) {
s.RegisterService(&_HelloService_serviceDesc, srv)
}
type HelloServiceClient interface {
Hello(ctx context.Context, in *String, opts ...grpc.CallOption) (*String, error)
}
type helloServiceClient struct {
cc *grpc.ClientConn
}
func NewHelloServiceClient(cc *grpc.ClientConn) HelloServiceClient {
return &helloServiceClient{cc}
}
GRPC库依赖下载
安装官方安装命令:
go get google.golang.org/grpc
是安装不起的,会报:
package google.golang.org/grpc: unrecognized import path "google.golang.org/grpc"(https fetch: Get https://google.golang.org/grpc?go-get=1: dial tcp 216.239.37.1:443: i/o timeout)
原因是这个代码已经转移到github上面了,但是代码里面的包依赖还是没有修改,还是google.golang.org这种,
所以不能使用go get的方式安装
git clone https://github.com/grpc/grpc-go.git $GOPATH/src/google.golang.org/grpc
git clone https://github.com/golang/net.git $GOPATH/src/golang.org/x/net
git clone https://github.com/golang/text.git $GOPATH/src/golang.org/x/text
go get -u github.com/golang/protobuf/{proto,protoc-gen-go}
git clone https://github.com/google/go-genproto.git $GOPATH/src/google.golang.org/genproto
cd $GOPATH/src/
go install google.golang.org/grpc
到这里还会报package golang.org/x/text: unrecognized import path “golang.org/x/text” (https fetch: Get https://golang.org/x/text?go-get=1: dial tcp 119.28.87.227:443: i/o timeout)错误
获取 golang.org/x/net 包,其实只需要以下步骤:
mkdir -p $GOPATH/src/golang.org/x
cd $GOPATH/src/golang.org/x
git clone https://github.com/golang/text.git
GRPC例子学习
上图一个项目目录结构
首先看下rpc.proto数据
syntax = "proto3";
package go_protoc;
message String {
string value = 1;
}
service HelloService {
rpc Hello (String) returns (String);
}
然后进行通过protoc --go_out=plugins=grpc:. rpc.proto
进行编译
zhiliaodeMBP:go_protoc zhiliao$ protoc --go_out=plugins=grpc:. rpc.proto
zhiliaodeMBP:go_protoc zhiliao$ pwd
/Users/zhiliao/zhiliao/go/src/go_protoc
zhiliaodeMBP:go_protoc zhiliao$
接下来看服务端的代码server.go
package main
import (
"context"
"go_protoc"
"google.golang.org/grpc"
"log"
"net"
)
type HelloServiceImpl struct{}
func (p *HelloServiceImpl) Hello(
ctx context.Context, args *go_protoc.String,
) (*go_protoc.String, error) {
reply := &go_protoc.String{Value: "hello:" + args.GetValue()}
return reply, nil
}
func main() {
grpcServer := grpc.NewServer()
go_protoc.RegisterHelloServiceServer(grpcServer, new(HelloServiceImpl))
lis, err := net.Listen("tcp", ":1234")
if err != nil {
log.Fatal(err)
}
grpcServer.Serve(lis)
}
首先是通过grpc.NewServer()构造一个GRPC服务对象,然后通过GRPC插件生成的RegisterHelloServiceServer函数注册我们实现的HelloServiceImpl服务。然后通过grpcServer.Serve(lis)在一个监听端口上提供GRPC服务。
接下来看客户端的代码client.go
package main
import (
"fmt"
"go_protoc"
"google.golang.org/grpc"
"log"
"golang.org/x/net/context"
)
func main() {
conn, err := grpc.Dial("localhost:1234", grpc.WithInsecure())
if err != nil {
log.Fatal(err)
}
defer conn.Close()
client := go_protoc.NewHelloServiceClient(conn)
reply, err := client.Hello(context.Background(), &go_protoc.String{Value: "hello-sadly"})
if err != nil {
log.Fatal(err)
}
fmt.Println(reply.GetValue())
}
其中grpc.Dial负责和GRPC服务建立链接,然后NewHelloServiceClient函数基于已经建立的链接构造HelloServiceClient对象。返回的client其实是一个HelloServiceClient接口对象,通过接口定义的方法就可以调用服务端对应的GRPC服务提供的方法。
启动服务端,然后启动客户端结果输出如下: