赞
踩
grpc官网:https://grpc.io/
grpc中文文档:https://doc.oschina.net/grpc
grpc github地址:https://github.com/grpc/grpc
grpc-go github地址:https://github.com/grpc/grpc-go
微服务
单体架构缺点:
微服务架构解决了单体架构的弊端,但是产生了新的问题:
服务拆分后,服务与服务之间发生的是进程与进程之间的相互调用,服务器与服务器之间的调用就需要发起网络调用,在网络调用中我们能立马想起的就是http,但在微服务架构中,http虽然方便快捷,但是性能较低,这就是就需要引入RPC(远程过程调用),通过自定义协议发起TCP调用来加快传输效率。
RPC(Remote Procedure Call 远程过程调用),是一种协议,是用来屏蔽分布式计算中的各种调用细节,使得你可以像是本地调用一样直接调用一个远程的函数。
客户端与服务端沟通的过程
RPC:
gRPC
gRPC是一个高性能的、开源的通用的RPC框架
在gRPC中,我们称调用方为client客户端,被调用方为server服务端,与其它的RPC框架相同,gRPC也是基于“服务定义”的思想,简单来说,就是我们通过某张方式来描述一个服务,这种描述方式是语言无关的,在这个“服务定义”的过程中,我们描述了我们提供的服务名是什么,有哪些方法可以被调用,这些方法有什么样的参数,有什么样的返回值。
也就是说,在定义好这些服务、方法之后,gRPC会屏蔽底层的细节,client只需要直接调用定义好的方法,就能拿到预期的返回结果,对于server来说,还需要实现我们定义的方法,同样的,gRPC也会帮我们屏蔽底层的细节,我们只需要实现定义好的方法的具体逻辑即可。
你可以发现,在上面的描述过程中,所谓的“服务定义”,就跟定义接口的语义是很接近的。我们更愿意理解为这是一种“约定”,双方约定好接口,然后server实现这个接口,client调用这个接口的代理对象,至于其它的细节,交给gRPC。
此外,gRPC还是语言无关的,你可以用C++作为server,golang、java等作为client,为了实现这一点,我们在“定义服务”和在编码和解码的过程中,应该是做到语言无关的。
因此,gRPC使用了Protocol Buffers,这是谷歌开源的一套成熟的数据结构序列化机制
可以把它当成一个代码生成工具以及序列化工具,这个工具可以把我们定义的方法,转换成特定语言的代码,比如你定义了一种类型的参数,它会帮你转换成golang中的struct结构体,你定义的方法,它会帮你转换成func函数,此外,在发送请求和接收响应的时候,这个工具还会完成对应的编码和解码的工作,将你即将发送的数据编码成grpc能够传输的形式,又或者将即将接收到的数据解码为编程语言能够理解的数据格式。
序列化:将数据结构或对象转换成二进制串的过程
反序列化:将序列化产生的二进制串转换成数据结构或对象的过程
protobuf
是谷歌开源的一种数据格式,适合高性能,对响应速度有要求的数据传输场景,因为protobuf
是二进制数据格式,需要编码和解码,数据本身不具有可读性,因此只能反序列化后得到真正刻度的数据
优势:
go的序列化和反序列化的代码:
package main import ( "fmt" "github.com/golang/protobuf/proto" "grpc-demo/service" ) func main() { user := service.User{ Username: "zhangsan", Age: 18, } // 序列化 marshal, err := proto.Marshal(&user) if err != nil { panic(err) } // 反序列化 newUser := service.User{} err = proto.Unmarshal(marshal, &newUser) if err != nil { panic(err) } fmt.Println(newUser.String()) }
protocol buffers
地址:https://github.com/protocolbuffers/protobuf/releasesProtocol buffers,通常成为protobuf,是谷歌开发的一种协议,用于允许对结构化数据进行序列化和反序列化,用于程序开发中通过网络相互通信和存储数据,谷歌开发的目的是提供一种比xml更好的方式进行通信
根据自己的系统选择合适的安装包进行下载
配置环境变量
检查,打开cmd,输入protoc --verion
go get -u google.golang.org/grpc
protoc-gen-go
,不过这里有个小小的坑,github.com/golang/protobuf/protoc-gen-go
和google.golang.org/protobuf/cmd/protoc-gen-go
是不同的,区别在于前者是旧版本,后者是谷歌接管后的新版本,它们之间的API是不同,也就是说用于生成的命令,以及生成的文件都是不一样的,因为目前的gRPC-go源码中的example用的是后者的生成方式,所以我们也采取最新的方式,需要安装两个库:go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
因为这些文件在安装grpc的时候就已经下载下来了,因此使用install命令即可,不需要使用get命令
安装后打开你的$GOPATH/bin目录下,应该有以下两个.exe文件
定义一个proto目录,用于存放编写的.proto文件,编写hello.proto
// 指定当前proto语法的版本,有2和3 syntax = "proto3"; // option go_package = "path;name"; // path表示生成的go文件的存放地址,会自动生成目录,.表示当前目录 // name表示生成的go文件的包名 option go_package = ".;service"; // 生成的go文件在上一层目录下的service包里 // 指定文件生成出来的package package service; // 然后需要定义一个服务,在这个服务中需要有一个方法,这个方法可以接收客户端的参数,再返回给服务端的响应 // 其实很容易可以看出,我们定义了一个service,成为SayHello,这个服务中有一个rpc方法,名为SayHello // 这个方法会发送一个HelloRequest,然后返回一个HelloResponse service SayHello { rpc SayHello(HelloRequest) returns (HelloResponse) {} } // 定义消息的类型,可以理解为go中的struct,后面的数字是序列号,指的是这个变量在这个message中的位置 message HelloRequest { string requestName = 1; // int64 id = 2; } message HelloResponse { string responseMsg = 1; }
编写完成后,在proto
目录下执行命令:
protoc --go_out=. --go-grpc_out=. hello.proto
系统会根据go_out指定的目录再拼接proto文件中go_package指定的目录生成对应的包名
用于protobuf
中定义一个消息类型
消息就是需要进行传输的数据格式的定义,类似于go中的struct
在消息中的数据分别对应每一个字段,其中每一个字段都有一个名字和一种类型
一个proto文件中可以定义多个消息类型
例如:
message User {
string username = 1;
int64 age = 2;
}
在消息中承载的数据分别对应每一个字段
其中每个字段都有一个名字和类型
在一个proto文件中message可以定义多个
required
:消息体中必填字段,不设置会导致编码解码的异常optional
:消息体中可选字段repeated
:消息体中可重复字段,重复的值的顺序会被保留,在go中重复的字段会定义为切片类型。默认为required
必填字段
例如:
message User {
string username = 1;
int64 age = 2;
optional string password = 3;
repeated string addresses = 4;
}
生成的go文件的User结构体
type User struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"`
Age int64 `protobuf:"varint,2,opt,name=age,proto3" json:"age,omitempty"`
Password *string `protobuf:"bytes,3,opt,name=password,proto3,oneof" json:"password,omitempty"`
Addresses []string `protobuf:"bytes,4,rep,name=addresses,proto3" json:"addresses,omitempty"`
}
.proto Type | Notes | C++ Type | Python Type | Go Type |
---|---|---|---|---|
double | double | float | float64 | |
float | float | float | float32 | |
int32 | 使用变长编码,对于负值的效率很低,如果你的域有 可能有负值,请使用sint64替代 | int32 | int | int32 |
uint32 | 使用变长编码 | uint32 | int/long | uint32 |
uint64 | 使用变长编码 | uint64 | int/long | uint64 |
sint32 | 使用变长编码,这些编码在负值时比int32高效的多 | int32 | int | int32 |
sint64 | 使用变长编码,有符号的整型值。编码时比通常的 int64高效。 | int64 | int/long | int64 |
fixed32 | 总是4个字节,如果数值总是比总是比228大的话,这 个类型会比uint32高效。 | uint32 | int | uint32 |
fixed64 | 总是8个字节,如果数值总是比总是比256大的话,这 个类型会比uint64高效。 | uint64 | int/long | uint64 |
sfixed32 | 总是4个字节 | int32 | int | int32 |
sfixed32 | 总是4个字节 | int32 | int | int32 |
sfixed64 | 总是8个字节 | int64 | int/long | int64 |
bool | bool | bool | bool | |
string | 一个字符串必须是UTF-8编码或者7-bit ASCII编码的文 本。 | string | str/unicode | string |
bytes | 可能包含任意顺序的字节数据。 | string | str | []byte |
protobuf3删除了protobuf2中用来设置默认值的default关键字,为各类型定义的默认值
类型 | 默认值 |
---|---|
bool | false |
整型 | 0 |
string | 空字符串"" |
枚举enum | 第一个枚举元素的值,因为Protobuf3强制要求第一个枚举元素的值必须是0,所以枚举的默认值就是0; |
message | 不是null,而是DEFAULT_INSTANCE |
在消息体的定义中,每个字段都必须有一个唯一的标识号,范围是[1,2^29-1]
例如上面user结构体中username的标识号为1,age为2
可以在其它消息类型中定义,使用消息类型,也可以使用外部定义的消息
message PersonInfo {
message Person {
string name = 1;
int32 height = 2;
repeated int32 weight = 3;
}
repeated Person info = 1;
repeated User user = 2;
}
生成的go文件中的结构体
type PersonInfo struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Info []*PersonInfo_Person `protobuf:"bytes,1,rep,name=info,proto3" json:"info,omitempty"` User []*User `protobuf:"bytes,2,rep,name=user,proto3" json:"user,omitempty"` } type PersonInfo_Person struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` Height int32 `protobuf:"varint,2,opt,name=height,proto3" json:"height,omitempty"` Weight []int32 `protobuf:"varint,3,rep,packed,name=weight,proto3" json:"weight,omitempty"` }
如果要在它的父消息类型的外部重用这个消息类型,需要PersonInfo.Person
的形式使用它
message PersonMessage {
PersonInfo.Person info = 1;
}
如果想要将消息类型用在rpc中,可以使用service定义一个服务接口,protocol buffer编译器会根据所选择的不同语言生成服务接口代码及存根
service SayHello {
// rpc 服务函数名(参数) returns (返回参数){}
rpc SayHello(HelloRequest) returns (HelloResponse) {}
}
上述代码表示,定义了一个rpc服务方法,该方法接收参数为HelloRequest
返回HelloResponse
import用于导入其它的proto文件
// 从执行protoc这个命令的当前目录开始算起
import "user.proto";
需要导入any.proto
,属性使用google.protobuf.Any
定义
import "google/protobuf/any.proto";
message HelloAny {
google.protobuf.Any data = 1;
}
结构体中的类型:
Data *anypb.Any `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
package main import ( "context" "google.golang.org/grpc" pb "grpc-study/hello-server/proto" "log" "net" ) type server struct { pb.UnimplementedSayHelloServer } func (s server) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloResponse, error) { return &pb.HelloResponse{ResponsonMsg: "hello " + req.RequestName}, nil } func main() { // 创建端口 listen, _ := net.Listen("tcp", ":9090") // 创建grpc服务 grpcServer := grpc.NewServer() // 注册服务 pb.RegisterSayHelloServer(grpcServer, &server{}) // 启动服务 err := grpcServer.Serve(listen) if err != nil { log.Fatal("服务启动失败:", err) return } }
package main import ( "context" "fmt" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" pb "grpc-study/hello-server/proto" "log" ) func main() { conn, err := grpc.Dial("127.0.0.1:9090", grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { log.Fatal("连接失败:", err) return } defer conn.Close() // 建立连接 client := pb.NewSayHelloClient(conn) // 执行rpc调用 resp, err := client.SayHello(context.Background(), &pb.HelloRequest{RequestName: "zhangsan"}) if err != nil { log.Fatal("调用失败:", err) return } fmt.Println(resp.GetResponsonMsg()) }
grpc是一个典型的C/S模型,需要客户端和服务端,客户端与服务端需要达成相同的协议,以确保在传输数据的过程中不会被外界破坏,grpc通常默认是使用protobuf来作为传输协议,当然也是可以使用其它自定义的方式。
那么,客户端与服务端要通信前,客户端如何知道自己的数据是发送给哪一个明确的服务端?反过来,服务端是不是也需要一个方式来判断自己的数据要返回给谁?
那么就需要grpc的认证来保证客户端与服务端之间可以进行安全的数据传输,认证方式有如下几种:
TLS(Transport Layer Security 安全传输层)是建立在TCP协议之上的协议,服务于应用层,它的前身是SSL(Secure Socket Layer 安全套接字层),它实现了将应用层的报文进行加密后在交由TCP进行传输的功能。
TLS协议主要解决如下三个网络安全问题。
encryption
实现,所有信息都加密传输生产环境可以购买证书或使用一些平台发放的免费证书
什么是SAN
SAN(Subject AlterNative Name)是SSL标准x509中定义的一个扩展,是用来 SAN 字段的 SSL 证书,可以扩展此证书支持的域名,使得一个证书可以支持多个不同域名的解析。
首先通过openssl
生成证书和私钥
或其他人做的便捷版安装包:http://slproweb.com/products/Win32OpenSSL.html
openssl
,安装成功后开始# 1.生成私钥
openssl genrsa -out server.key 2048
# 2.生成证书
openssl req -new -x509 -key server.key -out server.crt -days 36500
# 3.生成csr
openssl req -new -key server.key -out server.csr
# 1.复制一份安装的openssl.cnf到key目录
# 2.找到[ CA_default ],打开copy_extensions = copy(就是把前面的#去掉)
# 3.找到[ req ],打开req_extensions = v3_req
# 4.找到[ v3_req ],添加subjectAltName = @alt_names
# 5.添加新的标签[ alt_names ]和标签字段
DNS.1 = *.com
# 生成证书私钥test.key
openssl genpkey -algorithm RSA -out test.key
# 通过私钥test.key生成证书请求文件test.csr(注意cfg和cnf)
openssl req -new -nodes -key test.key -out test.csr -days 3650 -subj "/C=cn/OU=myorg/O=mycomp/CN=myname" -config ./openssl.cnf -extensions v3_req
# test.csr是上面生成的证书请求文件。ca.crt/server.key是CA证书文件和key,用来对test.csr进行签名认证。这两个文件在第一部分生成。
# 生成SAN证书 pem
openssl x509 -req -days 365 -in test.csr -out test.pem -CA server.crt -CAkey server.key -CAcreateserial -extfile ./openssl.cnf -extensions v3_req
package main import ( "context" "fmt" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" pb "grpc-study/hello-server/proto" "log" ) func main() { cred, _ := credentials.NewClientTLSFromFile("../key/test.pem", "*.com") // 连接到server端,此处禁用安全传输,没有加密和验证 conn, err := grpc.Dial("127.0.0.1:9090", cred) if err != nil { log.Fatalf("连接失败:%v", err) return } defer conn.Close() // 建立连接 client := pb.NewSayHelloClient(conn) // 执行rpc调用 resp, err := client.SayHello(context.Background(), &pb.HelloRequest{RequestName: "zhangsan"}) if err != nil { fmt.Println("调用失败", err) return } fmt.Println(resp.GetResponsonMsg()) }
package main import ( "context" "fmt" "google.golang.org/grpc" "google.golang.org/grpc/credentials" pb "grpc-study/hello-server/proto" "net" ) type server struct { pb.UnimplementedSayHelloServer } func (s server) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloResponse, error) { return &pb.HelloResponse{ResponsonMsg: "hello " + req.RequestName}, nil } func main() { cred, _ := credentials.NewServerTLSFromFile("../key/test.pem", "../key/test.key") // 创建端口 listen, _ := net.Listen("tcp", ":9090") // 创建grpc服务 grpcServer := grpc.NewServer(grpc.Creds(cred)) // 注册服务 pb.RegisterSayHelloServer(grpcServer, &server{}) // 启动服务 err := grpcServer.Serve(listen) if err != nil { fmt.Printf("failed to serve: %v", err) return } }
我们先看一个grpc提供的一个接口,这个接口中有两个方法,接口位于credentials包下,这个接口需要客户端来实现
type PerRPCCredentials interface {
GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error)
RequireTransportSecurity() bool
}
第一个方法作用是获取元数据信息,也就是客户端提供的key-value对,context用于控制超时和取消,uri是请求入口处的uri
第二个方法作用是是否需要基于TLS认证进行安全传输,如果返回值是true,则必须加上TLS验证,返回值是false则不用
package main import ( "context" "fmt" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" pb "grpc-study/hello-server/proto" "log" ) type ClientTokenAuth struct { } func (c *ClientTokenAuth) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { return map[string]string{ "username": "zhangsan", "password": "123456", }, nil } func (c *ClientTokenAuth) RequireTransportSecurity() bool { return false } func main() { // 连接到server端,此处禁用安全传输,没有加密和验证 var opts []grpc.DialOption opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials())) opts = append(opts, grpc.WithPerRPCCredentials(&ClientTokenAuth{})) conn, err := grpc.Dial("127.0.0.1:9090", opts...) if err != nil { log.Fatalf("连接失败:%v", err) return } defer conn.Close() // 建立连接 client := pb.NewSayHelloClient(conn) // 执行rpc调用 resp, err := client.SayHello(context.Background(), &pb.HelloRequest{RequestName: "zhangsan"}) if err != nil { fmt.Println("调用失败", err) return } fmt.Println(resp.GetResponsonMsg()) }
package main import ( "context" "errors" "fmt" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/metadata" pb "grpc-study/hello-server/proto" "log" "net" ) type server struct { pb.UnimplementedSayHelloServer } func (s server) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloResponse, error) { // 获取元数据的信息 md, ok := metadata.FromIncomingContext(ctx) if !ok { return nil, errors.New("未传输token") } var username string var password string if v, ok := md["username"]; ok { username = v[0] } if v, ok := md["password"]; ok { password = v[0] } if username != "zhangsan" || password != "123456" { return nil, errors.New("token错误") } return &pb.HelloResponse{ResponsonMsg: "hello " + req.RequestName}, nil } func main() { // 创建端口 listen, _ := net.Listen("tcp", ":9090") // 创建grpc服务 grpcServer := grpc.NewServer(grpc.Creds(insecure.NewCredentials())) // 注册服务 pb.RegisterSayHelloServer(grpcServer, &server{}) // 启动服务 err := grpcServer.Serve(listen) if err != nil { log.Fatal("服务启动失败:", err) return } }
服务端进行token验证的方式有两种:
上述代码是在业务逻辑中进行token验证的方案
在 HTTP/1.1 的时代,同一个时刻只能对一个请求进行处理或者响应,换句话说,下一个请求必须要等当前请求处理完才能继续进行。
HTTP/1.1需要注意的是,在服务端没有response的时候,客户端是可以发起多个request的,但服务端依旧是顺序对请求进行处理, 并按照收到请求的次序予以返回。
HTTP/2 的时代,多路复用的特性让一次同时处理多个请求成为了现实,并且同一个 TCP 通道中的请求不分先后、不会阻塞,HTTP/2 中引入了流(Stream) 和 帧(Frame) 的概念,当 TCP 通道建立以后,后续的所有操作都是以流的方式发送的,而二进制帧则是组成流的最小单位,属于协议层上的流式传输。
HTTP/2 在一个 TCP 连接的基础上虚拟出多个 Stream, Stream 之间可以并发的请求和处理, 并且 HTTP/2 以二进制帧 (frame) 的方式进行数据传送, 并引入了头部压缩 (HPACK), 大大提升了交互效率
// 普通 RPC
rpc SayHello (HelloRequest) returns (HelloResponse) {}
// 客户端流式 RPC
rpc ClientStream (stream HelloRequest) returns (HelloResponse) {}
// 服务器端流式 RPC
rpc ServerStream (HelloRequest) returns (stream HelloResponse) {}
// 双向流式 RPC
rpc BothStream (stream HelloRequest) returns (stream HelloResponse) {}
stream
关键字,当该关键字修饰参数时,表示这是一个客户端流式的 gRPC 接口;当该参数修饰返回值时,表示这是一个服务器端流式的 gRPC 接口;当该关键字同时修饰参数和返回值时,表示这是一个双向流式的 gRPC 接口。
客户端代码:
package main import ( "context" "fmt" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" pb "grpc-demo/proto" "log" "time" ) func main() { conn, err := grpc.Dial("127.0.0.1:9090", grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { log.Fatal("连接失败:", err) return } defer conn.Close() // 建立连接 client := pb.NewSayHelloClient(conn) // 执行rpc调用 clientStream, err := client.ClientStream(context.Background()) if err != nil { log.Fatal("调用失败", err) return } helloch := make(chan struct{}, 1) go helloRequest(clientStream, helloch) select { case <-helloch: resp, err := clientStream.CloseAndRecv() if err != nil { log.Fatal(err) } fmt.Println("客户端收到响应:", resp.ResponsonMsg) } } func helloRequest(stream pb.SayHello_ClientStreamClient, rsp chan struct{}) { count := 0 for { err := stream.Send(&pb.HelloRequest{RequestName: "zhangsan"}) if err != nil { log.Fatal(err) } time.Sleep(time.Second) count++ if count > 10 { rsp <- struct{}{} break } } }
服务端代码:
package main import ( "context" "fmt" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" pb "grpc-demo/proto" "io" "log" "net" ) type server struct { pb.UnimplementedSayHelloServer } func (s server) ClientStream(stream pb.SayHello_ClientStreamServer) error { count := 0 for { //源源不断的去接收客户端发来的信息 req, err := stream.Recv() if err != nil { if err == io.EOF { return nil } return err } fmt.Println("服务端接收到的流", req.RequestName, count) count++ if count > 10 { resp := &pb.HelloResponse{ResponsonMsg: req.RequestName} err := stream.SendAndClose(resp) if err != nil { return err } return nil } } } func main() { listen, _ := net.Listen("tcp", ":9090") // 创建grpc服务 grpcServer := grpc.NewServer(grpc.Creds(insecure.NewCredentials())) // 注册服务 pb.RegisterSayHelloServer(grpcServer, &server{}) // 启动服务 err := grpcServer.Serve(listen) if err != nil { log.Fatal("服务启动失败:", err) return } }
客户端代码:
package main import ( "context" "fmt" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" pb "grpc-demo/proto" "io" "log" ) func main() { conn, err := grpc.Dial("127.0.0.1:9090", grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { log.Fatal("连接失败:", err) return } defer conn.Close() // 建立连接 client := pb.NewSayHelloClient(conn) // 执行rpc调用 serverStream, err := client.ServerStream(context.Background(), &pb.HelloRequest{RequestName: "zhangsan"}) if err != nil { log.Fatal("获取流出错", err) } for { resp, err := serverStream.Recv() if err != nil { if err == io.EOF { fmt.Println("客户端数据接收完成") err := serverStream.CloseSend() if err != nil { log.Fatal(err) } break } log.Fatal(err) } fmt.Println("客户端收到的流", resp.ResponsonMsg) } }
服务端代码:
package main import ( "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" pb "grpc-demo/proto" "log" "net" "time" ) type server struct { pb.UnimplementedSayHelloServer } func (s server) ServerStream(req *pb.HelloRequest, stream pb.SayHello_ServerStreamServer) error { count := 0 for { resp := &pb.HelloResponse{ResponsonMsg: req.RequestName} err := stream.Send(resp) if err != nil { return err } time.Sleep(time.Second) count++ if count > 10 { return nil } } } func main() { listen, _ := net.Listen("tcp", ":9090") // 创建grpc服务 grpcServer := grpc.NewServer(grpc.Creds(insecure.NewCredentials())) // 注册服务 pb.RegisterSayHelloServer(grpcServer, &server{}) // 启动服务 err := grpcServer.Serve(listen) if err != nil { log.Fatal("服务启动失败:", err) return } }
客户端代码:
package main import ( "context" "fmt" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" pb "grpc-demo/proto" "log" "time" ) func main() { conn, err := grpc.Dial("127.0.0.1:9090", grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { log.Fatal("连接失败:", err) return } defer conn.Close() // 建立连接 client := pb.NewSayHelloClient(conn) // 执行rpc调用 bothStream, err := client.BothStream(context.Background()) if err != nil { log.Fatal("获取流出错", err) } for { err = bothStream.Send(&pb.HelloRequest{RequestName: "zhangsan"}) if err != nil { log.Fatal(err) } time.Sleep(time.Second) resp, err := bothStream.Recv() if err != nil { log.Fatal(err) } fmt.Println("客户端收到的流信息", resp.ResponsonMsg) } }
服务端代码:
package main import ( "fmt" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" pb "grpc-demo/proto" "log" "net" "time" ) type server struct { pb.UnimplementedSayHelloServer } func (s server) BothStream(stream pb.SayHello_BothStreamServer) error { for { req, err := stream.Recv() if err != nil { return nil } fmt.Println("服务端收到客户端的消息", req.RequestName) time.Sleep(time.Second) resp := &pb.HelloResponse{ResponsonMsg: req.RequestName} err = stream.Send(resp) if err != nil { return nil } } } func main() { listen, _ := net.Listen("tcp", ":9090") // 创建grpc服务 grpcServer := grpc.NewServer(grpc.Creds(insecure.NewCredentials())) // 注册服务 pb.RegisterSayHelloServer(grpcServer, &server{}) // 启动服务 err := grpcServer.Serve(listen) if err != nil { log.Fatal("服务启动失败:", err) return } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。