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 条评论