如何在Golang GRPC

时间:2019-10-29 20:09:19

标签: go streaming grpc

我正在构建需要以发布/订阅方式向所有订阅的消费者发送事件的服务。向所有当前连接的客户端发送一个事件。

我正在使用Protobuf,它具有以下原型定义:

service EventsService {
  rpc ListenForEvents (AgentProcess) returns (stream Event) {}
}

服务器和客户端都是用Go编写的。

我的问题是,当客户端启动连接时,它的流不是持久的,例如。服务器从ListenForEvents方法返回时:

func (e EventsService) ListenForEvents(process *pb.AgentProcess, listener pb.EventsService_ListenForEventsServer) error {
    //persist listener here so it can be used later when backend needs to send some messages to client

    return nil
}

然后客户端几乎立即会收到EOF错误,这意味着服务器可能关闭了连接。

我该怎么做才能使客户端长时间订阅服务器? 主要问题是,当客户端在服务器上调用ListenForEvents方法时,我可能没有什么可发送给客户端的文件,这就是为什么我希望此流长期存在的原因< / strong>,以便以后发送消息。

3 个答案:

答案 0 :(得分:0)

从服务器功能返回时,流终止。相反,您应该以某种方式接收事件,然后将其发送到客户端,而不从服务器返回。您可能有很多方法可以执行此操作。下面是一种实现方法的示意图。

这取决于在单独的goroutine上运行的服务器连接。有一个Broadcast()函数,它将向所有连接的客户端发送消息。看起来像这样:

var allRegisteredClients map[*pb.AgentProcess]chan Message
var clientsLock sync.RWMutex{}

func Broadcast(msg Message) {
  clientsLock.RLock()
  for _,x:=range allRegisteredClients {
      x<-msg
  }
  clientsLock.RUnlock()
}

然后,您的客户必须注册自己并处理消息:

func (e EventsService) ListenForEvents(process *pb.AgentProcess, listener pb.EventsService_ListenForEventsServer) error {
   clientsLock.Lock()
   ch:=make(chan Message)
   allRegisteredClients[process]=ch
   clientsLock.Unlock()

   for msg:=range ch {
       // send message
       // Deal with errors
       // Deal with client terminations
   }
   clientsLock.Lock()
   delete(allRegisteredClients,process)
   clientsLock.Unlock()
}

正如我所说的,这只是一个想法的草图。

答案 1 :(得分:0)

您需要为grpc客户端和服务器提供keepalive设置

答案 2 :(得分:0)

我设法弄清楚了。

基本上,我从不从方法ListenForEvents返回。 它创建了频道,并在订阅者的全局地图中保持存在,并无限期地从该频道中进行读取。

服务器逻辑的整个实现:

func (e EventsService) ListenForEvents(process *pb.AgentProcess, listener pb.EventsService_ListenForEventsServer) error {
    chans, exists := e.listeners[process.Hostname]

    chanForThisClient := make(chan *pb.Event)

    if !exists {
        e.listeners[process.Hostname] = []chan *pb.Event{chanForThisClient}
    } else {
        e.listeners[process.Hostname] = append(chans, chanForThisClient)
    }

    for {
        select {
        case <-listener.Context().Done():
            return nil
        case res := <-chanForThisClient:
            _ = listener.Send(res)
        }
    }

    return nil
}