concurrent relaying of data between multiple clients - go

I am currently working on an application relaying data sent from a mobile phone via a server to a browser using WebSockets. I am writing the server in go and I have a one-to-one relation between the mobile phones and the browsers as shown by the following illustration.
.
However, I want multiple sessions to work simultaneously.
I have read that go provides concurrency models that follow the principle "share memory by communicating" using goroutines and channels. I would prefer using the mentioned principle rather than locks using the sync.Mutex primitive.
Nevertheless, I have not been able to map this information to my issue and wanted to ask you if you could suggest a solution.

I had a similar to your problem, I needed multiple connections which each send data to each other through multiple servers.
I went with the WAMP protocol
WAMP is an open standard WebSocket subprotocol that provides two application messaging patterns in one unified protocol:
Remote Procedure Calls + Publish & Subscribe.
You can also take a look at a project of mine which is written in go and uses the protocol at hand: github.com/neutrinoapp/neutrino

There's nothing wrong with using a mutex in Go. Here's a solution using a mutex.
Declare a map of endpoints. I assume that a string key is sufficient to identify an endpoint:
type endpoint struct {
c *websocket.Conn
sync.Mutex // protects write to c
}
var (
endpoints = map[string]*endpoint
endpointsMu sync.Mutex // protects endpoints
)
func addEndpoint(key string, c *websocket.Connection) {
endpointsMu.Lock()
endpoints[key] = &endpoint{c:c}
endpointsMu.Unlock()
}
func removeEndpoint(key string) {
endpointsMu.Lock()
delete(endpoints, key)
endpointsMu.Unlock()
}
func sendToEndpoint(key string, message []byte) error {
endpointsMu.Lock()
e := endpoints[key]
endpointsMu.Unlock()
if e === nil {
return errors.New("no endpoint")
}
e.Lock()
defer e.Unlock()
return e.c.WriteMessage(websocket.TextMessage, message)
}
Add the connection to the map with addEndpoint when the client connects. Remove the connection from the map with removeEndpoint when closing the connection. Send messages to a named endpoint with sendToEndpoint.
The Gorilla chat example can be adapted to solve this problem. Change the hub map to connections map[string]*connection, update channels to send a type with connection and key and change the broadcast loop to send to a single connection.

Related

When making a go RPC call , the return type is channel

Firstly, here is a PRC server. Please notice one of the return type is chan:
func (c *Coordinator) FetchTask() (*chan string, error) {
// ...
return &reply, nil
}
Then the client makes a RPC call. Typically the caller will get a channel which type is *chan string.
call("Coordinator.FecthTask", &args, &reply)
Here is my question. If the server continuously write into the channel:
for i := 0; i < 100; i++ {
reply<- strconv.Itoa(i)
}
🎈🎈🎈 Can the client continuously read read from the channel?
for {
var s string = <-reply
}
I guess the client can't, cuz server and client are not in the same memory. They communicate via Internet. Therefore, even the variable reply is a pointer, it points different address in server and client.
I'am not sure about it. What do you think of it? Thanks a lot!!!!
🎈🎈 BTW, is there anyway to implement a REAL, stateful channel between server and client?
As you already mentioned, channels are in memory variables and it is not possible to use them in other apps or systems. In the other hand gRPC will pass and parse binary data which in this case again passing a channel pointer, will only returns the pointer address in server's memory. After client receiving that address it will try to point to that address in local machine's memory which will can be any sort of data unfortunately.
If you want to push a group of data (let's say an array of strings) you can use a Server streaming or Bidirectional streaming.
In the other hand if you want to accomplish some sort of stable and keep-alive connection you can consider websockets too.

Golang: RabbitMQ receiver + concurrent map + http server

TL;DR
What can I do to make two services (rabbitMQ consumer + HTTP server) share the same map?
More info
I'm new to Golang. Here's what I'm trying to achieve:
I have a RabbitMQ consumer receiving some json-format messages and store them into a concurrent map. On the other hand, I need an HTTP server that sends data from the concurrent map whenever a GET request arrives.
I kinda know that I need the"net/http" package for the HTTP server and the rabbitMQ client package.
However, I'm not sure how these two services can share the same map. Could anyone please offer some idea? Thank you in advance!
EDIT
One possible solution I can think of is to replace the concurrent map with Redis. So the running consumer will send the data to Redis server whenever a message arrives and then the http server will serve GET request from the data in Redis. But is there a better way to achieve my goal without adding this extra layer (Redis)?
Assuming that your two "services" live inside the same Go program, dependency injection. You can define a type that wraps your map (or provides equivalent functionality), instantiate it when your application starts, and inject it into both the HTTP handler and the MQ consumer.
The following code is meant to illustrate the concept:
package mymap
// imports
type MyMap struct {
// fields
}
package main
// imports
func main() {
...
// instantiate the shared map object
sharedMap := &MyMap{ /* init fields as appropriate */ }
mqconsumer := &mqpkg.Consumer{
SharedMap: sharedMap // inject the map obj into the mq consumer
// ...
}
// start the mq consumer
// inject the map obj into the http handler
http.HandleFunc("/foo", handlerWithMap(sharedMap))
log.Fatal(http.ListenAndServe(":8080", nil))
}
func handlerWithMap(mymap *mymap.MyMap) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// here the http handler is able to access the shared map object
}
}
With that said, unless your application has particular requirements, I would recommend to implement your own synchronized map. This isn't too difficult to accomplish with the sync package. The disadvantage of using third-party libraries is that you lose type safety, because their signatures must be designed to accept and return interface{}'s.

