0
点赞
收藏
分享

微信扫一扫

golang grpc使用示例


疑问写前面

  1. grpc有内部对心跳的处理吗,还是说,双工需要自己作心跳管理,有懂的留言一下。

SEO优化

grpc如何双工通信?
grpc如何从服务端推送消息给客户端?
gprc环境如何搭建?
grpc生成go文件的命令是?
grpc牛比.
grpc stream 服务端如何Close?

简介

gprc的详细描述不多介绍

这里仅与http作横向对比,集中体现在如下差异:

  1. 传输协议: grpc为tcp(http2),http为http1,http2,使用者感知度不大,不作介绍.
  2. 序列化协议: grpc为protobuf,http为json,xml等,json居多,前者为二进制协议,后者为文本协议,所以grpc序列化的速度和大小远优于http,但对普通的业务,性能感知差异不大。
  3. 服务路由: grpc根据预先在proto文件里声明好的服务方法,客户端可以对生成的客户端代码直接调用这些声明好的服务方法,而http则是根据url地址与method协同,来路由到正确的服务方法.
  4. 服务调用: grpc直接面向ip与端口,http面向域名,即一般是限定在80(http)和443(https)

选择

选择http还是grpc,这个可以根据简介里的差异来分析。

  1. 面向外部用户与否。grpc直接面向ip和端口,所以肯定不方便拿给外部用,选http最佳,但这不是绝对的,毕竟socket本身也是面向ip和端口的,需要tcp的场景也没有谁会刻意转http.
  2. 需不需要苛求带宽与速度 。grpc因为他的序列化protobuf优势,小而快,苛求带宽和速度刻意选用grpc.
  3. 长连接与否。如果只再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
}


举报

相关推荐

0 条评论