共计 9764 个字符,预计需要花费 25 分钟才能阅读完成。
导语:前几天做了一个繁难的聊天室,实现了聊天性能,聊天内容能够发送文本,图片,音频,视频,表情包等内容,反对一对一聊天,群组聊天。当初就之前的聊天室性能做一个简略的梳理和总结。
目录
- 原理简述
- 性能开发
- 成果体验
原理简述
这次应用了 socket.io
这个工具包进行通信。
webscoket
html5 中有 websocket 的性能,参考这篇文章《html 常识总结之 WebSocket》理解更多基础知识。
WebSocket 是一种在单个 TCP 连贯上进行全双工通信的协定, 能更好的节俭服务器资源和带宽,并且可能更实时地进行通信。
课外科普:
通信分类分为并行通信,串行通信,同步 / 异步,单工 / 双工,半双工 / 全双工。
- 并行通信 指的是数据的各位同时在多根数据线上发送或接管。管制简略,传输速度快;因为传输线较多,实用于短距离通信。
-
串行通信 是指数据的各位在同一根数据线上逐位发送和接管。管制简单,传输速度慢;只须要一根数据线,实用于远距离通信。
- 依据对数据流的分界、定时以及同步计划办法不同,可分为和同步串行通信形式和异步通信形式。
-
依据串行数据的传输方向,咱们能够将通信分为单工,半双工,双工。
- 单工:是指数据传输仅能沿一个方向,不能实现反向传输。
- 半双工:是指数据传输能够沿两个方向,但须要分时进行传输。
- 全双工:是指数据能够同时进行双向传输。
socket.io
socket.io是基于 websocket 协定的一套成熟的解决方案。长处是性能好,反对多平台;毛病是传输的数据并不齐全遵循 websocket 协定, 这就要求客户端和服务端都必须应用 socket.io 的解决方案。
区别
- http 和 webscoket 都是基于 tcp;
- http 建设的是短连贯;
- websocket 建设的是长连贯;
性能开发
当初就这个性能进行剖析并且开发前端和后端内容,先来买通后端局部,为前端连贯 socket 服务作筹备。
上面这个展现的是最根底的聊天室,包含以下几个性能:
- 多人聊天
- 显示用户名和人数
- 回车发送
- 到顶部
后端方面
这里就是如何在 node 中建设 socket 服务器。
装置依赖包
npm install -g express-generator
express --view=ejs chat
cd chat
npm install
npm install socket.io
配置 socket.io
关上 bin/www
文件,在 var server = http.createServer(app);
上面一行写入以下内容。
上面内容就离开介绍各个内容,不做全副代码粘贴。
- 引入 ws 服务
const ws = require('socket.io');
const io = ws(server, {
path: '/chat',
transports: [
'polling',
'websocket'
]
})
罕用办法
- 连贯和断开连接
io.on('connection', socket => {console.log('a user connected!');
//disconnect
socket.on('disconnect', () => {console.log('a user disconnected!');
})
}
- 退出和来到房间
// join room
socket.join(roomId);
// leave room
socket.leave(roomId);
- 承受音讯
socket.on('event name', data => {// data}
- 发送音讯
socket.emit('event name', {// some data});
- 向其他人播送
socket.broadcast.emit('event name', {// some data});
- 向某个房间发送音讯
io.to(roomId).emit('event name', {// some data})
简易程序
let roomInfo = {};
io.on('connection', socket => {
let roomId = socket.handshake.query.roomId;
// user login
socket.on('login', data => {socket.join(roomId);
if (!(roomId in roomInfo)) {roomInfo[roomId] = [];}
let names = [];
let users = roomInfo[roomId];
if (users.length) {for (let i = 0; i < users.length; i++) {names.push(users[i].name);
}
if (!(names.includes(data.user))) {
users.push({
id: socket.id,
name: data.name,
avatar: data.avatar
})
}
} else {roomInfo[roomId].push({
id: socket.id,
name: data.name,
avatar: data.avatar
});
}
console.log('roomInfo:', roomInfo);
io.to(roomId).emit('system', {
name: data.name,
users: roomInfo[roomId]
})
})
// client msg
socket.on('message', data => {io.to(roomId).emit('chat', data);
})
// leave room
socket.on('leave', data => {let users = roomInfo[roomId];
if (users && users.length) {for (let i = 0; i < users.length; i++) {const user = users[i];
if (data.name == user.name) {users.splice(i, 1);
}
}
}
socket.leave(roomId);
io.to(roomId).emit('logout', {
name: data.name,
users: roomInfo[roomId]
})
console.log('roomInfo:', roomInfo);
})
socket.on('disconnect', () => {console.log('a user disconnect!');
})
});
前端方面
- 引入
socket.io.js
文件
<script src="./js/socket.io.js"></script>
- html 局部
登录界面:
<div class="chat">
<h2>XQ 聊天室 </h2>
<form class="chat-form">
<p>
<label for="name"> 昵称:</label>
<input type="text" id="name" name="name" placeholder="请输出用户名" required>
</p>
<p>
<label for="avatar"> 头像:</label>
<select name="avatar" id="avatar" required>
<option value="avatar1"> 头像 1 </option>
<option value="avatar2"> 头像 2 </option>
<option value="avatar3"> 头像 3 </option>
</select>
</p>
<p>
<label for="roomId"> 房间:</label>
<select name="roomId" id="roomId" required>
<option value="1"> 房间 1 </option>
<option value="2"> 房间 2 </option>
<option value="3"> 房间 3 </option>
</select>
</p>
<p>
<input type="submit" value="进入房间">
</p>
</form>
</div>
房间界面:
<div class="room">
<div class="room-header">
<h3>XQ 聊天室(<span class="count">0</span>)</h3>
<button class="logout"> 退出 </button>
</div>
<div class="room-nav">
<small> 在线人数:</small>
<span id="room-users"> 暂无成员 </span>
</div>
<ul class="room-content">
</ul>
<div class="room-footer">
<input class="room-ipt" type="text" placeholder="轻易写点儿吧">
<input class="room-btn" type="submit" value="发送">
</div>
</div>
- css 局部
body {
margin: 0;
padding: 0;
background: #f9f9f9;
}
h1,h2,h3,h4,h5,h6,p {margin: 0;}
ul,li {
margin: 0;
padding: 0;
list-style: none;
}
.chat {
box-sizing: border-box;
margin: 50px auto;
padding: 20px;
width: 300px;
height: auto;
background: #fff;
}
.chat.active {display: none;}
.chat h2 {
margin-bottom: 10px;
font-size: 18px;
line-height: 1.5;
text-align: center;
}
.chat-form p {
display: flex;
justify-content: space-between;
align-items: center;
padding: 5px 0;
font-size: 15px;
line-height: 35px;
}
.chat-form p label {width: 50px;}
.chat-form p input,
.chat-form p select {
flex: 1;
box-sizing: border-box;
padding: 0 10px;
height: 30px;
border: 1px solid #ccc;
outline: none;
background: none;
}
.chat-form p input:focus,
.chat-form p select:focus {box-shadow: 0 0 5px #ccc;}
.room {
display: none;
width: 100%;
height: 100vh;
overflow: hidden;
}
.room.active {
display: flex;
flex-direction: column;
}
.room-header {
position: relative;
display: flex;
justify-content: space-between;
align-items: center;
box-sizing: border-box;
padding: 0 15px;
height: 60px;
background: #111;
color: #fff;
}
.room-header h3 {
font-size: 18px;
text-align: left;
}
.room-header button {
width: 50px;
height: 50px;
background: none;
color: #fff;
outline: none;
border: none;
text-align: right;
}
.room-nav {
box-sizing: border-box;
padding: 20px 15px;
line-height: 30px;
font-size: 14px;
}
.room-nav small,
.room-nav span {font-size: 14px;}
.room-nav span {color: rgb(6, 90, 146);
}
.room-content {
flex: 1;
padding: 15px 0;
background: #fff;
border-top: 1px solid #ccc;
border-bottom: 1px solid #ccc;
background: #eee;
overflow-x: hidden;
overflow-y: auto;
}
.room-content li {
display: flex;
justify-content: flex-start;
align-items: flex-start;
box-sizing: border-box;
padding: 15px 10px;
margin-bottom: 10px;
width: 100%;
}
.room-content li .room-user {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.room-content li img {
display: inline-block;
width: 40px;
height: 40px;
}
.room-content li span {font-size: 14px;}
.room-des {
position: relative;
margin-top: 5px;
margin-left: 10px;
box-sizing: border-box;
padding: 3px 5px;
font-size: 14px;
line-height: 30px;
background: #ccc;
border-radius: 5px;
}
.room-des::before,
.room-des::after {
content: '';
position: absolute;
top: 10px;
width: 0;
height: 0;
border: 5px solid transparent;
}
.room-des::before {
display: inline-block;
left: -10px;
border-right: 5px solid #ccc;
}
.room-des::after {
display: none;
right: -10px;
border-left: 5px solid #fff;
}
.room-me {flex-direction: row-reverse;}
.room-me .room-des {
margin-left: 0;
margin-right: 10px;
background: #fff;
}
.room-me .room-des::before {display: none;}
.room-me .room-des::after {display: inline-block;}
.room-content .system {
justify-content: center;
align-items: center;
padding: 0;
height: 35px;
line-height: 35px;
}
.system p {
box-sizing: border-box;
padding: 0 5px;
font-size: 14px;
text-align: center;
border-radius: 5px;
background: #ccc;
}
.room-footer {
display: flex;
justify-content: space-between;
align-items: center;
height: 50px;
background: #fff;
border-top: 1px solid #ccc;
}
.room-footer .room-ipt {
margin-top: 1px;
box-sizing: border-box;
padding: 10px;
width: 80%;
height: 48px;
background: none;
border: 1px solid transparent;
outline: none;
}
.room-footer .room-ipt:focus {
border: 1px solid #ccc;
box-shadow: 0 0 5px #ccc;
}
.room-footer .room-btn {
width: 19%;
height: 100%;
background: rgb(2, 54, 112);
border: 1px solid #ccc;
outline: none;
font-size: 15px;
color: #fff;
}
- js 局部
登录界面的 js
let chat = document.querySelector('.chat');
let chatForm = document.querySelector('.chat-form');
let user = document.querySelector('#name');
let avatar = document.querySelector('#avatar');
let roomId = document.querySelector('#roomId');
// io
let socket = io.connect('/', {path: '/chat'});
// login
chatForm.onsubmit = function(){
let userInfo = {
name: user.value,
avatar: `/img/${avatar.value}.webp`,
roomId: roomId.value
}
localStorage.setItem('userInfo', JSON.stringify(userInfo));
checkLogin();
return false;
};
checkLogin();
function checkLogin () {let userInfo = localStorage.getItem('userInfo');
userInfo = JSON.parse(userInfo);
if (userInfo && userInfo.name) {chat.classList.add('active');
room.classList.add('active');
socket.emit('login', userInfo);
} else {chat.classList.remove('active');
room.classList.remove('active');
}
}
房间局部的 js
let room = document.querySelector('.room');
let logout = document.querySelector('.logout');
let count = document.querySelector('.count');
let roomUsers = document.querySelector('#room-users');
let roomContent = document.querySelector('.room-content');
let roomIpt = document.querySelector('.room-ipt');
let roomBtn = document.querySelector('.room-btn');
// 退出登录
logout.addEventListener('click', function(){let userInfo = localStorage.getItem('userInfo');
userInfo = JSON.parse(userInfo);
socket.emit('leave', userInfo);
alert('退出胜利!');
localStorage.removeItem('userInfo');
checkLogin();})
roomIpt.addEventListener('keyup', sendMsg, false);
roomBtn.addEventListener('click', sendMsg, false);
// 发送音讯
function sendMsg (e) {if (e.type === 'click' || e.code === 'Enter') {
let val = roomIpt.value;
if (val == '') {alert('聊天内容不能为空!');
return false;
}
let userInfo = localStorage.getItem('userInfo');
userInfo = JSON.parse(userInfo);
userInfo.msg = val;
roomIpt.value = '';
socket.emit('message', userInfo);
goBot();}
}
goBot();
// 到底部
function goBot () {roomContent.scrollTop = roomContent.scrollHeight;}
// 零碎音讯提醒
function welcome (user = 'mark', type = 1) {
roomContent.innerHTML += `
<li class="system">
<p> 零碎音讯:<strong>${user}</strong>${type == 1 ? '来到' : '来到'}本房间!</p>
</li>
`;
goBot();}
// 零碎音讯
socket.on('system', data => {
let strs = '';
welcome(data.name);
count.innerText = data.users.length;
for (const item of data.users) {strs += item.name + ',';}
roomUsers.innerText = '';
roomUsers.innerText += strs;
})
// 退出揭示
socket.on('logout', data => {
let strs = '';
welcome(data.name, 2);
count.innerText = data.users.length;
for (const item of data.users) {strs += item.name + ',';}
roomUsers.innerText = '';
roomUsers.innerText += strs;
})
// 承受音讯
socket.on('chat', data => {let userInfo = localStorage.getItem('userInfo');
userInfo = JSON.parse(userInfo);
let isUser = data.name == userInfo.name;
roomContent.innerHTML += `
<li ${isUser ? 'class="room-me"':''}>
<div class="room-user">
<img class="room-avatar" src="/chatroom/${(isUser ? userInfo.avatar : data.avatar) ||'/img/avatar1.webp'}" alt="">
<span class="room-name">${isUser ? userInfo.name : data.name}</span>
</div>
<p class="room-des">${data.msg}</p>
</li>
`;
goBot();})
成果体验
终于做好了,接下来来体验一下网上冲浪 –XQ 聊天室的美好生活吧!
- 进入房间
首先,输出你本人的昵称,抉择好头像和房间,点击进入房间按钮对话。
- 发送音讯
而后,输出音讯内容,点击发送按钮,或者按 Enter
回车也能够。
能够关上一个隐衷无痕窗口或者新的游览器,关上网址,输出另一个测试账号进行
这是 jerry 登录后的界面
这是 mark 看到的 jerry 发来的音讯
- 退出登录
如果聊天完结,能够点击右上方退出聊天室。
这是 jerry 退出登录后,mark 看到的界面
再来看一下后端打印出的用户信息。
- 这是 mark 登录当前记录的信息
- 这是 jerry 登录当前记录的信息
- 这是 jerry 退出当前记录的信息
以上就是一个繁难的聊天室和 node 中 websocket
的常识总结。