grpc 检测客户端连接是否存在
默认情况下,服务端是没有检测客户端连接是否存活的。
如果因为网络抖动,客户端退出,此时客户端会向服务端发送一个Fin_wait2的消息。但这个消息如果丢失,服务端将长期认为客户端“仍然存在”,即使此时客户端已经退出。
为了解决这个问题,grpc服务端在启动的时候,可以传入keepalive参数,原理是:每隔N秒ping客户端,当客户端无法ping通的时候,服务端会主动断开连接。代码如下:
var kasp = keepalive.ServerParameters{
Time: 5 * time.Second, // Ping the client if it is idle for 5 seconds to ensure the connection is still active
Timeout: 1 * time.Second, // Wait 1 second for the ping ack before assuming the connection is dead
}
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer(grpc.KeepaliveParams(kasp))
pb.RegisterHelloServer(s, &svc{})
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
上面的代码表明,每隔5s ping一次客户端,并且回包必须在1s内返回。否则连接将被回收。
服务端的处理代码可以写成这样:
func (s *svc) HelloStream(stream pb.Hello_HelloStreamServer) error {
now := time.Now().Unix()
log.Println("enter stream", now)
req, err := stream.Recv()
if err != nil {
return err
}
name := req.Greetings
loop:
for {
out := new(pb.HelloRsp)
out.Resp = fmt.Sprintf("hello,%v", name)
sendctx, sendcancel := context.WithCancel(context.Background())
go func() {
err := stream.Send(out) // 在协程中send,使得context.Done响应能被处理
if err != nil {
log.Println(err)
}
sendcancel()
}()
select {
case <-sendctx.Done():
case <-stream.Context().Done()://当keepalive连接超时,这里的逻辑被执行,服务端退出
log.Println("client closed")
break loop
}
time.Sleep(time.Second)
}
log.Println("exit stream")
return nil
}
真的隐蔽啊。真的坑啊。
来自 大脸猪 写于 2019-11-26 11:15 -- 更新于2020-10-19 13:06 -- 0 条评论