How to broadcast in gRPC from server to client? - go

I'm creating a small chat application in gRPC right now and I've run into the issue where if a user wants to connect to the gRPC server as a client, I'd like to broadcast that the event has occurred to all other connected clients.
I'm thinking of using some sort of observer but I"m confused as to how the server knows of who is connected and how I would broadcast the event to all clients and not just one or two.
I know using streams is part of the answer, but because each client is creating it's own stream with the server, I'm unsure of how it can subscribe to other server-client streams.

Another option would be to use a long-polling approach. That is try something like below (code in Python, since that is what I'm most familiar with, but go should be very similar). This was not tested, and is meant to just give you an idea of how to do long-polling in 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

Yup, I don't see any other way than keeping a global data structure containing all the connected streams and looping through them, telling each about the even that just occurred.

Another approach is to spawn a grpc-server on client side too. On app-level you have some handshake from client to server to exchange the clients grpc-server ip and port. You probably want to create a client for that address at this point and store the client in a list.
Now you can push messages to the clients from the list with default unary RPC calls. No [bidi] stream needed.
Pros:
Possible to separate the clients "Push"-API from the server API.
Unary RPC push calls.
Cons:
Additional "server". Don't know if that is possible in every scenario.

A global map structure is needed, you can create a new chan for each connection. What I come up with is an intermediate channel to deal with the global map structure.
An example for server streaming:
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
}
}}
}
}
For the broadcast go routine:
//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:
...
}
}
}
I put some details here

A simple chat server/client implemented with gRPC in Go sample
All clients are stored in the 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
}
And broadcast messages to all clients
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
}
}
}
}
}

Related

How to communicate between a http request handler and websocket server using gin and gorilla/websocket?

I want to create a realtime api. Github Link of current code
There will be two types of users A and B.
Users A can connect to the service using a websocket to see realtime Updates.
Users B can only make http request to push data to mongodb. I have been following this tutorial. In this tutorial sqlitedb and redis pub/sub is used but i don't want to use that.
slotServer.go
...
func (server *WsServer) Run() {
gb := *models.GetInstanceGlobal()
for {
select {
case client := <-server.register:
server.registerClient(client)
case client := <-server.unregister:
server.unregisterClient(client)
case message := <-server.broadcast:
server.broadcastToClient(message)
case message := <-gb.Channel:
server.createRoomAndBroadCast(message)
}
}
}
...
func (server *WsServer) createRoom(name string) *Room {
room := NewRoom(name)
go room.RunRoom()
server.rooms[room] = true
return room
}
func (server *WsServer) createRoomAndBroadCast(name string) {
room := server.createRoom(name)
var roomList []string
for r := range server.rooms {
roomList = append(roomList, r.GetName())
}
addRoomReply := models.AddRoomReply{
RoomList: roomList,
AddedRoom: room.GetName(),
}
addReply := MessageAddRoom{
Data: addRoomReply,
Action: "room-add-success",
}
server.broadcast <- addReply.encode()
}
I am trying to a listen on channel global. If a string is pushed to it, a function createRoomAndBroadCast will be called.
models.go
....
type GlobalChannel struct {
Channel chan string
}
func GetInstanceGlobal() *GlobalChannel {
return &GlobalChannel{
Channel: make(chan string),
}
}
I am writing to this channel in POST message handler
room.go
...
//add room to mongo
if err := db.CreateRoom(&room); err != nil {
ctx.JSON(http.StatusBadGateway, gin.H{
"data": err,
})
return
}
//write it to channel
gb := models.GetInstanceGlobal()
gb.Channel <- *room.Name
// send reponse to user
ctx.JSON(http.StatusCreated, gin.H{
"data": "Room Created Successfully",
})
...
But my post request gets stuck at line gb := models.GetInstanceGlobal()
In the logs I see the following message
redirecting request 307: /api/v1/room/ --> /api/v1/room/
I don't understand whether I am doing something wrong or my logic is completely wrong.
I started reading about how channels work in golang and thanks to this post found that mentioning buffer limit in a channel is very important.
If ch is unbuffered, then ch <- msg will block until the message is consumed by a receiver
So I changed
models.go
//Channel: make(chan string),
Channel: make(chan string,100),
and it started transmitting the messages.
I'm only going to comment on the code you have shown here and not the overall design.
func GetInstanceGlobal() *GlobalChannel {
return &GlobalChannel{
Channel: make(chan string),
}
}
This is likely a misunderstanding of channels. While you have named it GetInstanceGlobal it is actually returning a brand new channel each time you call it. So if one side is calling it and then trying to receive messages and the other is calling it and pushing messages, the two sides will be different channels and never communicate. This explains why your push is blocking forever. And it also explains why when you add 100 to make it a buffered channel that appears to unblock it. But really all you have done is let it build up to 100 queued messages on the push side and eventually block again. The receiver is still on its own channel.
Likely what the code implies is that you wanted to create the global once, return it, and share it. I'm not going to get into the design issues with globals here. But it might look like this for your case:
var globalChannel *GlobalChannel
func GetInstanceGlobal() *GlobalChannel {
if globalChannel == nil {
globalChannel = &GlobalChannel{
Channel: make(chan string),
}
}
return globalChannel
}

