疑问写前面
- grpc有内部对心跳的处理吗,还是说,双工需要自己作心跳管理,有懂的留言一下。
SEO优化
grpc如何双工通信?
grpc如何从服务端推送消息给客户端?
gprc环境如何搭建?
grpc生成go文件的命令是?
grpc牛比.
grpc stream 服务端如何Close?
简介
gprc的详细描述不多介绍
这里仅与http作横向对比,集中体现在如下差异:
- 传输协议: grpc为tcp(http2),http为http1,http2,使用者感知度不大,不作介绍.
- 序列化协议: grpc为protobuf,http为json,xml等,json居多,前者为二进制协议,后者为文本协议,所以grpc序列化的速度和大小远优于http,但对普通的业务,性能感知差异不大。
- 服务路由: grpc根据预先在proto文件里声明好的服务方法,客户端可以对生成的客户端代码直接调用这些声明好的服务方法,而http则是根据url地址与method协同,来路由到正确的服务方法.
- 服务调用: grpc直接面向ip与端口,http面向域名,即一般是限定在80(http)和443(https)
选择
选择http还是grpc,这个可以根据简介里的差异来分析。
- 面向外部用户与否。grpc直接面向ip和端口,所以肯定不方便拿给外部用,选http最佳,但这不是绝对的,毕竟socket本身也是面向ip和端口的,需要tcp的场景也没有谁会刻意转http.
- 需不需要苛求带宽与速度 。grpc因为他的序列化protobuf优势,小而快,苛求带宽和速度刻意选用grpc.
- 长连接与否。如果只再grpc和http里选择,选grpc,当然可以,面向长连接我更推荐原生socket(tcp,ws)之类的
go示例
示例repo:
https://github.com/fwhezfwhez/TestX/tree/master/test_grpc 依赖:
protoc:https://github.com/golang/protobuf protoc-gen-go:https://github.com/golang/protobuf/tree/master/protoc-gen-go
需要在path下,找到 protoc和protoc-gen-go命令,执行protoc --version
输出版本号即安装正常.
proto文件生成go命令:
protoc --go_out=plugins=grpc:. hello.proto
hello.proto
syntax = "proto3";
package pb;
message HelloRequest {
string username = 1;
}
message HelloResponse {
string message = 1;
}
service HelloService {
rpc SayHello(HelloRequest) returns (HelloResponse){}
}
client
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
"test_X/test_grpc/pb"
)
func main() {
conn, err := grpc.Dial("localhost:6001", grpc.WithInsecure())
if err != nil {
fmt.Println(err.Error())
return
}
defer conn.Close()
c := pb.NewHelloServiceClient(conn)
r, err := c.SayHello(context.Background(), &pb.HelloRequest{Username: "ft"})
if err != nil {
fmt.Println(err.Error())
return
}
fmt.Println(r.Message)
}
server
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
"net"
"test_X/test_grpc/pb"
)
type HelloService struct {
}
func (hs HelloService) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error) {
return &pb.HelloResponse{Message: fmt.Sprintf("你好,%s", in.Username)}, nil
}
func main() {
lis, err := net.Listen("tcp", ":6001")
if err != nil {
fmt.Println(err.Error())
return
}
s := grpc.NewServer()
pb.RegisterHelloServiceServer(s, HelloService{})
s.Serve(lis)
}
拓展
1. grpc如何双工,服务端推送
在上述代码里直接拓展:
第一步,增加proto接口描述并重新生成go文件:
protoc --go_out=plugins=grpc:. hello.proto
// How to generate hello.proto to go file:
// protoc --go_out=plugins=grpc:. hello.proto
syntax = "proto3";
package pb;
message HelloRequest {
string username = 1;
}
message HelloResponse {
string message = 1;
}
// +
message ClientStream {
bytes stream = 1;
}
message ServerStream {
bytes stream = 1;
}
service HelloService {
rpc SayHello(HelloRequest) returns (HelloResponse){}
rpc Chat(stream ClientStream) returns (stream ServerStream){}
}
// +
第二步,增加服务端实现Chat的方法实例
server
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
"io"
"net"
"test_X/test_grpc/pb"
)
type HelloService struct {
}
func (hs *HelloService) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloResponse, error) {
return &pb.HelloResponse{Message: fmt.Sprintf("你好,%s", req.Username)}, nil
}
// ++++++++++++++++++++++++++++++++
func (hs *HelloService) Chat(conn pb.HelloService_ChatServer)error {
for {
stream, err:=conn.Recv()
if err == io.EOF {
return nil
}
if err != nil {
return err
}
fmt.Println("receive from client:",stream.Stream)
conn.Send(&pb.ServerStream{
Stream: newBytes(1,2,3,4,5),
})
}
return nil
}
// ++++++++++++++++++++
func main() {
lis, err := net.Listen("tcp", ":6001")
if err != nil {
fmt.Println(err.Error())
return
}
s := grpc.NewServer()
pb.RegisterHelloServiceServer(s, &HelloService{})
go func() {
s.Serve(lis)
}()
fmt.Println(s.GetServiceInfo())
select {}
}
func newBytes(a ...byte)[]byte{
return a
}
第三步,客户端调用
client
package main
import (
"context"
"errorX"
"fmt"
"google.golang.org/grpc"
"io"
"test_X/test_grpc/pb"
"time"
)
func main() {
conn, e := grpc.Dial("localhost:6001", grpc.WithInsecure())
if e != nil {
fmt.Println(e.Error())
return
}
defer conn.Close()
c := pb.NewHelloServiceClient(conn)
// say hello
r, e := c.SayHello(context.Background(), &pb.HelloRequest{Username: "ft"})
if e != nil {
fmt.Println(e.Error())
return
}
fmt.Println(r.Message)
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// chat
chatClilent, e :=c.Chat(context.Background())
if e != nil {
fmt.Println(e.Error())
return
}
go func(){
for{
stream, e:=chatClilent.Recv()
if e == io.EOF {
fmt.Println("EOF")
return
}
if e != nil {
fmt.Println(errorx.Wrap(e).Error())
return
}
fmt.Println("receive from server:", stream.Stream)
}
}()
chatClilent.Send(&pb.ClientStream{
Stream: newBytes(10,9,8,7),
})
select{
case <-time.After(20 * time.Second):
}
}
func newBytes(a ...byte)[]byte{
return a
}
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
结束:
cd server
go run main.go
cd client
go run main.go
receive from client: [10 9 8 7]
你好,ft
receive from server: [1 2 3 4 5]
服务端如何Close
rpc面向每个连接,即服务端chat函数return即Close连接
func (hs *HelloService) Chat(conn pb.HelloService_ChatServer)error {
for {
stream, err:=conn.Recv()
if err == io.EOF {
fmt.Println("EOF")
return nil
}
if err != nil {
return err
}
fmt.Println("receive from client:",stream.Stream)
conn.Send(&pb.ServerStream{
Stream: newBytes(1,2,3,4,5),
})
// 关闭连接
// return nil
}
return nil
}