使用 Node.js 和 Socket.io 构建一个简单的聊天应用程序
您是否正在尝试寻找一种构建聊天应用程序的方法,但却难以找到一个可以解释所有内容的简单教程?
这篇文章很适合您!
这是我们将要构建的内容的预览
免责声明
本文将介绍聊天背后的逻辑,但不会涉及所有样式部分。如果您需要 CSS 文件,我会在文章末尾提供 GitHub 仓库的链接。
设置
我假设你已经安装了 npm 和 node,并且了解它们的工作原理(至少了解基础知识)。此外,还需要具备 JavaScript 的基础知识。
那么,让我们开始吧。
为应用程序创建一个目录,用您喜欢的编辑器打开该目录:
mkdir chatApplication && cd client
接下来,我们将目录初始化为 Nodejs 应用程序。
npm init -y
现在让我们开始安装构建此应用程序所需的所有依赖项:
npm i express randomcolor socket.io uuid
作为开发依赖项,我们将安装 nodemon,它是一个实用程序,可以监视源中的任何更改并自动重启服务器。
npm i -D nodemon
构建服务器端
我们将服务器端逻辑保存在 app.js 文件(位于主文件夹中)中,这是一个简单的快速服务器
//app.js
const express = require('express');
const app = express();
let randomColor = require('randomcolor');
const uuid = require('uuid');
//middlewares
app.use(express.static('public'));
//routes
app.get('/', (req,res)=>{
res.sendFile(__dirname + '/client/index.html');
});
//Listen on port 5000
server = app.listen( process.env.PORT || 5000);
现在服务器已经准备好了,让我们开始处理 socket.io 的逻辑。首先,我们需要实例化 socket.io,之后它会监听每个连接。当用户连接时,我们的套接字会监听以下事件:
- change_username:获取用户名并发送
- new_message:监听用户的新消息
- 断开连接:监听用户断开聊天连接的情况
因此让我们编写代码来使其工作!
//app.js
//socket.io instantiation
const io = require("socket.io")(server);
const users = [];
const connnections = [];
//listen on every connection
io.on('connection', (socket) => {
console.log('New user connected');
//add the new socket to the connections array
connnections.push(socket)
//initialize a random color for the socket
let color = randomColor();
//Set the first username of the user as 'Anonymous'
socket.username = 'Anonymous';
socket.color = color;
//listen on change_username
socket.on('change_username', data => {
let id = uuid.v4(); // create a random id for the user
socket.id = id;
socket.username = data.nickName;
users.push({id, username: socket.username, color: socket.color});
updateUsernames();
})
//update Usernames in the client
const updateUsernames = () => {
io.sockets.emit('get users',users)
}
//listen on new_message
socket.on('new_message', (data) => {
//broadcast the new message
io.sockets.emit('new_message', {message : data.message, username : socket.username,color: socket.color});
})
//Disconnect
socket.on('disconnect', data => {
if(!socket.username)
return;
//find the user and delete from the users list
let user = undefined;
for(let i= 0;i<users.length;i++){
if(users[i].id === socket.id){
user = users[i];
break;
}
}
users.splice(user,1);
//Update the users list
updateUsernames();
connnections.splice(connnections.indexOf(socket),1);
})
})
如果你想知道randomColor()的作用,你会发现什么时候会覆盖前端
前端
前端非常简单,只包含一个 html 文件和两个 javascript 文件(以及一个 css 文件,本教程不会涉及)。
让我们进入客户端文件夹并创建以下文件:index.html、chat.js、modalScript.js以及用于保存style.css 的css 文件夹 。index.html首次渲染时会包含一个模态框来获取用户名,至于其他内容,我使用了 flexbox 布局来提高响应速度:
<!--index.html-->
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" const="text/html;charset=UTF-8" />
<link href="http://fonts.googleapis.com/css?family=Comfortaa" rel="stylesheet" type="text/css">
<link rel="stylesheet" type="text/css" href="css/style.css" >
<!--Socket.io scirpt-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.0.4/socket.io.js"></script>
<title>Simple Chat App</title>
</head>
<body>
<!-- The Modal -->
<div id="myModal" class="modal">
<!-- Modal content -->
<div class="modal-content">
<h1 class="modal-title">What's your nickname?</h1>
<input id="nickname-input" class="custom-input" type="text" />
</div>
</div>
<!--Big wrapper-->
<div class="big-wrapper">
<!-- Left Column-->
<div class="online-user-wrapper">
<div class="online-user-header-container">
<header>
<h2>Online Users</h2>
</header>
</div>
<div>
<!--Online users goes here-->
<ul id="users-list">
</ul>
</div>
</div>
<!--Chat Wrapper -->
<div class="chat-wrapper">
<div class="super-chat-title-container">
<header>
<h1>Chat</h1>
</header>
</div>
<!--Messages container-->
<div id="chatroom">
<!--x is typing goes here-->
<div id="feedback"></div>
</div>
<!-- Input zone -->
<div id="input_zone">
<input id="message" class="vertical-align custom-input" type="text" />
<button id="send_message" class="vertical-align btn" type="button">Send</button>
</div>
</div>
</div>
<!--jQuery script-->
<script src="http://code.jquery.com/jquery-latest.min.js"></script>
<!--Scripts-->
<script src="./chat.js"></script>
<script src="./modalScript.js"></script>
</body>
</html>
如果我们保留index.html,那么模式就不会消失,所以我们需要添加一些 javascript 来处理它。
//modalScript.js
// Get the modal
var modal = document.getElementById("myModal");
const nicknameInput = document.getElementById("nickname-input");
// Close modal when nick-name is typed
nicknameInput.onkeypress = e => {
let keycode = (e.keyCode ? e.keyCode : e.which);
if(keycode == '13'){
modal.style.display = "none";
}
};
如果后端正在监听事件,那么前端必须发送这些事件。我们将通过 chat.js 文件发送这些事件。例如,当新用户进入应用时,我们必须监听该用户,获取用户名并将其发送到后端。
//chat.js file
$(function () {
//make connection
let socket = io.connect('http://localhost:5000');
//buttons and inputs
let message = $("#message");
let send_message = $("#send_message");
let chatroom = $("#chatroom");
let feedback = $("#feedback");
let usersList = $("#users-list");
let nickName = $("#nickname-input");
//Emit typing
message.bind("keypress", e => {
let keycode = (e.keyCode ? e.keyCode : e.which);
if(keycode != '13'){
socket.emit('typing')
}
});
消息
正如您所猜测的消息一样,原理是一样的!
//chat.js
$(function () {
//make connection
let socket = io.connect('http://localhost:5000');
//buttons and inputs
let message = $("#message");
let send_message = $("#send_message");
let chatroom = $("#chatroom");
let feedback = $("#feedback");
let usersList = $("#users-list");
let nickName = $("#nickname-input");
//Emit message
// If send message btn is clicked
send_message.click(function(){
socket.emit('new_message', {message : message.val()})
});
// Or if the enter key is pressed
message.keypress( e => {
let keycode = (e.keyCode ? e.keyCode : e.which);
if(keycode == '13'){
socket.emit('new_message', {message : message.val()})
}
})
//Listen on new_message
socket.on("new_message", (data) => {
feedback.html('');
message.val('');
//append the new message on the chatroom
chatroom.append(`
<div>
<div class="box3 sb14">
<p style='color:${data.color}' class="chat-text user-nickname">${data.username}</p>
<p class="chat-text" style="color: rgba(0,0,0,0.87)">${data.message}</p>
</div>
</div>
`)
keepTheChatRoomToTheBottom()
});
//Emit a username
nickName.keypress( e => {
let keycode = (e.keyCode ? e.keyCode : e.which);
if(keycode == '13'){
socket.emit('change_username', {nickName : nickName.val()});
socket.on('get users', data => {
let html = '';
for(let i=0;i<data.length;i++){
html += `<li class="list-item" style="color: ${data[i].color}">${data[i].username}</li>`;
}
usersList.html(html)
})
}
});
});
// function thats keeps the chatbox stick to the bottom
const keepTheChatRoomToTheBottom = () => {
const chatroom = document.getElementById('chatroom');
chatroom.scrollTop = chatroom.scrollHeight - chatroom.clientHeight;
}
打字
为了完善我们的聊天应用,我们需要添加一个所有聊天应用都具备的功能:发送正在输入内容的用户信息。实现这个功能非常简单。
在chat.js文件中,我们需要在“你”输入时发出消息,并在“其他”用户输入时监听消息。
我们来添加以下几行代码:
//Emit typing
message.bind("keypress", e => {
let keycode = (e.keyCode ? e.keyCode : e.which);
if(keycode != '13'){
socket.emit('typing')
}
});
//Listen on typing
socket.on('typing', (data) => {
feedback.html("<p><i>" + data.username + " is typing a message..." + "</i></p>")
});
在app.js文件中,我们想要广播用户正在输入的内容。只需添加以下三行代码即可:
//listen on typing
socket.on('typing', data => {
socket.broadcast.emit('typing',{username: socket.username})
})
结论
我们的应用到此就完成了。正如你所见,它非常简单,而且不像我之前想象的那样需要大量的代码。
欢迎随时报告问题和错误,改进代码,让它变得更好。
以下是 Github 仓库: