第二章——server端几种server服务的学习
2.1、创建proto文件
在proto文件夹下创建person.proto文件 在文件中定义两个message,一个是PersonReq,另一个是PersonRes。这两个消息里有两个内容strnig name与int32 age
message PersonReq{
string name= 1 ;
int32 age= 2 ;
}
message PersonRes{
string name= 1 ;
int32 age= 2 ;
}
定义四种service
1对1应答服务,rpc Search1(PersonReq) returns (PersonRes);
1对n流式服务,rpc Search2(stream PersonReq) returns (PersonRes);
n对1流式服务,rpc Search3(PersonReq) returns (stream PersonRes);
n对n流式服务,rpc Search4(stream PersonReq) returns (stream PersonRes);
person.proto全文
syntax = "proto3";
option go_package="../pb;pb";
message PersonReq{
string name=1;
int32 age=2;
}
message PersonRes{
string name=1;
int32 age=2;
}
service Searchservice{
rpc Search1(PersonReq) returns (PersonRes);
rpc Search2(stream PersonReq) returns (PersonRes);
rpc Search3(PersonReq) returns (stream PersonRes);
rpc Search4(stream PersonReq) returns (stream PersonRes);
}
在工程目录下使用命令:protoc --proto_path=proto --go_out=pb --go-grpc_out=pb proto/*.proto
生成pb文件
2.2、编写server端
重新定义server文件夹下的main.go文件 定义一个结构体personServer,里面包含UnimplementedSearchserviceServer
type personServer struct {
pb. UnimplementedSearchserviceServer
}
实现1对1应答服务
func ( p * personServer) Search1 ( ctx context. Context, req * pb. PersonReq) ( res * pb. PersonRes, err error ) {
name := req. GetName ( )
age:= req. GetAge ( )
fmt. Printf ( "Get message from client:name=%s,age=%d\n" , name, age)
res = & pb. PersonRes{
Name: "tiansiben" ,
Age: 12 ,
}
return res, nil
}
简略实现流式服务
func ( p * personServer) Search2 ( pb. Searchservice_Search2Server) error {
return nil
}
func ( p * personServer) Search3 ( req * pb. PersonReq, server3 pb. Searchservice_Search3Server) error {
return nil
}
func ( p * personServer) Search4 ( pb. Searchservice_Search4Server) error {
return nil
}
创建main方法,创建地址监听以及服务调用
func main ( ) {
listen, _ := net. Listen ( "tcp" , ":8888" )
s:= grpc. NewServer ( )
pb. RegisterSearchserviceServer ( s, & personServer{ } )
s. Serve ( listen)
}
2.3、编写client端实现1对1服务交流
func main ( ) {
conn, _ := grpc. Dial ( "localhost:8888" , grpc. WithInsecure ( ) )
defer conn. Close ( )
client := pb. NewSearchserviceClient ( conn)
res, _ := client. Search1 ( context. Background ( ) , & pb. PersonReq{ Name: "client" , Age: 24 } )
name := res. GetName ( )
age := res. GetAge ( )
fmt. Printf ( "Get message from serivce:name=%s,age=%d\n" , name, age)
}
2.4、编写server端实现流失输入
2.4.1、完善server段流式传入代码
将Search2函数的接受参数命名为server 函数体中使用server.Recv方法接受客户端传过来的流 由于是流式传入,需要使用for进行无限循环调用Recv方法进行接受,使用函数返回值err判断当err!=nil时流传输完成,使用server.SendAndClose()方法,传入一个回复信息并关闭此次服务
func ( p * personServer) Search2 ( server pb. Searchservice_Search2Server) error {
for {
req, err:= server. Recv ( )
fmt. Println ( req)
if err!= nil {
server. SendAndClose ( & pb. PersonRes{ Name: "服务端完成了流式传入" } )
break
}
}
return nil
}
2.4.2、完善client端代码
建立端口监听以及获取客户端代码不变 使用client.Search2(context.Background())
获取流式传入客户端 利用for循环十次,向服务端发送十次消息,用于模拟一次流式传入 之后使用search2Client.CloseAndRecv()
用于接收服务端传回来的结束消息
func main ( ) {
conn, _ := grpc. Dial ( "localhost:8888" , grpc. WithInsecure ( ) )
defer conn. Close ( )
client := pb. NewSearchserviceClient ( conn)
search2Client, _ := client. Search2 ( context. Background ( ) )
for i:= 0 ; i< 10 ; i++ {
search2Client. Send ( & pb. PersonReq{ Name: "客户端信息" } )
}
res, _ := search2Client. CloseAndRecv ( )
fmt. Println ( res)
}
2.5、实现流失输出
2.5.1、编写server端
server端将接收到的请求接收提取出name 循环十次发送接收到的name,模拟流式输出
func ( p * personServer) Search3 ( req * pb. PersonReq, server3 pb. Searchservice_Search3Server) error {
name := req. Name
for i := 0 ; i < 10 ; i++ {
server3. Send ( & pb. PersonRes{ Name: "我拿到了" + name} )
}
return nil
}
2.5.2、编写client
建立端口监听以及获取客户端代码不变 使用client.Search3方法获取到流式输出的client,其中需要传两个参数,一个是context.Background(),另一个是PersonReq结构体 循环接收服务端传过来的流式输出,用err!=ni
l判断是否传输完毕
func main ( ) {
conn, _ := grpc. Dial ( "localhost:8888" , grpc. WithInsecure ( ) )
defer conn. Close ( )
client := pb. NewSearchserviceClient ( conn)
search3Client, _ := client. Search3 ( context. Background ( ) , & pb. PersonReq{ Name: "this is client" } )
for {
req, err := search3Client. Recv ( )
fmt. Println ( req)
if err != nil {
break
}
}
}
2.6、实现n对n流失传输
2.6.1、编写server端
server端首先接收客户端传过来的流失消息;server端还可以用流形式返回给客户端一些消息 由于接受与发送都是流式,需要循环使用接受与发送,所以为了不妨碍彼此的运行,这里将接收以新线程并发形式与发送一起执行 首先创建一个通道用于在接收与发送两个线程间传递字符串:str:=make(chan string)
在使用go func(){}()形式,用匿名函数包装接收并以新线程启动匿名函数
go func ( ) {
for {
req, err:= server4. Recv ( )
if err!= nil {
str<- "end"
break
}
str<- req. Name
}
} ( )
主线程中循环发送子线程接收到的str
for {
s := <- str
if s == "end" {
break
}
server4. Send ( & pb. PersonRes{ Name: s} )
}
完整代码
func ( p * personServer) Search4 ( server4 pb. Searchservice_Search4Server) error {
str := make ( chan string )
go func ( ) {
for {
req, err := server4. Recv ( )
if err != nil {
str <- "end"
break
}
str <- req. Name
}
} ( )
for {
s := <- str
if s == "end" {
break
}
server4. Send ( & pb. PersonRes{ Name: s} )
}
return nil
}
func main ( ) {
listen, _ := net. Listen ( "tcp" , ":8888" )
s := grpc. NewServer ( )
pb. RegisterSearchserviceServer ( s, & personServer{ } )
s. Serve ( listen)
}
2.6.2、编写client
建立监听与获取客户端代码不变 使用client.Search4(context.Background())
获取对应服务客户端 使用线程执行匿名函数的接受操作 主函数循环发送操作
func main ( ) {
conn, _ := grpc. Dial ( "localhost:8888" , grpc. WithInsecure ( ) )
defer conn. Close ( )
client := pb. NewSearchserviceClient ( conn)
search4Client, _ := client. Search4 ( context. Background ( ) )
go func ( ) {
for {
req, err := search4Client. Recv ( )
fmt. Println ( req)
if err != nil {
break
}
}
} ( )
for {
err:= search4Client. Send ( & pb. PersonReq{ Name: "this is client" } )
if err!= nil {
break
}
}
}