1.技术介绍
1.1 socket.io
随着web技术的发展,使用场景和需求也越来越复杂,客户端不再满足于简单的请求得到状态的需求。实时通讯越来越多应用于各个领域。
HTTP是最常用的客户端与服务端的通信技术,但是HTTP通信只能由客户端发起,无法及时获取服务端的数据改变。只能依靠定期轮询来获取最新的状态。时效性无法保证,同时更多的请求也会增加服务器的负担。
WebSocket技术应运而生。
不同于HTTP半双工协议,WebSocket是基于TCP 连接的全双工协议,支持客户端服务端双向通信。
WebSocket
使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
在WebSocket API
中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。
WebSocket对象一共支持四个消息 onopen, onmessage, onclose和onerror。
1.当Browser和WebSocketServer连接成功后,会触发onopen消息。
Socket.onopen = function(evt) {};
2.如果连接失败,发送、接收数据失败或者处理数据出现错误,browser会触发onerror消息。
Socket.onerror = function(evt) {};
3.当Browser接收到WebSocketServer端发送的关闭连接请求时,就会触发onclose消息。
Socket.onclose = function(evt) {};
4.当Browser接收到WebSocketServer发送过来的数据时,就会触发onmessage消息,参数evt中包含server传输过来的数据。
Socket.onmessage = function(evt) {};
5.send用于向服务端发送消息。
Socket.send();
WebSocket是跟随HTML5一同提出的,所以在兼容性上存在问题,这时一个非常好用的库就登场了——socket.io。
socket.io封装了websocket,同时包含了其它的连接方式,你在任何浏览器里都可以使用socket.io来建立异步的连接。socket.io包含了服务端和客户端的库,如果在浏览器中使用了socket.io的js,服务端也必须同样适用。
socket.io是基于 Websocket 的Client-Server 实时通信库。
socket.io底层是基于engine.io这个库。engine.io为 socket.io 提供跨浏览器/跨设备的双向通信的底层库。engine.io使用了 Websocket 和 XHR 方式封装了一套 socket 协议。在低版本的浏览器中,不支持Websocket,为了兼容使用长轮询(polling)替代。
1.2 express
Express 是一个基于 Node.js 封装的上层服务框架,它提供了更简洁的 API 更实用的新功能。它通过中间件和路由让程序的组织管理变的更加容易;它提供了丰富的 HTTP 工具;它让动态视图的渲染变的更加容易;它还定义了一组可拓展标准。
express主要是对外暴露一个createApplication函数用于创建express应用,同时暴露出内置的路由对象、一些中间件等。express
对外暴露的内容下图所示:
对express
有两种不同的用法,其一是把它当作一个http
服务使用; 其二,是把它当作一个已有的http/https
服务的中间件模型使用,也就是此时的express
仅仅承担一个已有服务的扩展功能,使已有服务支持中间件模型。
当作http服务使用示例
const app = express();
app.listen(3000,() => {});
2.聊天室的搭建
2.1前端搭建
2.1.1选择聊天室界面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.12.1/css/all.min.css"
integrity="sha256-mmgLkCYLUQbXn0B1SRqzHar6dCnv9oZFPEC1g1cwlkk="
crossorigin="anonymous"
/>
<link rel="stylesheet" href="css/style.css" />
<title>Chat App</title>
</head>
<body>
<div class="join-container">
<header class="join-header">
<h1><i class="fas fa-smile"></i> WebChat</h1>
</header>
<main class="join-main">
<form action="chat.html">
<div class="form-control">
<label for="username">Username</label>
<input
type="text"
name="username"
id="username"
placeholder="Enter username..."
required
/>
</div>
<div class="form-control">
<label for="room">Room</label>
<select name="room" id="room">
<option value="聊天室a">聊天室a</option>
<option value="聊天室b">聊天室b</option>
<option value="聊天室c">聊天室c</option>
<option value="聊天室d">聊天室d</option>
<option value="聊天室e">聊天室e</option>
<option value="聊天室f">聊天室f</option>
</select>
</div>
<button type="submit" class="btn">加入聊天室</button>
</form>
</main>
</div>
</body>
</html>
2.1.2聊天界面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.12.1/css/all.min.css"
integrity="sha256-mmgLkCYLUQbXn0B1SRqzHar6dCnv9oZFPEC1g1cwlkk="
crossorigin="anonymous"
/>
<link rel="stylesheet" href="css/style.css" />
<title>WebChat</title>
</head>
<body>
<div class="chat-container">
<header class="chat-header">
<h1><i class="fas fa-smile"></i> 在线聊天室</h1>
<a id="leave-btn" class="btn">离开</a>
</header>
<main class="chat-main">
<div class="chat-sidebar">
<h3><i class="fas fa-comments"></i> 房间号:</h3>
<h2 id="room-name"></h2>
<h3><i class="fas fa-users"></i> 用户列表</h3>
<ul id="users"></ul>
</div>
<div class="chat-messages"></div>
</main>
<div class="chat-form-container">
<form id="chat-form">
<input
id="msg"
type="text"
placeholder=""
required
autocomplete="off"
/>
<button class="btn"><i class="fas fa-paper-plane"></i> 发送</button>
</form>
</div>
</div>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/qs/6.9.2/qs.min.js"
integrity="sha256-TDxXjkAUay70ae/QJBEpGKkpVslXaHHayklIVglFRT4="
crossorigin="anonymous"
></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
// 获取 Leave Room 按钮
const leaveBtn = document.getElementById('leave-btn');
// 为按钮添加点击事件监听器
leaveBtn.addEventListener('click', function () {
// 使用 window.location.href 实现页面重定向
window.location.href = 'index.html';
});
});
</script>
<script src="/socket.io/socket.io.js"></script>
<script src="js/main.js"></script>
</body>
</html>
2.1.3 socket.io实现
const chatForm=document.getElementById('chat-form');
const chatMessages=document.querySelector('.chat-messages');
const roomName=document.getElementById('room-name');
const userList=document.getElementById('users');
//获取用户名及房间名
const {username,room}=Qs.parse(location.search,{
ignoreQueryPrefix:true
});
const socket = io();
//向服务器发送用户名与房间号
socket.emit('joinRoom',{username,room});
//获取房间和用户信息
socket.on('roomUsers',({room,users})=>{
outputRoomName(room);
OutputUsers(users);
});
socket.on('message',message=>{
console.log(message);
//提交的消息展示函数
outputMessage(message);
//设置滚动属性
chatMessages.scrollTop=chatMessages.scrollHeight;
});
//消息提交
chatForm.addEventListener('submit',(e)=>{
//防止默认文本
e.preventDefault();
//得到信息文本
const msg=e.target.elements.msg.value;
//发送给服务器信息
socket.emit('chatMessage',msg);
//当发送一条消息后,清空文本框
e.target.elements.msg.value = '';
e.target.elements.msg.focus();
});
//消息展示函数
function outputMessage(message){
const div=document.createElement('div');
div.classList.add('message');
div.innerHTML=`<p class="meta">${message.username} <span>${message.time}</span></p>
<p class="text">
${message.text}
</p>`;
document.querySelector('.chat-messages').appendChild(div);
}
//房间号显示函数
function outputRoomName(room){
roomName.innerText=room;
}
//显示用户列表函数
function OutputUsers(users){
userList.innerHTML=`
${users.map(user =>`<li>${user.username}</li>`).join('')}
`;
}
2.2后端搭建
使用express与socket.io实现后端服务器功能。
const path=require('path');
const http=require('http');
const express=require('express');
//套接字
const sockrtio=require('socket.io');
const formatMessage=require('./utils/messages');
const {userJoin,getCurrentUser,userLeave,getRoomUsers}=require('./utils/users');
const app=express();
const server=http.createServer(app);
const io=sockrtio(server);
const botName='聊天助手';
//当客户连接时执行
io.on('connection',socket=>{
console.log('New WS Connection...');
socket.on('joinRoom',({username,room})=>{
//创建用户
const user=userJoin(socket.id,username,room);
//将当前socket加入指定房间中
socket.join(user.room);
socket.emit('message',formatMessage(botName,'欢迎来到聊天室!'));
//用户连接时广播
socket.broadcast.to(user.room).emit('message',formatMessage(botName,`${user.username}加入了聊天室`));
//发送用户与房间信息,侧栏展示
io.to(user.room).emit('roomUsers',{
room:user.room,
users:getRoomUsers(user.room)
});
});
//监听聊天信息
socket.on('chatMessage',(msg)=>{
//以套接字id获取该用户
const user=getCurrentUser(socket.id);
io.to(user.room).emit('message',formatMessage(user.username,msg));
});
//用户断开连接时运行
socket.on('disconnect',()=>{
const user=userLeave(socket.id);
if(user){
io.to(user.room).emit('message',formatMessage(botName,`${user.username}离开了聊天室`));
//发送用户与房间信息,侧栏展示
io.to(user.room).emit('roomUsers',{
room:user.room,
users:getRoomUsers(user.room)
});
}
});
});
//设置静态文件夹
app.use(express.static(path.join(__dirname,'public')));
const PORT=10000||process.env.PORT;
server.listen(PORT,()=>console.log(`Server running on port ${PORT}`));
处理用户信息
const users=[];
//加入用户进聊天室中
function userJoin(id,username,room){
const user={id,username,room};
users.push(user);
return user;
}
//获取当前用户id
function getCurrentUser(id){
return users.find(user=>user.id===id);
}
//用户离开房间
function userLeave(id){
const index=users.findIndex(user=>user.id===id);
if(index !== -1){
return users.splice(index,1)[0];//删除特定索引元素
}
}
//获取房间用户
function getRoomUsers(room){
return users.filter(user=>user.room===room);
}
module.exports={
userJoin,
getCurrentUser,
userLeave,
getRoomUsers
};
3.成果展示
聊天室A
聊天室B
4.总结
网络程序设计课程由浅入深地讲解了网络编程相关技术,孟宁老师的课程讲解及课后的编程实践,从原理到应用,让之前没有接触过网络编程的同学也能够非常快速的学习上手网络编程相关技术,本次课程考察,我选取了JavaScript专题,深入了解了websocket技术,简单搭建了一个在线聊天室平台,随着后续对网络技术的更加深入全面的学习,能够在现有平台的基础上逐渐完善,学以致用,方能不辜负孟老师一篇苦心。
总的来说,这门课程给我带来了很多收获,感谢孟宁老师的悉心教导!