问题描述
Go和GRPC都是个新手,所以请忍受。
使用go版本go1.14.4 Windows / amd64,proto3和最新的grpc(我认为是1.31)。我正在尝试建立一个比迪流传输连接,该连接可能会打开更长的时间。一切都在本地工作,除非我终止客户端(或其中一个客户端),它也会杀死服务器,并出现以下错误:
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
函数中的最后一个暂停,以停止可靠地显示恐慌(如您所愿)
因此,一个简单的解决方法可能是简单地创建两个单独的通道(一个用于发送,一个用于接收)并在两个通道上都进行阻止-但是,如果您没有机会进行响应,这必然会使发送循环保持打开状态,因此除非您有充分的理由追求不同的东西,否则最好像上面的示例那样构造。
另一种可能性是服务器/请求上下文混合在一起,但是我敢肯定以上内容会解决-如果在上述更改后仍然有问题,请删除服务器设置代码的更新