我正在构建需要以发布/订阅方式向所有订阅的消费者发送事件的服务。向所有当前连接的客户端发送一个事件。
我正在使用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>,以便以后发送消息。
答案 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
}