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
- Create a websocket connection between the client and the server
- The client and server send messages to each other
- Adding a User Name
- Add the private chat
- Enter/leave the room to chat
- 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!!