The previous article, “It’s time for WebSocket to Show your Best side,” is an unfinished read

Because of this, promised you things or cash, the next article, let us together to use the lovely socket. IO to achieve a chat room function

Note: If you are writing chat for the first time, it will take some time to digest, but after you have typed it for two or three times, you will gradually understand and use it.

Here put the address of the project, need to learn, please take it away!

Chat room development process

In fact, this process from the user’s point of view, is nothing more than a connection, send a message.

But in fact, from the user’s point of view, that’s exactly what it looks like, so let’s just cut to the chase

Establish a connection

Of course, yes, this is definitely the first step in all fantastic metaphysics. If you don’t make a connection, you’re talking about a ball

At this point, it suddenly occurred to me that I should first give you the structure of HTML, otherwise how can I knock together step by step

First paste a directory structure, the following files are corresponding to the directory

Page structure

In terms of layout style, BOOTstrap is used directly to make it convenient and quick. The main purpose is to show you how it looks. Here is not a waste of time, the address of the index.html file

There is no function, just the layout of the page, we copy, see how it looks

Let’s try to write down two sets of code for creating a connection on the client side and on the server side

That’s what’s important, masturbation

The client establishes a connection

/ / index. Js fileletsocket = io(); // Listen on the connection to the server socket.on('connect', () => {
    console.log('Connection successful'); 
});
Copy the code

Socket. IO is easy to use, convenient to use, to buy fast, ha ha, continue to write the server side of the connection bar

The server establishes the connection

We will use express to build the server side

// app.js file const express = require('express'); const app = express(); App. use(express.static(__dirname)); // WebSocket is a handshake that relies on HTTP const server = require('http').createServer(app);
const io = require('socket.io')(server); // Listen for connection events with client IO. On ('connection', socket => {
    console.log('Server connection successful'); }); Server.listen (4000); // app.listen (4000);Copy the code

The client and server set up a Websocket connection, so easy, then continue to write and send messages

Send a message

Now that you have your list Ul, input fields, and buttons ready to send a message

Send a message to the server via the socket.emit(‘message’) method

// index.js file // list list, input box content, button sendBtnlet list = document.getElementById('list'),
    input = document.getElementById('input'),
    sendBtn = document.getElementById('sendBtn'); // The method to send the messagefunction send() {
    let value = input.value;
    if(value) {// Send a message to the server socket.emit('message', value);
        input.value = ' ';
    } else {
        alert('The input cannot be empty! '); }} // Click the button to send a message sendBtn.onclick = send;Copy the code

Enter to send message

Click the send button every time, is also enough anti-user action behavior, so we still add the familiar return to send, look at the code, the + sign indicates the new code

// index.js file... Omit // Return to send message method +function enterSend(event) {
+    let code = event.keyCode;
+    if(code === 13) send(); +} // Send message when input box onkeyDown + input. onkeyDown =function(event) { + enterSend(event); +};Copy the code

The front-end has sent the message, then the server steps out, continue to wank

The server processes the message

// app.js file... Check to omit the IO.'connection', socket => {// Listen for messages from clients + socket.on('message', MSG => {// the server sends the message to the client, and then sends the message to the client.'message', {
+            user: 'the system', + content: msg, + createAt: new Date().toLocaleString() + }); +}); });Copy the code

The io.emit() method is broadcast to the lobby and everyone in the room

The client renders the message

We continue to write in index.js, receiving and rendering the message sent by the server

// index.js file... Omit // listen for message events to receive messages from the server + socket.on('message', data => {// Create a new li element and finally add it to the list +let li = document.createElement('li');
+     li.className = 'list-group-item';
+     li.innerHTML = `
        <p style="color: #ccc;">
            <span class="user">${data.user}</span>
            ${data.createAt}
        </p>
        <p class="content">${data.content}</p>`; // Add li to list + list.appendChild(li); + list.scrollTop = list.scrollHeight; +});Copy the code

