GO grpc

视频地址:https://www.bilibili.com/video/BV1eE411T7GC?p=18

  • 建立服务端 go_grpc(工程名,刚开始忘记加server)

创建相应的目录

1.terminal运行

go get google.golang.org/grpc 

2.编写message.proto&&enum.proto

坑1 可能import会出现问题,就算在goland上报错只要能顺利执行下一步的指令就可以忽略红色报错

message

syntax ="proto3";


import "enums.proto";
import "google/protobuf/timestamp.proto";
option go_package = "./pb";
package pb;

message Employee {
    int32 id = 1;
    int32 number = 2;
    string firstName = 3;
    string lastName = 4;
    MonthSalary monthSalary = 6;
    EmployeeStatus status = 7;
    google.protobuf.Timestamp lastModfied = 8;
    reserved 5;
    reserved "salary";

}

message MonthSalary {
    float basic = 1;
    float bonus = 2;
}
message GetByNoRequest {
    int32 number = 1;
}

message EmployeeResponse {
    Employee employee = 1;
}


message GetAllRequest { }

message AddPhotoRequest {
    bytes data = 1;
}

message AddPhotoResponse {
    bool isOk = 1;
}

message EmployeeRequest {
    Employee employee = 1;
}

service EmployeeService {
    rpc GetByNo(GetByNoRequest) returns (EmployeeResponse);
    rpc GetAll(GetAllRequest) returns (stream EmployeeResponse);
    rpc AddPhoto(stream AddPhotoRequest) returns (AddPhotoResponse);
    rpc Save(EmployeeRequest) returns (EmployeeResponse);
    rpc SaveAll(stream EmployeeRequest) returns (stream EmployeeResponse);
}

enum

syntax ="proto3";

option go_package = "./pb";
package pb;
enum EmployeeStatus {
    NORMAL = 0;
    ON_VACATION = 1;
    RESIGNED = 2;
    RETIRED = 3;
}

 

3.运行指令生成两个go proto文件//

注意一定要使用plugin命令,不然无法生成对应的service的proto

注意proto中的

