簡體   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