Do we need to use different grpc connections for different streams for same client (specific to golang)?

My project (in golang) has a use case where I need to open several streams from single GRPC client to single/multiple GRPC servers.
Say my proto file is -
syntax = "proto3";
package helloworld;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (stream HelloRequest) returns (stream HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
Now for my client I have 2 options -
package main
import (
"google.golang.org/grpc"
"hello/grpc/helloworld"
)
func newConn() helloworld.GreeterClient {
conn, err := grpc.Dial("localhost:9009", grpc.WithInsecure())
if err != nil {
panic(err)
}
return helloworld.NewGreeterClient(conn)
}
func clientWork(client helloworld.GreeterClient) {
// client work
}
func main() {
// option 1 - new connection objects for all streams
for i:=0; i<1000; i++ {
clientWork(newConn())
}
// option 2 - single connection object for all streams
globalConn := newConn()
for i:=0; i<1000; i++ {
clientWork(globalConn)
}
}
Option 1 is using new grpc.Conn for streams
Option 2 is using single global grpc.Conn for all streams
My question - is their any recommended way of achieving the same (if it is case specific, can I get some exmaples)
I went through https://github.com/grpc/grpc-go/issues/2086#issuecomment-389947160 that suggests that to release underlying buffers/memory I have few options one of which includes closing the grpc.Conn that got me thinking which option should be more suitable in my case.
Thanks
With gRPC it is generally recommended that you use a single connection for all streams to the same server. The main exceptions are for servers that have limits on the number of concurrent streams per connection.

Sending a Websocket message to a specific channel in Go (using Gorilla)

I am very new to Go and have found myself working with sockets as my first project. This is a redundant question, but I have failed to understand how to send a websocket update to a specific channel in Go (using Gorilla).
I am using code sample from this link
this method. But failed to modify to send messages to specific channel.
Here is my sample code
main.go
func main() {
flag.Parse()
hub := newHub()
go hub.run()
http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
fmt.Println(hub)
serveWs(hub, w, r)
})
err := http.ListenAndServe(*addr, nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
There is other two files called hub.go and client.go
I think hub.go below here something could be done
return &Hub{
broadcast: make(chan []byte),
register: make(chan *Client),
unregister: make(chan *Client),
clients: make(map[*Client]bool),
}
What should I change from here?
UPDATE
What I am trying to do is I have a socket server written in go.
Now suppose we have many clients written in react listening on server with a specific url like wss://abc.com/wss1 or may be wss://abc.com/wss2
Now if client wss1 send a message to server, server will emit this message all clients listening on url wss1 not wss2 and vice versa.
Till now i have been able to do broadcast to all clients irrespective of wss1 or wss2.
Hope I made it clear.
To add a "channel" or "chat room" feature to the Gorilla chat example, do the following. I'll use the word "room" in this answer to avoid confusion with Go channels.
Define a type for messages that includes the payload and a room identifier:
type message struct {
roomID string
data []byte
}
Replace the hub broadcast channel with:
broadcast chan message
Add a room identifier to the Client type:
type Client struct {
roomID string
hub *Hub
...
}
The handler extracts the room identifier from the request URI and sets the room identifier when creating a Client and sending a message.
Change the Hubs clients fields to a map keyed by room identifier. Initialize this field as appropriate when initializing the hub.
// Registered clients by room
rooms map[string]map[*Client]bool
Change the Hub run function to work with rooms. This code is structurally similar to the code in the original example.
select {
case client := <-h.register:
room := h.rooms[client.roomID]
if room == nil {
// First client in the room, create a new one
room = make(map[*Client]bool)
h.rooms[client.roomID] = room
}
room[client] = true
case client := <-h.unregister:
room := h.rooms[client.roomID]
if room != nil {
if _, ok := room[client]; ok {
delete(room, client)
close(client.send)
if len(room) == 0 {
// This was last client in the room, delete the room
delete(h.rooms, client.roomID)
}
}
}
case message := <-h.broadcast:
room := h.rooms[message.roomID]
if room != nil {
for client := range room {
select {
case client.send <- message.data:
default:
close(client.send)
delete(room, client)
}
}
if len(room) == 0 {
// The room was emptied while broadcasting to the room. Delete the room.
delete(h.rooms, message.roomID)
}
}
}

Syncing websocket loops with channels in Golang

I'm facing a dilemma here trying to keep certain websockets in sync for a given user. Here's the basic setup:
type msg struct {
Key string
Value string
}
type connStruct struct {
//...
ConnRoutineChans []*chan string
LoggedIn bool
Login string
//...
Sockets []*websocket.Conn
}
var (
//...
/* LIST OF CONNECTED USERS AN THEIR IP ADDRESSES */
guestMap sync.Map
)
func main() {
post("Started...")
rand.Seed(time.Now().UTC().UnixNano())
http.HandleFunc("/wss", wsHandler)
panic(http.ListenAndServeTLS("...", "...", "...", nil))
}
func wsHandler(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("Origin")+":8080" != "https://...:8080" {
http.Error(w, "Origin not allowed", 403)
fmt.Println("Client origin not allowed! (https://"+r.Host+")")
fmt.Println("r.Header Origin: "+r.Header.Get("Origin"))
return
}
///
conn, err := websocket.Upgrade(w, r, w.Header(), 1024, 1024)
if err != nil {
http.Error(w, "Could not open websocket connection", http.StatusBadRequest)
fmt.Println("Could not open websocket connection with client!")
}
//ADD CONNECTION TO guestMap IF CONNECTION IS nil
var authString string = /*gets device identity*/;
var authChan chan string = make(chan string);
authValue, authOK := guestMap.Load(authString);
if !authOK {
// NO SESSION, CREATE A NEW ONE
newSession = getSession();
//defer newSession.Close();
guestMap.Store(authString, connStruct{ LoggedIn: false,
ConnRoutineChans: []*chan string{&authChan},
Login: "",
Sockets: []*websocket.Conn{conn}
/* .... */ });
}else{
//SESSION STARTED, ADD NEW SOCKET TO Sockets
var tempConn connStruct = authValue.(connStruct);
tempConn.Sockets = append(tempConn.Sockets, conn);
tempConn.ConnRoutineChans = append(tempConn.ConnRoutineChans, &authChan)
guestMap.Store(authString, tempConn);
}
//
go echo(conn, authString, &authChan);
}
func echo(conn *websocket.Conn, authString string, authChan *chan string) {
var message msg;
//TEST CHANNEL
authValue, _ := guestMap.Load(authString);
go sendToChans(authValue.(connStruct).ConnRoutineChans, "sup dude?")
fmt.Println("got past send...");
for true {
select {
case val := <-*authChan:
// use value of channel
fmt.Println("AuthChan for user #"+strconv.Itoa(myConnNumb)+" spat out: ", val)
default:
// if channels are empty, this is executed
}
readError := conn.ReadJSON(&message)
fmt.Println("got past readJson...");
if readError != nil || message.Key == "" {
//DISCONNECT USER
//.....
return
}
//
_key, _value := chief(message.Key, message.Value, &*conn, browserAndOS, authString)
if writeError := conn.WriteJSON(_key + "|" + _value); writeError != nil {
//...
return
}
fmt.Println("got past writeJson...");
}
}
func sendToChans(chans []*chan string, message string){
for i := 0; i < len(chans); i++ {
*chans[i] <- message
}
}
I know, a big block of code eh? And I commented out most of it...
Anyway, if you've ever used a websocket most of it should be quite familiar:
1) func wsHandler() fires every time a user connects. It makes an entry in guestMap (for each unique device that connects) which holds a connStruct which holds a list of channels: ConnRoutineChans []*chan string. This all gets passed to:
2) echo(), which is a goroutine that constantly runs for each websocket connection. Here I'm just testing out sending a message to other running goroutines, but it seems my for loop isn't actually constantly firing. It only fires when the websocket receives a message from the open tab/window it's connected to. (If anyone can clarify this mechanic, I'd love to know why it's not looping constantly?)
3) For each window or tab that the user has open on a given device there is a websocket and channel stored in an arrays. I want to be able to send a message to all the channels in the array (essentially the other goroutines for open tabs/windows on that device) and receive the message in the other goroutines to change some variables set in the constantly running goroutine.
What I have right now works only for the very first connection on a device, and (of course) it sends "sup dude?" to itself since it's the only channel in the array at the time. Then if you open a new tab (or even many), the message doesn't get sent to anyone at all! Strange?... Then when I close all the tabs (and my commented out logic removes the device item from guestMap) and start up a new device session, still only the first connection gets it's own message.
I already have a method for sending a message to all the other websockets on a device, but sending to a goroutine seems to be a little more tricky than I thought.
To answer my own question:
First, I've switched from a sync.map to a normal map. Secondly, in order for nobody to be reading/writing to it at the same time I've made a channel that you call to do any read/write operation on the map. I've been trying my best to keep my data access and manipulation quick to execute so the channel doesn't get crowded so easily. Here's a small example of that:
package main
import (
"fmt"
)
var (
guestMap map[string]*guestStruct = make(map[string]*guestStruct);
guestMapActionChan = make (chan actionStruct);
)
type actionStruct struct {
Action func([]interface{})[]interface{}
Params []interface{}
ReturnChan chan []interface{}
}
type guestStruct struct {
Name string
Numb int
}
func main(){
//make chan listener
go guestMapActionChanListener(guestMapActionChan)
//some guest logs in...
newGuest := guestStruct{Name: "Larry Josher", Numb: 1337}
//add to the map
addRetChan := make(chan []interface{})
guestMapActionChan <- actionStruct{Action: guestMapAdd,
Params: []interface{}{&newGuest},
ReturnChan: addRetChan}
addReturned := <-addRetChan
fmt.Println(addReturned)
fmt.Println("Also, numb was changed by listener to:", newGuest.Numb)
// Same kind of thing for removing, except (of course) there's
// a lot more logic to a real-life application.
}
func guestMapActionChanListener (c chan actionStruct){
for{
value := <-c;
//
returned := value.Action(value.Params);
value.ReturnChan <- returned;
close(value.ReturnChan)
}
}
func guestMapAdd(params []interface{}) []interface{} {
//.. do some parameter verification checks
theStruct := params[0].(*guestStruct)
name := theStruct.Name
theStruct.Numb = 75
guestMap[name] = &*theStruct
return []interface{}{"Added '"+name+"' to the guestMap"}
}
For communication between connections, I just have each socket loop hold onto their guestStruct, and have more guestMapActionChan functions that take care of distributing data to other guests' guestStructs
Now, I'm not going to mark this as the correct answer unless I get some better suggestions as how to do something like this the right way. But for now this is working and should guarantee no races for reading/writing to the map.
Edit: The correct approach should really have been to just use a sync.Mutex like I do in the (mostly) finished project GopherGameServer

