background
Simply describe the requirement scenario: the application needs client-to-client communication, and websocket can do this well. Currently, NetEase Yunxin's IM and other functions are also made use of websocket.
Necessity
For front-end developers, there are still a lot of third-party tools that can provide mock services. Basically, after the request path, request field and response field have been agreed with back-end developers, they can be developed independently.
However, the biggest difference between a websocket server and an http server is that a websocket server must always provide services, otherwise clients cannot communicate with each other.
In order to reflect the essence of front-end separation and improve development efficiency.It must be impossible to write all the logic blindly before debugging with the background, then decide to write a simple websocket communication service with nodejs after the interface name and data format have been agreed with the background.
function
Websockets use more text to transfer (simply a string), so this string carries the recipient's user identity (toId), other information (such as message type, message content data, and so on), usually JSON.stringfy() followed by a string, servicesThe end forwards the sender's user identity (fromId), other information (such as the type of message sent and the content data of the message) to the receiver, who receives the string before JSON.parse() proceeds to the next step.
Use the diagram below to describe the functionality I want the websocket server to provide to clients.
Client
Common methods provided by WebSockets are: new, onopen, onclose, onerror, onmessage, send
There are undoubtedly three ways for front-end developers to combine business logic and focus their attention:
new
Pass your own id when connecting to the websocket server
const ws = new WebSocket(`ws://localhost:8080/websocketServer/${id}`);
send
Send a message (string) to the server, which forwards it to the target
const msgObj = { toId: 666, type: 'hello', data: 'message......' }; const msgStr = JSON.stringfy(msgObj); ws.send(msgStr);
sendTo
Since send cannot receive data of object or array type, it has to write a temporary object at a time and call the JSON.stringfy() method to convert it to a string.
That encapsulates a method that fits your business scenario and simplifies the steps to deliver functionality during development.
// Increased sendTo method on prototype object if ( ! WebSocket.prototype.sendTo ) { WebSocket.prototype.sendTo = function(toId, type, data) { const msg = JSON.stringify({ toId, type, data }); this.send(msg); } } // Call Form ws.sendTo(toId, type, data);
onmessage
Receiving a message usually depends on the value of the type in the message:
- Do something yourself
- Give each other a response (call sendTo method)
ws.onmessage = function(msg) { const { fromId, type, data } = JSON.parse(msg); switch (type) { case 'message': // Do something yourself break; case 'hello': // Return a message this.sendTo(fromId, 'response', 'response data'); } }
Finally, simply wrap up the initialization method for easy use in different places.
const wsUrl = 'ws://localhost:8080/'; const wsPath = 'socketServer/'; // Generate Instances let ws = null; const connect = async () => { ws = await connectWS(uid, messageHandle); // ... // ... // What to do after successful connection } function connectWS(uid, msgHandle) { if ( ! WebSocket.prototype.sendTo ) { WebSocket.prototype.sendTo = function(toId, type, data) { const msg = JSON.stringify({ toId, type, data }); this.send(msg); } } return new Promise((resolve, reject) => { const ws = new WebSocket(`${wsUrl}${wsPath}${uid}`); ws.onopen = () => { console.log('open'); resolve(ws); }; ws.onclose = () => { console.log('close'); }; ws.onerror = () => { console.log('error'); }; ws.onmessage = (msg) => { msgHandle(JSON.parse(msg.data)); }; }); } // Processing functions for receiving messages function messageHandle(msgData) { const { fromId, type, data } = msgData; switch (type) { case '': break; default: break; } }
Server
Both the commonly used nodejs-websocket and express-ws frameworks need to maintain an object to record which user each websocket connection is for message forwarding.
Take express-ws for example:
Record Connections
First, the server records the currently connected user so that the message can be forwarded to the target correctly.
relay the message
The server takes the toId value received from the sender's message as the target (receiver) to forward, finds the connection of the receiver in the object maintained by the server itself, and forwards the sender's identity as fromId to the receiver.
const express = require('express'); const express_ws = require('express-ws'); const app = express(); const wsObj = {}; express_ws(app); app.ws('/socketServer/:uid', (ws, req) => { const uid = req.params.uid; wsObj[uid] = ws; ws.onmessage = (msg) => { let { toId, type, data} = JSON.parse(msg.data); const fromId = uid; if (fromId != toId && wsObj[toId]) { // wsObj[toId] represents the connection between the receiver and the server // wsObj[fromId] represents the connection between the sender and the server wsObj[toId].send(JSON.stringify( { fromId, type, data } )) } } }); app.listen(8080);