繁体   English   中英

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

[英]How to broadcast in gRPC from server to client?

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

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

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

是的,除了保持包含所有已连接流的全局数据结构并遍历它们的全局数据结构,并告诉它们刚刚发生的偶数,我没有其他方法。

另一种选择是使用长轮询方法。 可以像下面这样尝试(Python代码,因为这是我最熟悉的,但是go应该非常相似)。 这未经测试,只是为了让您了解如何在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

需要一个全局的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 go例程:

//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 business logic, send the message to suiteable conn in the map as you like
            case EventEnum.ReceiveResponse:
                ...
        }
    }
}

在这里放一些细节

Go示例中使用 gRPC 实现的简单聊天服务器/客户端

所有客户端都存储在map[string]chan *chat.StreamResponse

type server struct {
    Host, Password string

    Broadcast chan *chat.StreamResponse

    ClientNames   map[string]string
    ClientStreams map[string]chan *chat.StreamResponse

    namesMtx, streamsMtx sync.RWMutex
}

并向所有客户端广播消息

func (s *server) broadcast(_ context.Context) {
    for res := range s.Broadcast {
        s.streamsMtx.RLock()
        for _, stream := range s.ClientStreams {
            select {
            case stream <- res:
                // noop
            default:
                ServerLogf(time.Now(), "client stream full, dropping message")
            }
        }
        s.streamsMtx.RUnlock()
    }
}

// send messages in individual client
func (s *server) sendBroadcasts(srv chat.Chat_StreamServer, tkn string) {
    stream := s.openStream(tkn)
    defer s.closeStream(tkn)

    for {
        select {
        case <-srv.Context().Done():
            return
        case res := <-stream:
            if s, ok := status.FromError(srv.Send(res)); ok {
                switch s.Code() {
                case codes.OK:
                    // noop
                case codes.Unavailable, codes.Canceled, codes.DeadlineExceeded:
                    DebugLogf("client (%s) terminated connection", tkn)
                    return
                default:
                    ClientLogf(time.Now(), "failed to send to client (%s): %v", tkn, s.Err())
                    return
                }
            }
        }
    }
}

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

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

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

缺点:

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM