视频地址:https://www.bilibili.com/video/BV1eE411T7GC?p=18
创建相应的目录
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
-
}
-
-
创建相应的目录文件,并存放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.
-
解决:问题原因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的消息传输类型有4种:
- 第一种是一元的消息,就是简单的请求响应
- 第二种是server streaming(流),server会把数据streaming回给client(streaming是把数据分成块传输)
- 第三种是client streaming,也就是client会把数据streamin给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)
-
}
-
}
采用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...)
-
}
-
}
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
转载:https://blog.csdn.net/leekerian/article/details/115654055