我现在正在gRPC中创建一个小型聊天应用程序而且我遇到了如果用户想要作为客户端连接到gRPC服务器的问题,我想要广播事件发生在所有其他连接的客户端上。
我正在考虑使用某种观察者,但我对服务器如何知道谁连接以及如何将事件广播给所有客户而不仅仅是一两个人感到困惑。 / p>
我知道使用流是答案的一部分,但由于每个客户端都在创建自己的服务器流,我不确定它如何订阅其他服务器客户端流。
答案 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]流。 优点:
缺点:
答案 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