代码地址
超时取消
https://github.com/wanmei002/grpc-learn/tree/master/ch06
单方面取消
https://github.com/wanmei002/grpc-learn/tree/master/ch07
简单介绍
取消请求又分超时取消请求 和 单方面的主动取消。取消主要是用 context.Context[golang版] 来远程传递取消操作。可以用
grpc 的 status.Code() 来获取错误码,不同的错误码对应不同的错误。完整的错误码列表可以在gRPC官方文档查看;或者看 grpc/codes/codes.go 源码里有错误码的描述
错误码1 | 数字 | 描述 |
---|---|---|
OK | 0 | 成功 |
CANCELLED | 1 | 操作已被(调用者)取消 |
DEADLINE_EXCEEDED | 4 | 在操作完成前,就已超过了截止时间 |
在客户端请求中传递context
的时候,请求会监测 context
的状态:
现在我们直接上代码吧
protobuffer 定义
// 简单模拟两个人对话
service ChatSvr {
rpc SendMessage(stream MsgInfo) returns (stream MsgInfo);
}
message MsgInfo {
uint64 ChatRoomID = 1;
uint64 UserID = 2;
string UserName = 3;
string UserHeadImg = 4;
string Msg = 5;
string Ext = 6;
}
客户端代码模拟的是聊天室
main函数代码
func main(){
input := os.Args
say := "你好"
if len(input) >= 2 {
say = input[1]
}
// grpc 自带的拨号连接,采用http2
d, err := grpc.Dial(":8093", grpc.WithInsecure())
if err != nil {
log.Println("grpc dial failed; err:", err)
return
}
defer d.Close()
client := chat.NewChatSvrClient(d)
// 创建可以取消的上下文
ctx, cancel := context.WithCancel(context.Background())
clientStream, err := client.SendMessage(ctx)
if err != nil {
log.Println("get client stream failed; err:", err)
return
}
msg := &chat.MsgInfo{ChatRoomID:1,UserID:4567,UserName:"zyn",UserHeadImg:"love.jpg",Msg:say}
fmt.Println("zyn: ", say)
err = clientStream.Send(msg)
if err != nil {
log.Println("client send data failed; err:", err)
return
}
sendMsgTime := new(int64)
*sendMsgTime = time.Now().Unix()
// 起一个 goroutine 来接收客户端发送的数据
go getRecvMsg(clientStream)
ch := make(chan struct{})
// 起一个 goroutine 来检查是否长时间未通话,未通话关闭连接
go heartBeat(sendMsgTime, ch)
// 起一个 goroutine 来发送数据
go sendMsg(clientStream, ctx, sendMsgTime)
// 阻塞,上面检查心跳可以给这个chan赋值,来取消阻塞
<- ch
// 长时间未通话,关闭 grpc 连接
cancel()
time.Sleep(1e9)
log.Println("client end")
}
其它go协程代码
代码比较粗糙,大家简单的看下
// 客户端发送数据
func sendMsg(clientStream chat.ChatSvr_SendMessageClient, ctx context.Context, sendMsgTime *int64) {
for {
select {
case <-ctx.Done(): // 检查是否关闭了连接
return
default:
err := client2svr(clientStream, sendMsgTime)
if err != nil {
log.Println("send msg failed; err:", err)
return
}
}
}
}
func client2svr(clientStream chat.ChatSvr_SendMessageClient, sendMsgTime *int64) error {
say := ""
fmt.Scanf("%s\n", &say)
if say == "over" {
clientStream.CloseSend()
fmt.Printf(" 通话结束 \n")
return nil
}
msg := &chat.MsgInfo{ChatRoomID:1,UserID:4567,UserName:"zyn",UserHeadImg: "love.jpg",Msg:say}
msg.Msg = say
err := clientStream.Send(msg)
if err != nil {
log.Println("send msg failed; err:", err)
return err
}
*sendMsgTime = time.Now().Unix()
return nil
}
// 检查多长时间未通话了
func heartBeat(t *int64, ch chan struct{}) {
defer func(){
ch <- struct{}{}
}()
for {
time.Sleep(time.Second)
nowTime := time.Now().Unix()
sub := nowTime - *t
// 超过 3 秒 关闭连接
if sub > 3 {
log.Println("长时间没有通话了[自动关闭连接]")
return
}
}
}
func getRecvMsg(client chat.ChatSvr_SendMessageClient) {
for {
msg, err := client.Recv()
if err != nil {
if err == io.EOF {
fmt.Println("END")
return
} else if status.Code(err) == codes.Canceled {// 获取 grpc 错误码状态,看是不是对方关闭了连接
log.Println("服务端已经关闭了请求")
return
}
log.Println("client recv failed; err:", err)
return
}
fmt.Println(" ", msg.Msg, " :", msg.UserName)
}
}
服务端代码
type server struct {}
func (s *server) SendMessage(recvStream chat.ChatSvr_SendMessageServer) error {
// for 循环接收客户端发送的信息
for {
msgInfo, err := recvStream.Recv()
if err != nil {
if err == io.EOF {
log.Println("客户端请求结束发送")
return nil
} else if status.Code(err) == codes.Canceled {// 获取错误码状态,看是否是客户端关闭了连接
log.Println("客户端取消了连接")
// 返回给客户端 grpc 错误码
return status.Error(codes.Canceled, "server closed")
}
log.Println("recv failed; err:", err)
return err
}
sendMsg := &chat.MsgInfo{ChatRoomID:msgInfo.ChatRoomID,UserID:123,UserName:"zzh",UserHeadImg:"img.jpg",Msg:""}
switch msgInfo.Msg {
case "你好":
sendMsg.Msg = "你好"
err = recvStream.Send(sendMsg)
case "你在哪呢":
sendMsg.Msg = "在家里呢"
err = recvStream.Send(sendMsg)
default:
sendMsg.Msg = "没听清, 你再说一次"
err = recvStream.Send(sendMsg)
}
if err != nil {
log.Println("send msg failed; err : ", err)
return err
}
}
}