【问题标题】:Go GRPC client disconnect terminates Go serverGo GRPC 客户端断开连接终止 Go 服务器
【发布时间】:2020-09-09 23:04:52
【问题描述】:

对于 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)的帖子,没有其他人遇到这个问题。相反,这个问题更流行的版本是“我的客户端流在断开连接后保持打开状态”。我期待这个问题。不是这个。

【问题讨论】:

    标签: go network-programming grpc-go


    【解决方案1】:

    我不是 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 func 中的最后一个暂停以停止可靠地显示恐慌(如您的情况)

    因此,一个简单的解决方法可能是简单地创建两个单独的通道(一个用于发送,一个用于接收)并阻止两者 - 但是,如果您没有机会响应,这会使发送循环必然打开除非你有充分的理由去追求不同的东西,否则按照上面的例子可能会更好。

    另一种可能性是某种服务器/请求上下文混淆了,但我很确定上述问题会解决 - 如果在上述更改后您仍然遇到问题,请使用您的服务器设置代码进行更新

    【讨论】:

    • 我从来没有真正理解为什么在大多数示例中发送和接收都在一个 go-routine 中。这是否意味着你不能发送任何东西,除非你先接收一些东西?我希望客户端(它也有单独的发送和接收的 go-routines)和服务器在它准备好时发送数据,而不管它是否被要求。我对退出通道的处理可能是错误的。但老实说,我尝试过不使用频道,使用从不开放的频道,使用返回而不是休息,这没关系。这些似乎都不会影响真正的问题。
    • 在收到请求之前,您不能发送任何内容,因此您永远不会在未从客户端收到任何内容的情况下发送(即使是空请求消息)...一旦收到初始请求(并且您在客户端上有句柄)您可以向客户端发送多个回复和未经请求的消息,您只需要考虑如何构建代码来实现这一点 - 它很可能最终会出现多个循环 - 正如我所说你所做的不一定是一个坏方法,只是非典型的并且没有完全正确实施,这就是我想解释这个问题的原因:)
    • 正如我所说,2 个单独的通道或将通道容量设为 2 并等待两条消息应该会阻止您的示例崩溃以快速获胜,尽管如果您不这样做,这可能会使其挂起确保两个通道始终关闭 另请注意:该示例是一个聊天应用程序,它可以以非常双向的方式发送和接收 - 通道是 go 中最大的 PITA,也是它的最佳功能
    • 是的,我也试过了。我将服务器端代码缩减为该示例中的内容 - 没有 go 例程,recv() 和 send() 在一个 for 循环中一起使用。相同的行为 - 启动服务器,启动客户端(或两个或三个)关闭一个连接的客户端,服务器崩溃,其他客户端终止。问题似乎是终止客户端取消了服务器端流上下文,但服务器仍尝试从中读取。我尝试使用服务器处理程序的 HandleConn 函数中的门来保护它,但这只会一次又一次地起作用 - 当我在 for 循环停止在 recv 之前捕获它时。
    • 您能否发布更新的服务端点以及您设置和启动 grpc 服务器的位置。如果不是通道问题,则可能是服务器上下文混淆。有一个服务器级别上下文和一个请求级别上下文 - 您希望服务器级别取消请求级别(用于正常关闭)但听起来可能以某种方式取消请求正在取消服务器上下文
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-06-05
    • 2020-06-02
    • 2020-09-23
    • 2020-06-25
    • 2017-02-11
    • 2018-07-04
    • 1970-01-01
    相关资源
    最近更新 更多