grpc go : how to know in server side, when client closes the connection

I am using grpc go
i have an rpc which looks roughly like this
196 service MyService {
197 // Operation 1
198 rpc Operation1(OperationRequest) returns (OperationResponse) {
199 option (google.api.http) = {
200 post: "/apiver/myser/oper1"
201 body: "*"
202 };
203 }
Client connects by using grpc.Dial() method
When a client connects, the server does some book keeping. when the client disconnects, the bookkeeping needs to be removed.
is there any callback that can be registered which can be used to know that client has closed the session.
Based on your code, it's an unary rpc call, the client connect to server for only one time, send a request and get a response. The client will wait for the response until timeout.
In server side streaming, you can get the client disconnect from
<-grpc.ServerStream.Context.Done()
signal.
With that above, you can implement your own channel in a go routine to build your logic. Use select statement as:
select {
case <-srv.Context().Done():
return
case res := <-<YOUR OWN CHANNEL, WITH RECEIVED RESQUEST OR YOUR RESPONSE>
....
}
I provide some detailed code here
In client streaming, besides the above signal, you can check whether the server can receive the msg:
req, err := grpc.ServerStream.Recv()
if err == io.EOF {
break
} else if err != nil {
return err
}
Assuming that the server is implemented in go, there's an API on the *grpc.ClientConn that reports state changes in the connection.
func (cc *ClientConn) WaitForStateChange(ctx context.Context, sourceState connectivity.State) bool
https://godoc.org/google.golang.org/grpc#ClientConn.WaitForStateChange
These are the docs on each of the connectivity.State
https://github.com/grpc/grpc/blob/master/doc/connectivity-semantics-and-api.md
If you need to expose a channel that you can listen to for the client closing the connection then you could do something like this:
func connectionOnState(ctx context.Context, conn *grpc.ClientConn, states ...connectivity.State) <-chan struct{} {
done := make(chan struct{})
go func() {
// any return from this func will close the channel
defer close(done)
// continue checking for state change
// until one of break states is found
for {
change := conn.WaitForStateChange(ctx, conn.GetState())
if !change {
// ctx is done, return
// something upstream is cancelling
return
}
currentState := conn.GetState()
for _, s := range states {
if currentState == s {
// matches one of the states passed
// return, closing the done channel
return
}
}
}
}()
return done
}
If you only want to consider connections that are shutting down or shutdown, then you could call it like so:
// any receives from shutdownCh will mean the state Shutdown
shutdownCh := connectionOnState(ctx, conn, connectivity.Shutdown)
as the github issue:link
you can do like this
err = stream.Context().Err()
if err != nil {
break
}

Resources