Relay data between two different tcp clients in golang

I'm writing a TCP server which simultaneously accepts multiple connections from mobile devices and some WiFi devices (IOT). The connections needs to be maintained once established, with the 30 seconds timeout if there is no heartbeat received. So it is something like the following:
// clientsMap map[string] conn
func someFunction() {
conn, err := s.listener.Accept()
// I store the conn in clientsMap
// so I can access it, for brevity not
// shown here, then:
go serve(connn)
}
func serve(conn net.Conn) {
timeoutDuration := 30 * time.Second
conn.SetReadDeadline(time.Now().Add(timeoutDuration))
for {
msgBuffer := make([]byte, 2048)
msgBufferLen, err := conn.Read(msgBuffer)
// do something with the stuff
}
}
So there is one goroutine for each client. And each client, once connected to the server, is pending on the read. The server then processes the stuff read.
The problem is that I sometimes need to read things off one client, and then pass data to another (Between a mobile device and a WiFi device). I have stored the connections in clientsMap. So I can always access that. But since each client is handled by one goroutine, shall I be passing the data from one client to another by using a channel? But if the goroutine is blocked waiting for a pending read, how do I make it also wait for data from a channel? Or shall I just obtain the connection for the other party from the clientsMap and write to it?
The documentation for net.Conn clearly states:
Multiple goroutines may invoke methods on a Conn simultaneously.
So yes, it is okay to simply Write to the connections. You should take care to issue a single Write call per message you want to send. If you call Write more than once you risk interleaving messages from different mobile devices. This implies calling Write directly and not via some other API (in other words don't wrap the connection). For instance, the following would not be safe:
json.NewEncoder(conn).Encode(myValue) // use json.Marshal(myValue) instead
io.Copy(conn, src) // use io.ReadAll(src) instead

How to obtain a channel "handle" without an associated type

I have a backend (Go server) which services multiple frontends (web pages) and all requests/responses are handled via channels of specific types. Eg, each frontend is associated (on the backend) with a channel where responses are sent (type = chan<- Response).
I have recently implemented a login system where each frontend is associated with a user ID. To keep track of users I have a map:
logins map[chan<- Response]LoginData
Using this I can quickly look up things related to a frontend, such as permissions. This all works fine.
However, to keep things safer and more modular I have moved all the Login stuff to a separate package. This all works except for one gotcha - the logins map is keyed by the type "chan<- Response", but the Response type is defined in my main package and I don't want to expose it to the Login package. (I don't think I could anyway as it would create a circular reference.)
I only want to use the "chan<- Response" as a handle type in the Login package - I don't need to write to that channel from there. I tried converting the channel to an unsafe.Pointer but that is not allowed by the compiler. On the other hand I can't use a pointer to the channel variable (*chan<- Response) as the handle as the channel is stored in several places so the channel variable will have different addresses.
I also tried casting to a different type of chan such as chan int and chan interface{} but the compiler does not like that. There does not seem to be any way to convert a channel into a "generic" channel.
I really just want the address of the channel's internal data - like you get when you fmt.Printf a channel with %v. The best I can come up with is to use a string like this:
var c chan<- Response = ...
var userID = "steve"
loginKey = fmt.Sprint(c)
Login.Add(loginKey, userID)
I'm not sure this is valid but seems to work, but it seems to me there should be a better way.
On the other hand I can't use a pointer to the channel variable (*chan<- Response) as the handle as the channel is stored in several places so the channel variable will have different addresses.
But this is the only (halfway acceptable solution): Do not pass around a chan Response but a *(chan Response) (adding directions to your likings). Everything else is crap. Best thing to do is hiding this chan Response in a type.

Golang goroutine-safe http client with different timeout?

Suppose I have the following function:
func SendRequest(c *Client, timeout time.Duration) {
if timeout > 0 {
c.Timeout = timeout
} else {
c.Timeout = defaultTimeout
}
...
}
I want to allow multiple go-routines to call this function (to share the same HTTP client), but the way this is written apparently can't guarantee goroutine safety. (Also changing the timeout of the client passed in is weird too...)
I'm not sure what's the best way to do this. Should I use different client for different timeouts? Should I use some mutex? Or in general how do I share a HTTP client with different timeouts?
Thanks!
You need to use different Clients. Even if you protect your function with a mutex, you can't protect the internal access by the Client, and another goroutine could change it while making the request.
Multiple Clients can still share the same Transport, and they both will use the DefaultTransport if you don't specify one.

Resources