Go GRPC客户端断开连接会终止Go服务器

问题描述

Go和GRPC都是个新手,所以请忍受。

使用go版本go1.14.4 Windows / amd64,proto3和最新的grpc(我认为是1.31)。我正在尝试建立一个比迪流传输连接,该连接可能会打开更长的时间。一切都在本地工作,除非我终止客户端(或其中一个客户端),它也会杀死服务器,并出现以下错误

无法交易数据rpc错误代码=取消desc =上下文已取消

错误来自此代码服务器端

func (s *exchangeserver) Trade(stream proto.ExchageService_TradeServer) error {

    endchan := make(chan int)
    defer close(endchan)

    go func() {
        for {
            req,err := stream.Recv()
            if err == io.EOF {
                break
            }
            if err != nil {
                log.Fatal("Unable to Trade data ",err)
                break
            }

            fmt.Println("Got ",req.GetNumber())
        }

        endchan <- 1
    }()

    go func() {
        for {
            resp := &proto.WordResponse{Word: "Hello again "}
            err := stream.Send(resp)
            if err != nil {
                log.Fatal("Unable to send from server ",err)
                break
            }

            time.Sleep(time.Duration(500 * time.Millisecond))
        }

        endchan <- 1
    }()

    <-endchan
    return nil
}

Trade()RPC非常简单,因此不值得发布.proto。 该错误显然是从Recv()调用中发出的,但是该调用会阻塞,直到它看到一条消息,例如客户端断开连接,这时我希望它杀死流,而不是整个进程。我试过用HandleConn(context,stats.ConnStats)添加一个服务处理程序,它确实在服务器死掉之前捕获了断开连接,但是我不能做任何事情。我什至尝试创建一个全局通道,在调用HandleRPC(context,stats.RPCStats)时,服务处理程序将值推入该通道,并且仅允许在通道中有值的情况下调用Recv(),但不能是的,这就像为了安全起见阻止了阻止功能,反​​正还是行不通。

这必须是初学者犯下的那些真正愚蠢的错误之一。如果GPRC无法处理不中断的客户端断开连接,它将有什么用?但是我已经从互联网的各个角落阅读了大约一万亿(ish)的帖子,而没有其他人遇到这个问题。相反,此问题的比较流行的版本是“断开连接后,我的客户端流保持打开状态”。我希望那个问题。不是这个。

解决方法

我不是100%知道应该如何运行,但是我注意到您正在同时启动单独的接收和发送goroutine。这可能是有效的,但不是典型的方法。相反,您通常会收到要处理的内容,然后启动嵌套循环来处理回复。

从此处查看典型的双向流式传输示例:https://grpc.io/docs/languages/go/basics/

func (s *routeGuideServer) RouteChat(stream pb.RouteGuide_RouteChatServer) error {
    for {
        in,err := stream.Recv()
        if err == io.EOF {
            return nil
        }
        if err != nil {
            return err
        }
        key := serialize(in.Location)
                ... // look for notes to be sent to client
        for _,note := range s.routeNotes[key] {
            if err := stream.Send(note); err != nil {
                return err
            }
        }
    }
}

同时发送和接收可能对您的用例有效,但是如果您要这样做,那么我认为您对频道的处理不正确。无论哪种方式,请继续阅读以了解该问题,因为它是常见的问题。

您拥有一个通道,该通道仅阻塞直到接收到一条消息为止,一旦解除阻塞,函数结束并且该通道关闭(通过延迟)。

您正尝试通过发送和接收发送到该频道 循环。

当最后一个尝试发送到频道时,它将被关闭(第一个完成),并且服务器将出现紧急情况。令人讨厌的是,您实际上看不到任何迹象,因为服务器将在goroutine转储其恐慌之前退出(没有任何线索-可能是您登陆此处的原因)

在此处查看问题的示例(删除了grpc代码): https://play.golang.org/p/GjfgDDAWNYr 注意:请注释掉main函数中的最后一个暂停,以停止可靠地显示恐慌(如您所愿)

因此,一个简单的解决方法可能是简单地创建两个单独的通道(一个用于发送,一个用于接收)并在两个通道上都进行阻止-但是,如果您没有机会进行响应,这必然会使发送循环保持打开状态,因此除非您有充分的理由追求不同的东西,否则最好像上面的示例那样构造。

另一种可能性是服务器/请求上下文混合在一起,但是我敢肯定以上内容会解决-如果在上述更改后仍然有问题,请删除服务器设置代码的更新