如何在gRPC中从服务器广播到客户端?

时间:2018-03-30 20:09:48

标签: go redis publish-subscribe grpc http2

我现在正在gRPC中创建一个小型聊天应用程序而且我遇到了如果用户想要作为客户端连接到gRPC服务器的问题,我想要广播事件发生在所有其他连接的客户端上。

我正在考虑使用某种观察者,但我对服务器如何知道谁连接以及如何将事件广播给所有客户而不仅仅是一两个人感到困惑。 / p>

我知道使用流是答案的一部分,但由于每个客户端都在创建自己的服务器流,我不确定它如何订阅其他服务器客户端流。

4 个答案:

答案 0 :(得分:2)

另一种选择是使用长轮询方法。这是尝试下面的东西(Python中的代码,因为这是我最熟悉的,但是应该非常相似)。这没有经过测试,只是为了让您了解如何在gRPC中进行长轮询:

.PROTO defs
-------------------------------------------------
service Updater {
    rpc GetUpdates(GetUpdatesRequest) returns (GetUpdatesResponse);
}

message GetUpdatesRequest {
    int64 last_received_update = 1;
}

message GetUpdatesResponse {
    repeated Update updates = 1;
    int64 update_index = 2;
}

message Update {
    // your update structure
}


SERVER
-----------------------------------------------------------
class UpdaterServer(UpdaterServicer):
    def __init__(self):
        self.condition = threading.Condition()
        self.updates = []

    def post_update(self, update):
        """
        Used whenever the clients should be updated about something. It will
        trigger their long-poll calls to return
        """
        with self.condition:
            # TODO: You should probably remove old updates after some time
            self.updates.append(updates)
            self.condition.notify_all()

    def GetUpdates(self, req, context):
        with self.condition:
            while self.updates[req.last_received_update + 1:] == []:
                self.condition.wait()
            new_updates = self.updates[req.last_received_update + 1:]
            response = GetUpdatesResponse()
            for update in new_updates:
                response.updates.add().CopyFrom(update)
            response.update_index = req.last_received_update + len(new_updates)
            return response


SEPARATE THREAD IN THE CLIENT
----------------------------------------------
request = GetUpdatesRequest()
request.last_received_update = -1
while True:
    stub = UpdaterStub(channel)
    try:
        response = stub.GetUpdates(request, timeout=60*10)
        handle_updates(response.updates)
        request.last_received_update = response.update_index
    except grpc.FutureTimeoutError:
        pass

答案 1 :(得分:1)

是的,我没有看到任何其他方式,除了保持包含所有连接流的全局数据结构并循环遍历它们,告诉每个人关于刚刚发生的偶数。

答案 2 :(得分:0)

另一种方法是在客户端也生成一个grpc服务器。在应用程序级别,您需要从客户端到服务器进行一些握手,以交换客户端grpc-server ip和端口。此时,您可能想为此地址创建一个客户端,并将该客户端存储在列表中。

现在,您可以使用默认的一元RPC调用从列表将消息推送到客户端。不需要[bidi]流。 优点:

  • 可以将客户端“推送” -API与服务器API分开。
  • 一元RPC推送调用。

缺点:

  • 其他“服务器”。不知道在每种情况下是否有可能。

答案 3 :(得分:0)

需要一个全局的 map 结构,您可以为每个连接创建一个新的 chan。我想出的是一个处理全局 map 结构的中间渠道。

服务器流的示例:

func (s *server) Subscribe(req *pb.SubscribeRequest, srv pb.SubscribeServer) error {
    //get trace id or generated a random string or whatever you want to indicate this goroutine
    ID:="randomString"
    //create a chan to receive response message
    conn := make(chan *pb.SubscribeResponse)
    //an intermediate channel which has the ownership of the `map`
    s.broadcast <- &broadcastPayload {
        //an unique identifier
        ID: ID
        //the chan corresponse to the ID
        Conn: conn
        //event to indicate add, remove or send message to broadcast channel
        Event: EventEnum.AddConnection
    }
    
    for {
        select {  
            case <-srv.Context().Done():  
                s.broadcast <- &entity.BroadcastPayload{  
                     ID: ID,
                     Event: EventEnum.RemoveConnection
                }
                return nil  
            case response := <-conn:  
                if status, ok := status.FromError(srv.Send(response)); ok {  
                    switch status.Code() {  
                    case codes.OK:  
                        //noop  
                    case codes.Unavailable, codes.Canceled, codes.DeadlineExceeded:  
                        return nil  
                    default:  
                        return nil  
             }  
         }}
    }
}

对于 broadcast 去例程:

//this goroutine has the ownership of the map[string]chan *pb.SubscribeResponse
go func(){
    for v:=range s.broadcast {
        //do something based on the event
        switch v.Event {
            //add the ID and conn to the map
            case EventEnum.AddConnection:
                ...
            //delete map key and close conn channel here
            case EventEnum.RemoveConnection:
                ...
            //receive message from bussiness logic, send the message to suiteable conn in the map as you like
            case EventEnum.ReceiveResponse:
                ...
        }
    }
}

我放了一些细节here