golang grpc使用示例

疑问写前面

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

SEO优化

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

简介

gprc的详细描述不多介绍,可以参考:
http://doc.oschina.net/grpc?t=58008

这里仅与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
}
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值