option go_package = "./pb";
protoc ./*.proto --go_out=plugins=grpc:../

运行完上述指令之后会自动在pb文件下创立pb.go

4.生成证书认证

巨坑,见下面的客户端

server 代码

package main

import (
	"errors"
	"fmt"
	"go_grpc/pb"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"google.golang.org/grpc/metadata"
	"io"
	"log"
	"net"
)


const port = ":5001"

func main(){
	listen, err := net.Listen("tcp",port)

	if err != nil {
		log.Fatalln(err.Error())
	}
	//创建creds证书
	creds,err := credentials.NewServerTLSFromFile("server.pem","server.key")
	if err != nil {
		log.Fatalln(err.Error())
	}
	//通过grpc传递creds证书
	options := []grpc.ServerOption{grpc.Creds(creds)}
	//创建server
	server := grpc.NewServer(options...)
	pb.RegisterEmployeeServiceServer(server,new(employeeService))

	log.Println("gRPC Server started ..." + port)
	//开启server监听listen端口号
	server.Serve(listen)

}

type employeeService struct{}

//GetByNo:通过员工编号找到员工
//一元消息传递
func (s *employeeService) GetByNo (ctx context.Context,
	req *pb.GetByNoRequest) (*pb.EmployeeResponse, error){
	for _, e :=range employees {
		if req.Number == e.Number {
			return &pb.EmployeeResponse{
				Employee: &e,
			},nil
		}
	}

	return nil,errors.New("Employee not found")
}

//二元消息传递
//服务端会将数据以streaming的形式传回
func (s *employeeService)GetAll(req *pb.GetAllRequest,
	stream pb.EmployeeService_GetAllServer) error {

	for _,e := range employees {
		//stream.send会将数据一块块的传给客户端
		stream.Send(&pb.EmployeeResponse{
			Employee: &e,
		})
	}
	return nil
}

//client 以stream的形式传输图片给服务端
func (s *employeeService)AddPhoto(stream  pb.EmployeeService_AddPhotoServer) error {
	md, ok := metadata.FromIncomingContext(stream.Context())

	if ok {
		//通过metadata获取并输出employee的number
		fmt.Printf("Employee: %s\n",md["number"][0])
	}

	img := []byte{}
	for {
		data,err := stream.Recv()
		if err == io.EOF {
			//输出文件大小
			fmt.Printf("File Size: %d\n",len(img))
			//告诉客户端已经成功接收
			return stream.SendAndClose(&pb.AddPhotoResponse{
				IsOk: true,
			})
			if err != nil {
				return err
			}
		}
		//输出每次接收的一小块的大小
		fmt.Printf("File received: %d\n",len(data.Data))
		img = append(img,data.Data...)
	}
}

func (s *employeeService)Save(context.Context,
	*pb.EmployeeRequest) (*pb.EmployeeResponse, error){
	return nil,nil
}

//双向传送stream
func (s *employeeService)SaveAll(
	stream pb.EmployeeService_SaveAllServer) error {
	for {
		empReq, err := stream.Recv()
		if err == io.EOF {
			break
		}
		if err != nil {
			return err
		}
		employees = append(employees,*empReq.Employee)
		stream.Send(&pb.EmployeeResponse{
			Employee: empReq.Employee,
		})
	}

	for _,emp := range employees {
		fmt.Println(emp)
	}

	return nil
}


 

 

 

 

  • 建立客户端 go_grpc_client

创建相应的目录文件,并存放proto

1.terminal运行

go get google.golang.org/grpc 

2.运行指令生成两个go proto文件

 protoc ./*.proto --go_out=plugins=grpc:../

 

这样客户端和服务器端的证书会相互匹配

3.编写main函数

package main

import (
	"context"
	"fmt"
	"go_grpc_client/pb"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"log"
)

const port = ":5001"
func main() {
	creds, err := credentials.NewClientTLSFromFile("cert.pem","")
	if err != nil {
		log.Fatal(err.Error())
	}
	//接着设置options
	options := []grpc.DialOption{grpc.WithTransportCredentials(creds)}
	conn, err := grpc.Dial("localhost" + port,options ...)
	if err != nil {
		log.Fatal(err.Error())
	}
	defer conn.Close()
	client := pb.NewEmployeeServiceClient(conn)
	fmt.Println("Client Server started...")
	getByNo(client)
}

func getByNo(client pb.EmployeeServiceClient) {
	res, err := client.GetByNo(context.Background(),&pb.GetByNoRequest{Number: 1994})
	if err != nil {
		log.Fatal(err.Error())
	}
	fmt.Println(res.Employee)
}

go run main.go 报错

code = Unavailable desc = connection error: desc = "transport: authentication handshake failed: x509: certificate relies on legacy Common Name field, use SANs or temporarily enable Common Name matching with GODEBUG=x509ignoreCN=0"
Exiting.
  • 运行报错 GRPC X509 Common Name field, use SANs or temporarily enable Common Name matching

解决:问题原因GO1.15版本以上已经把Common Name这个东西砍了!解决办法:

转载自:https://blog.csdn.net/weixin_40280629/article/details/113563351

其中需要注意的是:alt_names 下的DNS.1=www.xxx.com(这个地方自己设置) 还有下面的ip也设置一下

然后将服务端生成的server.pem文件拷贝到客户端,修改代码段:

//第二个参数就是刚才在alt_names中设置的DNS
creds, err := credentials.NewClientTLSFromFile("server.pem","www.test.com")

服务端修改代码段:

creds,err := credentials.NewServerTLSFromFile("server.pem","server.key")

开始运行,通过

成功获取了服务端的数据,自此,简单的grpc一元消息传递已经完成

其实这里可以去补一下http协议中的ssl和tls

  • grpc消息传输类型

grpc的消息传输类型有4种:

  1. 第一种是一元的消息,就是简单的请求响应
  2. 第二种是server streaming(流),server会把数据streaming回给client(streaming是把数据分成块传输)
  3. 第三种是client streaming,也就是client会把数据streamin给server
  4. 第四种是双向的streaming

 

  • server streaming

server 代码

//二元消息传递
//服务端会将数据以streaming的形式传回
func (s *employeeService)GetAll(req *pb.GetAllRequest,
	stream pb.EmployeeService_GetAllServer) error {

	for _,e := range employees {
		//stream.send会将数据一块块的传给客户端
		stream.Send(&pb.EmployeeResponse{
			Employee: &e,
		})
	}
	return nil
}

client 代码

func getAll(client pb.EmployeeServiceClient) {
	stream, err := client.GetAll(context.Background(),&pb.GetAllRequest{})
	if err != nil {
		log.Fatal(err.Error())
	}
	for {
		res,err := stream.Recv()
		//如果服务端数据发送结束,则为EOF
		if err == io.EOF {
			break
		}
		if err != nil {
			log.Fatal(err.Error())
		}
		fmt.Println(res.Employee)
	}
}
  • client streaming

采用stream的形式向服务器传送图片

client 代码

func addPhoto (client pb.EmployeeServiceClient) {
	imgFile, err := os.Open("WechatIMG3.jpeg")
	if err != nil {
		log.Fatal(err.Error())
	}
	defer imgFile.Close()
	//metadata相当于报文的header,我们只需要把用户number放在header传输一次就可以了
	md := metadata.New(map[string]string{"number":"1994"})
	context := context.Background()
	context = metadata.NewOutgoingContext(context,md)

	stream, err := client.AddPhoto(context)
	if err != nil {
		fmt.Println(err)
		log.Fatal(err.Error())
	}
	//循环分块传输数据
	for {
		chunk := make([]byte,128*1024)
		chunkSize, err := imgFile.Read(chunk)
		if err == io.EOF {
			break
		}
		if err != nil {
			log.Fatal(err.Error())
		}
		if chunkSize < len(chunk) {
			chunk = chunk[:chunkSize]
		}
		//开始分块发送数据
		stream.Send(&pb.AddPhotoRequest{Data: chunk})

	}
	//closeandrec会向客户端发送一个信号EOF,等待服务端发回一个响应
	res,err := stream.CloseAndRecv()
	if err != nil {
		log.Fatal(err.Error())
	}
	fmt.Println(res.IsOk)
}

server 代码

//client 以stream的形式传输图片给服务端
func (s *employeeService)AddPhoto(stream  pb.EmployeeService_AddPhotoServer) error {
	md, ok := metadata.FromIncomingContext(stream.Context())

	if ok {
		//通过metadata获取并输出employee的number
		fmt.Printf("Employee: %s\n",md["number"][0])
	}

	img := []byte{}
	for {
		data,err := stream.Recv()
		if err == io.EOF {
			//输出文件大小
			fmt.Printf("File Size: %d\n",len(img))
			//告诉客户端已经成功接收
			return stream.SendAndClose(&pb.AddPhotoResponse{
				IsOk: true,
			})
			if err != nil {
				return err
			}
		}
		//输出每次接收的一小块的大小
		fmt.Printf("File received: %d\n",len(data.Data))
		img = append(img,data.Data...)
	}



}

client 代码

//client 以stream的形式传输图片给服务端
func (s *employeeService)AddPhoto(stream  pb.EmployeeService_AddPhotoServer) error {
	md, ok := metadata.FromIncomingContext(stream.Context())

	if ok {
		//通过metadata获取并输出employee的number
		fmt.Printf("Employee: %s\n",md["number"][0])
	}

	img := []byte{}
	for {
		data,err := stream.Recv()
		if err == io.EOF {
			//输出文件大小
			fmt.Printf("File Size: %d\n",len(img))
			//告诉客户端已经成功接收
			return stream.SendAndClose(&pb.AddPhotoResponse{
				IsOk: true,
			})
			if err != nil {
				return err
			}
		}
		//输出每次接收的一小块的大小
		fmt.Printf("File received: %d\n",len(data.Data))
		img = append(img,data.Data...)
	}
}

 

  • 双向stream

client 代码

func saveAll(client pb.EmployeeServiceClient) {
	employees := []pb.Employee{
		pb.Employee{
			Id:          200,
			Number:      201,
			FirstName:   "xx",
			LastName:    "xx1",
			MonthSalary: &pb.MonthSalary{
				Basic: 200,
				Bonus: 125.5,
			},
			Status:      pb.EmployeeStatus_NORMAL,
			LastModfied: &timestamppb.Timestamp{
				Seconds: time.Now().Unix(),
			},

		},pb.Employee{
			Id:        300,
			Number:    301,
			FirstName: "asd",
			LastName:  "wefewf",
			MonthSalary: &pb.MonthSalary{
				Basic: 300,
				Bonus: 5.5,
			},
			Status: pb.EmployeeStatus_NORMAL,
			LastModfied: &timestamppb.Timestamp{
				Seconds: time.Now().Unix(),
			},
		},pb.Employee{
			Id:        400,
			Number:    401,
			FirstName: "www",
			LastName:  "wwwwq",
			MonthSalary: &pb.MonthSalary{
				Basic: 4566,
				Bonus: 100,
			},
			Status: pb.EmployeeStatus_NORMAL,
			LastModfied: &timestamppb.Timestamp{
				Seconds: time.Now().Unix(),
			},
		},
	}

	stream,err := client.SaveAll(context.Background())
	if err != nil {
		log.Fatal(err.Error())
	}
	//我们不知道什么时候服务器会把数据发回,我们不能在这阻塞,采用goroutine
	finshChannel := make(chan struct{})
	go func() {
		for {
			res,err := stream.Recv()
			if err == io.EOF {
				finshChannel <- struct{}{}
				break
			}
			if err != nil {
				log.Fatal(err.Error())
			}
			fmt.Println(res.Employee)
		}
	}()

	for _,e := range employees {
		err := stream.Send(&pb.EmployeeRequest{
			Employee: &e,
		})
		if err != nil {
			log.Fatal(err.Error())
		}
	}
	stream.CloseSend()
	<-finshChannel
}

 

server 代码

//双向传送stream
func (s *employeeService)SaveAll(
	stream pb.EmployeeService_SaveAllServer) error {
	for {
		empReq, err := stream.Recv()
		if err == io.EOF {
			break
		}
		if err != nil {
			return err
		}
		employees = append(employees,*empReq.Employee)
		stream.Send(&pb.EmployeeResponse{
			Employee: empReq.Employee,
		})
	}

	for _,emp := range employees {
		fmt.Println(emp)
	}

	return nil
}

 

client

package main

import (
	"context"
	"fmt"
	"go_grpc_client/pb"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"google.golang.org/grpc/metadata"
	"google.golang.org/protobuf/types/known/timestamppb"
	"io"
	"log"
	"os"
	"time"
)


const port = ":5001"
func main() {
	creds, err := credentials.NewClientTLSFromFile("server.pem","www.test.com")
	if err != nil {
		log.Fatal(err.Error())
	}
	//接着设置options
	options := []grpc.DialOption{grpc.WithTransportCredentials(creds)}
	conn, err := grpc.Dial("localhost" + port,options ...)
	if err != nil {
		log.Fatal(err.Error())
	}
	defer conn.Close()
	client := pb.NewEmployeeServiceClient(conn)
	fmt.Println("Client Server started...")
	//getByNo(client)
	//getAll(client)
	//addPhoto(client)
	saveAll(client)
}

func saveAll(client pb.EmployeeServiceClient) {
	employees := []pb.Employee{
		pb.Employee{
			Id:          200,
			Number:      201,
			FirstName:   "xx",
			LastName:    "xx1",
			MonthSalary: &pb.MonthSalary{
				Basic: 200,
				Bonus: 125.5,
			},
			Status:      pb.EmployeeStatus_NORMAL,
			LastModfied: &timestamppb.Timestamp{
				Seconds: time.Now().Unix(),
			},

		},pb.Employee{
			Id:        300,
			Number:    301,
			FirstName: "asd",
			LastName:  "wefewf",
			MonthSalary: &pb.MonthSalary{
				Basic: 300,
				Bonus: 5.5,
			},
			Status: pb.EmployeeStatus_NORMAL,
			LastModfied: &timestamppb.Timestamp{
				Seconds: time.Now().Unix(),
			},
		},pb.Employee{
			Id:        400,
			Number:    401,
			FirstName: "www",
			LastName:  "wwwwq",
			MonthSalary: &pb.MonthSalary{
				Basic: 4566,
				Bonus: 100,
			},
			Status: pb.EmployeeStatus_NORMAL,
			LastModfied: &timestamppb.Timestamp{
				Seconds: time.Now().Unix(),
			},
		},
	}

	stream,err := client.SaveAll(context.Background())
	if err != nil {
		log.Fatal(err.Error())
	}
	//我们不知道什么时候服务器会把数据发回,我们不能在这阻塞,采用goroutine
	finshChannel := make(chan struct{})
	go func() {
		for {
			res,err := stream.Recv()
			if err == io.EOF {
				finshChannel <- struct{}{}
				break
			}
			if err != nil {
				log.Fatal(err.Error())
			}
			fmt.Println(res.Employee)
		}
	}()

	for _,e := range employees {
		err := stream.Send(&pb.EmployeeRequest{
			Employee: &e,
		})
		if err != nil {
			log.Fatal(err.Error())
		}
	}
	stream.CloseSend()
	<-finshChannel
}


func addPhoto (client pb.EmployeeServiceClient) {
	imgFile, err := os.Open("WechatIMG3.jpeg")
	if err != nil {
		log.Fatal(err.Error())
	}
	defer imgFile.Close()
	//metadata相当于报文的header,我们只需要把用户number放在header传输一次就可以了
	md := metadata.New(map[string]string{"number":"1994"})
	context := context.Background()
	context = metadata.NewOutgoingContext(context,md)

	stream, err := client.AddPhoto(context)
	if err != nil {
		fmt.Println(err)
		log.Fatal(err.Error())
	}
	//循环分块传输数据
	for {
		chunk := make([]byte,128*1024)
		chunkSize, err := imgFile.Read(chunk)
		if err == io.EOF {
			break
		}
		if err != nil {
			log.Fatal(err.Error())
		}
		if chunkSize < len(chunk) {
			chunk = chunk[:chunkSize]
		}
		//开始分块发送数据
		stream.Send(&pb.AddPhotoRequest{Data: chunk})

	}
	//closeandrec会向客户端发送一个信号EOF,等待服务端发回一个响应
	res,err := stream.CloseAndRecv()
	if err != nil {
		log.Fatal(err.Error())
	}
	fmt.Println(res.IsOk)
}
func getAll(client pb.EmployeeServiceClient) {
	stream, err := client.GetAll(context.Background(),&pb.GetAllRequest{})
	if err != nil {
		log.Fatal(err.Error())
	}
	for {
		res,err := stream.Recv()
		//如果服务端数据发送结束,则为EOF
		if err == io.EOF {
			break
		}
		if err != nil {
			log.Fatal(err.Error())
		}
		fmt.Println(res.Employee)
	}
}

func getByNo(client pb.EmployeeServiceClient) {
	res, err := client.GetByNo(context.Background(),&pb.GetByNoRequest{Number: 1994})
	if err != nil {
		log.Fatal(err.Error())
	}
	fmt.Println(res.Employee)
}

 

至此完结散花!!!

 

1.go get -u github.com/golang/protobuf/protoc-gen-go
2.go get -u google.golang.org/grpc




protoc ./person.proto --go_out=./
将当前目录下的person.roto转译成go语言放在当前目录
注意在person.proto中写上
option go_package="./;firstpb";
左边表示转译之后存放的位置,右边表示go语言的包名

protoc ./message.proto --go_out=plugins=grpc:../
用plugins工具编译service函数


openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes -subj '/CN=localhost'
设置证书,设置key.pem和cert.pem

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值