At this point, the sending part of the message is done, and the executing code should look like the following figure

The above code still has some flaws, but let’s keep working on it

According to the picture, all users are “system”, it is not clear who is who, let’s judge, need to add a user name

Creating a User Name

Here we can know, when the user is the first time to come in, there is no user name, need to be set after the corresponding name will be displayed

As a result, we used the first entry as the user name

// app.js file... Omit // set the SYSTEM to a constant to make it easier to use const SYSTEM ='the system';

io.on('connection', socket => {// Record the user name, which is used to record whether it is the first time to enter, default is undefined +let username;
    socket.on('message', MSG => {// If the user name has +if(username) {// then broadcast to all + IO. Emit ('message', { + user: username, + content: msg, + createAt: new Date().toLocaleString() + }); +}else{// if the username does not exist // if the username does not exist // if the username does not exist // if the username does not exist // if the username does not exist // if the username does not exist // if the username does not exist // + socket.broadcast.emit(+ socket.broadcast.emit)'message', {
+                user: SYSTEM,
+                content: `${username}Join the chat! `, + createAt: new Date().toLocaleString() + }); +}}); });Copy the code

☆️ socket.broadcast.emit, which broadcasts to everyone except yourself

Yes, after all, I have not entered the chat room in my heart, ha ha

Now take a look at the effect of the implementation, please see the figure

The private chat

Add the private chat

In the group, we all know that “@” means that this message is exclusive to the person who is “@”, and others do not need to care

How to achieve private chat? In this case, we’re going to do a private chat by clicking on the user name in the message list, so let’s just write

@ the

// index.js file... // Private chat method +function privateChat(event) {
+    lettarget = event.target; // Get the corresponding user name +letuser = target.innerHTML; // Only those whose class is user are the target element +if (target.className === 'user') {// Display the @ username in the input field + input.value = '@${user}`; +} +} // Click to chat privately + list.onclick =function(event) { + privateChat(event); +};Copy the code

The client has set the @ username format in the input box, as long as the message is sent, the server can distinguish between private and public chat, continue to write the server processing logic

Server-side processing

First of all, the premise of private chat is to have obtained a user name

The re then determines which messages are private

Finally, you need to find the socket instance of the other party to send messages to the other party

So, look at the following code

// app.js file... Omit // to save the corresponding socket, that is, record the peer socket instance +let socketObj = {};

io.on('connection', socket => {
    let username;
    socket.on('message', msg => {
        if(username) {// determine whether the message is private +let private = msg.match(/@([^ ]+) (.+)/);

+           if(private) {// Private chat message // private chat user, the first group of the re match +lettoUser = private[1]; // Private chat content, re match the second group +letcontent = private[2]; // Get the socket + from socketObj for private chat userslet toSocket = socketObj[toUser];

+                if(toSocket) {tosocket.send ({+ user: username, + content, + createAt: new Date().tolocaleString () +}); +}}else{// public message IO. Emit ('message', { user: username, content: msg, createAt: new Date().toLocaleString() }); }}else{// The user name does not exist... SocketObj = {socketObj = {'Jay Chou': socket, 'Nicholas Tse': socket } + socketObj[username] = socket; }}); });Copy the code

At this point, we have completed the function of public chat and private chat, which is great, but not arrogant, we will improve a few small details

Now all usernames and message bubbles are the same color, which makes it hard to tell the difference between users

SO, let’s change the color part

Assign users different colors

The server handles the color

// app.js file... omitletsocketObj = {}; // Set an array of colors so that each user enters the chat with a different color +let userColor = ['#00a1f4'.'#0cc'.'#f44336'.'# 795548'.'#e91e63'.'#00bcd4'.'# 009688'.'#4caf50'.'#8bc34a'.'#ffc107'.'#607d8b'.'#ff9800'.'#ff5722']; // Unscramble the arrayfunction shuffle(arr) {
+    let len = arr.length, random;
+    while(0! = = len) {/ / right shift operator down the random integer + = (Math) random () * len -) > > > 0; + [arr[len], arr[random]] = [arr[random], arr[len]]; +},return arr;
+ }

io.on('connection', socket => {
     let username;
+    letcolor; // Socket.on ('message', msg => {
        if(username) { ... omitif(private) { ... omitif(toSocket) { toSocket.send({ user: username, + color, content: content, createAt: new Date().toLocaleString() }); }}else {
                io.emit('message', { user: username, + color, content: msg, createAt: new Date().toLocaleString() }); }}else{// The user name does not exist... + color = shuffle(userColor)[0]; shuffle(userColor)[0]; socket.broadcast.emit('message', {
                user: 'the system',
+               color,
                content: `${username}Join the chat! `, createAt: new Date().toLocaleString() }); }}); });Copy the code

