Some sharing about nestjs, socket.io and @socket.io/redis-adapter

Tram Ho

Question

Recently I had direct contact with socket.io at work and had a problem related to data transmission between clients via socket. My problem can be simply explained as follows:

With 2 given clients (1 Web and 1 App), how can client 1 - App send data to client 2 - Web for processing. As far as I know, there is no way for client 1 to send data directly to client 2 . There is only one way that is through an intermediate server in the middle that plays the role of “transferring” data as shown below.

File_000

OK, so the problem has been determined, now how to solve it?

Solution

With interaction from client 1 to server we can use normal API call, but interaction from server to client 2 can not use “API call”. Theoretically we can use one of the following three methods:

  • Polling
  • Long Polling
  • websocket

With Polling , as shown below, the client will “constantly ask” the server whether there is new data or not? This query frequency is set according to the “polling frequency”. In case the answer is intermittent , this “constant asking” will waste time and waste resources

Screen Shot 2022-09-15 at 23 26 56

Because Polling works “inefficiently”, we have another approach, Long Polling , as shown below the client will “keep” the connection alive until:

  • Get the data returned from the server
  • timeout threshold reached

When the client receives the data returned from the server, it will send a request to the server to continue the Long Polling process as above. However, Long Polling still has the following disadvantages:

  • client 1 and client 2 in the above problem “probably” won’t connect to the same server (this is also a problem I encountered during dev, this problem will be covered in the next section) . The reason is that HTTP is stateless – that is, the server will not know anything about the client’s state, so if the load balancer uses the round robin algorithm to allocate the request, it “maybe” the server receiving the data cannot connect to the client it needs. receive data
  • The server also cannot notify the client when the connection is broken
  • In case the data is not sent too much, the request long polling is still sent continuously when timeout

Screen Shot 2022-09-16 at 8 14 56

The final approach here is websocket , which is a common approach that helps the server to update the changes to the client.

Screen Shot 2022-09-17 at 10 46 43

In a nutshell, a websocket is a bi-directional connection initiated on the client side. When initialized, it is still an HTTP connection but will be “upgraded” to become a websocket connection through “handshake” steps. It is not convenient for me to talk about this process in depth here, but you can understand it roughly as follows:

B1: An HTTP/1.1 connection will still be made between the client and the server

B2: The client will send an “upgrade” HTTP request with a header containing at least 2 of the following information

B3: The client waits until the server responds with a response containing HTTP 101 Switching Protocols , this response shows that the server is switching to the protocol that the client requested to upgrade in the header of the request.

B4: After receiving the response, the websocket connection will be established

More details about websocket you can read at the 2 links below:

Back to our problem, with this “sustainable” websocket connection, the server can completely update all changes to the client side and the connection management will also be completely on the server side.

Note : the parts about Polling , Long Polling , websocket I all refer to from the book System Design Interview - P1 by author Alex Xu . You can find and buy at the link

Solution

For my problem I decided to use socket.io . For those of you who don’t know, socket.io is a pretty famous library to implement websocket connection for Realtime chat projects, …

Its homepage is: https://socket.io/

