视频地址: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.youkuaiyun.com/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种:
- 第一种是一元的消息,就是简单的请求响应
- 第二种是server streaming(流),server会把数据streaming回给client(streaming是把数据分成块传输)
- 第三种是client streaming,也就是client会把数据streamin给server
- 第四种是双向的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: ×tamppb.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: ×tamppb.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: ×tamppb.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: ×tamppb.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: ×tamppb.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: ×tamppb.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