The server side of the color assigned, the front side of the rendering is done, continue to write, do not stop

Color rendering

On the created Li element, color the user name and content in the style style as follows

// index.js ... Check omit the socket.'message', data => {
    let li = document.createElement('li');
    li.className = 'list-group-item'; // Add the color + li.innerHTML = '<p style='"color: #ccc;"><span class="user" style="color:${data.color}">${data.user} </span>${data.createAt}</p>
                    <p class="content" style="background:${data.color}">${data.content}</p>`; list.appendChild(li); List.scrolltop = list.scrollHeight; list.scrollheight = list.scrollheight; });Copy the code

It’s done, it’s done. Let’s see what happens

Now, let’s write the last feature in theory: enter a group to chat, and the message can only be seen by the group

Join a designated room (group)

We’ve been looking at the two group buttons in the screenshot above, and you can see what they are for by looking at their literal meanings, just for this moment

Here we go again, continue to masturbate, will soon complete the masterpiece

Client – In and out of room (group)

// index.js file... // The way to enter the room +function join(room) {
+    socket.emit('join', room); // If you have entered the room, display the leave room button + socket.on('joined', room => {
+    document.getElementById(`join-${room}`).style.display = 'none';
+    document.getElementById(`leave-${room}`).style.display = 'inline-block'; +}); // The way to leave the room +function leave(room) {
    socket.emit('leave', room); // If you have left the room, display the enter room button + socket.on('leaved', room => {
+    document.getElementById(`leave-${room}`).style.display = 'none';
+    document.getElementById(`join-${room}`).style.display = 'inline-block'; +});Copy the code

The join and leave methods defined above can be called directly on the corresponding buttons, as shown in the figure below

Server – Handling in and out of rooms (cluster)

// app.js file... Check to omit the IO.'connection', socket => { ... Omit // the array + that records which rooms are enteredlet rooms = [];
    io.on('message', msg => { ... Omit}); // Listen for room entry events + socket.on('join', room => {+ // Determine whether the user has entered the room, if not, let it enter the room +if(username && rooms. IndexOf (room) === -1) {// socket.join indicates to enter a room + socket.join(room); + rooms.push(room); // Let the front end listen and control the room button explicitly + socket.emit('joined', room); Send ({+ user: SYSTEM, + color, + content: 'You are in${room}+ createAt: new Date().tolocaleString () +}); + +}}); // Listen for events leaving the room + socket.on('leave', room => {// index is the index of the room in array rooms, which is convenient to delete +let index = rooms.indexOf(room);
+       if(index ! == -1) { + socket.leave(room); // Leave the room + room.splice (index, 1); // Send a leaved event to allow the front end to listen and control the room button explicitly + socket.emit('leaved', room); Send ({+ user: SYSTEM, + color, + content: 'You are gone${room}+ createAt: new Date().tolocaleString () +}); + +}}); });Copy the code

At this point, we have also implemented the join and leave room functions, as shown below

So let’s write the room logic again and continue to iterate in app.js

Handle in-room speeches

// app.js file... Omit // to log a socket.id to find the corresponding user +let mySocket = {};

io.on('connection', socket => { ... MySocket [socket.id] = socket; mySocket[socket.id] = socket; socket.on('message', msg => {
        if(private) { ... Omit}else{// If the rooms array has a value, a user entered the room +if(room.length) {// Used to store the corresponding socket.id + that enters the roomletsocketJson = {}; + room. forEach(room => {// Get access to all sockets in the roomhashValue, which is the socket. Id + receivedlet roomSockets = io.sockets.adapter.rooms[room].sockets;
+                   Object.keys(roomSockets).forEach(socketId => {
                        console.log('socketId', socketId); // Perform a de-duplicate, with only the corresponding socketId + in socketJsonif(! socketJson[socketId]) { + socketJson[socketId] = 1; + +}}); +}); Keys (socketJson).foreach (socketId => {+ mySocket[socketId].emit('message', { + user: username, + color, + content: msg, + createAt: new Date().toLocaleString() + }); +}); }else{// If it is not private, broadcast IO. Emit ('message', { user: username, color, content: msg, createAt: new Date().toLocaleString() }); }}}); });Copy the code

After re-running the app.js file and entering the room to chat, the effect as shown in the picture below will be displayed. Only users in the same room can see messages between each other

Message history

After all, every time you enter a chat room, it is empty and too pale. You still want to know what the previous users talked about

So let’s go ahead and implement our last feature

Displaying historical messages

In fact, the correct development of the situation, the user input all messages should be stored in the database, but we do not involve other aspects of the knowledge point, directly with the pure front-end technology to simulate the implementation

Get historical messages

This tells the client to send a getHistory event that tells the server we want to get the latest 20 messages when the socket connection is successful

// index.js ... Check omit the socket.'connect', () => {
    console.log('Connection successful'); // Send getHistory to the server to get the message + socket.emit('getHistory');
});
Copy the code

The server processes the history and returns it

// app.js ... Omit // Create an array to store the last 20 message records, which will be stored in the database in the real projectlet msgHistory = [];

io.on('connection', socket => { ... Check to omit the IO.'message', msg => { ... omitif(private) { ... Omit}else {
            io.emit('message', { user: username, color, content: msg, createAt: new Date().toLocaleString() }); + msghistory. push({+ user: username, + color, + content: MSG, + createAt: new Date().toLocaleString() + }); }}); // Listen for events that get history messages + socket.on('getHistory', () => {// Intercepts the last 20 messages with the array's slice method +if (msgHistory.length) {
+           let history= msgHistory.slice(msgHistory.length - 20); / / sendhistoryEvent and returnhistoryMessage array to client + socket.emit('history'.history); + +}}); });Copy the code

Client renders history messages

// index.js ... Omit // Receive history messages + socket.on('history'.history= > {/ /historyWe get an array, so map it to the new array, and then join it to make a string +let html = history.map(data => {
+       return `<li class="list-group-item">
            <p style="color: #ccc;"><span class="user" style="color:${data.color}">${data.user} </span>${data.createAt}</p>
            <p class="content" style="background-color: ${data.color}">${data.content}</p>
        </li>`;
+   }).join(' ');
+   list.innerHTML = html + '
  • ; + list.scrollTop = list.scrollHeight; +});Copy the code

    This completes the final history message function, as shown below

    comb

    The function of the chat room is complete, see here head a little dizzy, now simple recall, actually have what function

    1. Create a websocket connection between the client and the server
    2. The client and server send messages to each other
    3. Adding a User Name
    4. Add the private chat
    5. Enter/leave the room to chat
    6. Message history

    Small Tips

    For the above code commonly used to send messages to distinguish:

    • Socket.send () sends messages to show them to yourself
    • IO. Emit () sending messages is for everyone to see
    • Socket.broadcast.emit () Sends messages to all but yourself

    Finally, to say my feelings, this article is a little uncomfortable

    Because the article can not express the same as personally, so I am also exploring how to write a good technical article, I hope you understand and give more comments (the new code part how to write more clearly), thank you for your hard watching, goodbye!!