The project I take on uses nestjs ( https://nestjs.com/ ) for the server and ReactJS ( https://reactjs.org/ ) for the web side (client 2). For the sake of illustration, I will use ReactJS for both client 1 and client 2

You can refer to the illustrative code in the following repository: https://github.com/tuananhhedspibk/BlogCode/tree/main/SocketIoEmitter

Basically two clients 1 & 2 will interact as shown below, client 1 will send data (here is a message) to client 2 .

Screen Shot 2022-09-21 at 18 34 34

Specific explanation

client 1:

https://github.com/tuananhhedspibk/BlogCode/tree/main/SocketIoEmitter/client1/src/App.tsx#L11

In client 1 I use the axios library to send API POST request to the server. Information about the axios library you can see at the link https://github.com/axios/axios

Servers:

https://github.com/tuananhhedspibk/BlogCode/tree/main/SocketIoEmitter/server/src/app.controller.ts#L10

https://github.com/tuananhhedspibk/BlogCode/tree/main/SocketIoEmitter/server/src/events/events.gateway.ts#L13

How to implement socket with nestjs you can refer to the 2 links below:

client 2:

https://github.com/tuananhhedspibk/BlogCode/tree/main/SocketIoEmitter/client2/src/App.tsx#L5

https://github.com/tuananhhedspibk/BlogCode/tree/main/SocketIoEmitter/client2/src/App.tsx#L10

On the client side 2 I use socket.io-client to deploy, you can refer to the details of installing and using this library at the link https://socket.io/docs/v4/client- initialization/

BINGO!!! So our problem is solved.

But life is not like a dream with local or staging environment this deployment works fine but with production it does NOT , yes it DOESN’T WORK

The problem here is that the system doesn’t throw any exceptions or errors, that’s what’s giving me a headache.

Re-simulate the “scene”

We see that client 1 cannot send a message to client 2 even though the system does not report an error (WTF ???). Here I will recreate the “scene” for your convenience.

Server & Client

On the server side I only have one change in the settings for the gateway socket, namely I specify the connection method as websocket instead of using the default method of polling .

In fact, the default value here is ['polling', 'websocket'] ie nestjs will use polling for socket gateway, in case polling fails, nestjs will use websocket instead.

More specifically, you can see the file: https://github.com/tuananhhedspibk/BlogCode/blob/main/SocketIoEmitter/server/src/events/events.gateway.ts#L7

On the client 2 (client listens for events from the server) I also added the transports option to the socket.io-client constructor io()

More specifically, you can see the file: https://github.com/tuananhhedspibk/BlogCode/blob/main/SocketIoEmitter/client2/src/App.tsx#L5

Now I will create 2 server instances listening on ports 6969 and 7000 respectively by passing the PORT command line parameter to the server initialization command.

OK, so both server instances are enabled. The next problem is how to make 2 clients connect to different servers one by one here. The simplest way is to specify the port of the server in each client is different.

  • For client 1 I will let it connect to port 6969server 1
  • For client 2 I will let it connect to port 7000server 2

Result

Our “scene” will look like this

File_000

As shown above, you can also see that our two clients 1 and 2 will not connect to the same server, resulting in a situation where the message sent from client 1 cannot reach client 2.

We can also see it directly on the demo code as shown below

Screen Shot 2022-09-24 at 12 43 28

Specifically, client 2 (running at port 3002 ) connects socket to server 2 , and client 1 (running at port 3001 ) connects to server 1 , so the message from client 1 will be sent to server 1 instead of server 2 , resulting in status client 2 DON’T RECEIVE MESSAGE FROM client 1

That’s the problem, what’s the solution?

Solution to the problem that arises

Thinking simply, readers can probably come up with an idea that is to connect these two servers so that they can transfer data to each other and finally transmit to the other 2nd client.

Exactly, our solution here is that, but how to implement???

Fortunately, socket.io has also “thought” of this situation and developed for us a great library that is @socket.io/redis-adapter (github link: https://github.com/socketio/ socket.io-redis-adapter ). As the socket.io homepage ( https://socket.io/docs/v4/adapter/ ) states that an adapter is a server-side component that “broadcasts” events to all clients and when deploying sockets to multiple clients. servers we need to replace in memory adapter default with other implementation and here I choose Redis Adapter

And it’s amazing that nestjs also supports allowing us to implement redis adapter easily (you can refer to it at https://docs.nestjs.com/websockets/adapter )

The steps to deploy redis adapter are as follows:

B1: Build redis instance (deploying redis adapter without redis is strange)

B2: Deploy redis adapter

Build redis instance

You can refer to how to install redis-cli at this link https://redis.io/docs/getting-started/installation/

After booting, the redis instance will listen on port 6379

Deploy redis adapter

You can follow the instructions of nestjs at https://docs.nestjs.com/websockets/adapter

And here is my implementation

And finally

Screen Shot 2022-09-24 at 16 56 03

Screen Shot 2022-09-24 at 16 56 11

You see, the message from client 1 has been sent to client 2, specifically the websocket connection on the client 2 has received the EmitData event with data corresponding to the message sent from client 1

How @socket.io /redis-adapter . works

This library works based on Redis’ publisher/subscriber mechanism

The publisher/subscriber mechanism can be explained simply as shown in the following figure:

File_000 (1)

Publishers (senders) will send messages to the channel regardless of who the receiver is. The subscriber side will be “interested” in a certain channel, every time this channel has a new message, the subscriber will receive the message without knowing who its sender (publisher) is.

The picture below is how @socket.io/redis-adapter

redis io adapter

① When the server receives the message, it broadcasts the message to all clients that are connected to it.

② Messages from one server will be published to the redis channel from which the remaining socket.io servers will receive these messages. That’s why each socket.io server is both a publisher and a subscriber as we can see in the code below:

So when client 1 sends a request containing a message to server 1 , this server will publish that message to redis channel, server 2 subscribe to redis channel will receive a message from there to “broadcast” to client 2 connecting to it. And as a result, the screen side of client 2 can display the message sent from client 1.

Conclude

Above are some of my shares about nestjs , socket.io and @socket.io/redis-adapter I hope you can use it as a reference for your personal projects as well as in your work. Thank you for patiently reading the entire article, see you in another article.

Share the news now

Source : Viblo