websocket+nodejs实现聊天室
websocket+nodejs实现聊天室
一、websocket
1、websocket简介
传统http协议,是基于请求和响应的,无法直接做到客户端向客户端发送消息。
websocket协议是基于tcp的一种新的网络协议。实现了浏览器与服务器全双工通信。全双工:客户端可以主动给服务器发送消息,服务器也可以主动给客户端发送消息。
websocket是一种持久协议,http是非持久的
传统http协议实现即使聊天,必须通过ajax轮询,就是客户端会一直向服务器发送请求来确认是否有消息,浪费性能和资源。
2、使用websocket
2.1、在H5中使用websocket
<!DOCTYPE html><htmllang="en">
<head>
<metacharset="UTF-8">
<title>Title</title>
<style>
div {
width: 200px;
height: 200px;
border: 1px solid #000;
}
</style>
</head>
<body>
<inputtype="text"placeholder="请输入">
<button>发送</button>
<!-- 显示内容 -->
<div></div>
<!-- websocket在浏览器的使用 H5提供了websocket的API-->
<script>
let input = document.querySelector('input');
let button = document.querySelector('button');
let div = document.querySelector('div');
//创建WebSocket('WebSocket服务器地址')
let socket = new WebSocket('ws://echo.websocket.org');
//监听WebSocket事件 open和WebSocket服务器连接成功触发
socket.addEventListener('open',()=>{
div.innerHTML = '连接成功';
});
//给webSocket发送消息
button.addEventListener('click',()=>{
let value = input.value;
socket.send(value);
});
//接受websocket服务的消息
socket.addEventListener('message',(msg)=>{
console.log(msg.data);
//把消息显示到div
div.innerHTML = msg.data;
});
//端口服务
socket.addEventListener('close',()=>{
console.log('服务断开');
});
</script>
</body>
</html>
2.2、使用nodejs开发websocket
使用nodejs开发websocket需要一个依赖包 Nodejs Webs
npm i nodejs-websocket
app.js代码
const ws = require('nodejs-websocket');const PORT = 3000
//创建server,每次只要有用户连接,回调执行就会给用户创建一个connect对象
const server = ws.createServer(connect => {
console.log('用户连接成功');
//用户传来数据,触发text事件
connect.on('text', data => {
console.log(`接受到用户的数据:${data}`);
//接受到数据后给用户响应数据
connect.sendText(data);
});
//连接关闭触发close事件
connect.on('close',()=>{
console.log('连接断开');
});
//注册error事件,用户端口后就会触发该异常
connect.on('error',()=>{
console.log('用户连接异常');
});
});
server.listen(PORT, () => {
console.log('监听3000');
});
把html的websocket服务器地址改为'ws://localhost:3000/'
运行效果
控制台
3、使用websocket开发一个简单的聊天室
app.js
const ws = require('nodejs-websocket');//记录当前连接的用户数量
let count = 0;
const server = ws.createServer(conn => {
console.log('新连接');
count++;
//给用户一个固定的名字
conn.userName = `用户${count}`;
//告诉所有用户,有人加入聊天室
broadcast(`${conn.userName}加入聊天室`);
//接受到客户端的数据触发该事件
conn.on('text', data => {
//接受到某个用户的数据,告诉所有的用户此消息,广播
broadcast(data);
});
//关闭连接
conn.on('close', () => {
console.log('关闭连接')
count--;
//有人退出也告诉所有的用户
broadcast(`${conn.userName}离开了聊天室`)
});
//发送异常
conn.on('error', () => {
console.log('异常');
});
});
//广播
const broadcast = (msg) => {
//server.connection表示所有的用户
server.connections.forEach(item => {
//遍历出每个用户,挨个发消息
item.send(msg);
});
}
server.listen(3000, () => {
console.log('监听3000');
});
index.html
<!DOCTYPE html><htmllang="en">
<head>
<metacharset="UTF-8">
<title>Title</title>
<style>
/*div {
width: 200px;
height: 200px;
border: 1px solid #000;
}*/
</style>
</head>
<body>
<inputtype="text"placeholder="请输入">
<button>发送</button>
<!-- 显示内容 -->
<div></div>
<!-- websocket在浏览器的使用 H5提供了websocket的API-->
<script>
let input = document.querySelector('input');
let button = document.querySelector('button');
let div = document.querySelector('div');
//创建WebSocket('WebSocket服务器地址')
let socket = new WebSocket('ws://localhost:3000/');
//监听WebSocket事件 open和WebSocket服务器连接成功触发
socket.addEventListener('open',()=>{
div.innerHTML = '连接成功';
});
//给webSocket发送消息
button.addEventListener('click',()=>{
let value = input.value;
socket.send(value);
input.value = '';
});
//接受websocket服务的消息
socket.addEventListener('message',(msg)=>{
console.log(msg.data);
//把消息显示到div,以追加的方式
let dv = document.createElement('div');
dv.innerHTML = msg.data;
div.appendChild(dv);
});
//端口服务
socket.addEventListener('close',()=>{
console.log('服务断开');
});
</script>
</body>
</html>
运行效果
3.1、优化聊天室消息效果
把消息优化为一个对象:
- type:消息的类型
- 0:表示进入聊天室的消息
- 1:表示离开聊天室的消息
- 2:正常聊天
- msg:消息的内容
- time:聊天的具体时间
- 注意:发送的时候要把这个对象转换为JSON格式
app.js
const ws = require('nodejs-websocket');//进入消息
const TYPE_ENTER = 0;
//离开消息
const TYPE_LEAVE = 1;
//正常消息
const TYPE_MSG = 2;
//记录当前连接的用户数量
let count = 0;
const server = ws.createServer(conn => {
console.log('新连接');
count++;
//给用户一个固定的名字
conn.userName = `用户${count}`;
//告诉所有用户,有人加入聊天室
broadcast({
type: TYPE_ENTER,
msg: `${conn.userName}加入聊天室`,
time: newDate().toLocaleDateString()
});
//接受到客户端的数据触发该事件
conn.on('text', data => {
//接受到某个用户的数据,告诉所有的用户此消息,广播
broadcast({
type: TYPE_MSG,
msg: data,
time: newDate().toLocaleDateString()
});
});
//关闭连接
conn.on('close', () => {
console.log('关闭连接')
count--;
//有人退出也告诉所有的用户
broadcast({
type: TYPE_LEAVE,
msg: `${conn.userName}离开了聊天室`,
time: newDate().toLocaleDateString()
})
});
//发送异常
conn.on('error', () => {
console.log('异常');
});
});
//广播
const broadcast = (msg) => {
//server.connection表示所有的用户
server.connections.forEach(item => {
//遍历出每个用户,挨个发消息
item.send(JSON.stringify(msg));
});
}
server.listen(3000, () => {
console.log('监听3000');
});
html的改变
const TYPE_ENTER = 0;const TYPE_LEAVE = 1;
const TYPE_MSG = 2;
//接受websocket服务的消息
socket.addEventListener('message',(e)=>{
let data = JSON.parse(e.data);
//把消息显示到div,以追加的方式
let dv = document.createElement('div');
//为不同的消息类型添加效果
dv.innerHTML = data.msg + '---' + data.time;
if (data.type === TYPE_ENTER) {
dv.style.color = 'green';
} elseif (data.type === TYPE_LEAVE){
dv.style.color = 'red';
} else {
dv.style.color = 'blue';
}
div.appendChild(dv);
});
运行效果
二、socketio基本使用
2.1、无框架
Socket.IO是一个库,用于在浏览器和服务器之间实现实时、双向和基于事件的通信。
Socket.IO不是WebSocket 的实现。
安装socketio
npm i --save socket.io
app.js
//创建服务器const http = require('http');
const fs = require('fs');
const app = http.createServer();
//监听request事件,请求服务时返回index.html
app.on('request', (req, res) => {
fs.readFile(__dirname + '/index.html',
(err, data) => {
if (err) {
res.writeHead(500);
return res.end('Error loading index.html');
}
res.writeHead(200);
res.end(data);
});
})
app.listen(3000, () => {
console.log('监听3000');
});
//socketio依赖于nodejs服务器
const io = require('socket.io')(app);
//监听用户连接的事件
//socket表示用户的连接
//socket emit表示触发某个事件 如果向浏览器发送一个数据,需要触发浏览器注册的某个事件
//socket on表示注册某个事件,如果需要获取浏览器数据,就需要注册一个事件,等待浏览器触发
io.on('connection', socket => {
console.log('新用户连接');
//给浏览器发送数据emit('发送的事件','发送的事件')
socket.emit('send', {name: 'jack'});
//获取浏览器发送的数据,注册事件只要和触发事件一样就行
socket.on('clientData',data=>{
console.log(data);
})
});
index.html
<!DOCTYPE html><htmllang="en">
<head>
<metacharset="UTF-8">
<title>Title</title>
</head>
<body>
hello
<scriptsrc="/socket.io/socket.io.js"></script>
<script>
//连接socket服务
let socket = io('http://localhost:3000');
//浏览器注册服务端
socket.on('send',data =>{
console.log(data);
});
//向服务器发送数据
socket.emit('clientData',{name:'jack'});
</script>
</body>
</html>
发送数据使用emit,接受数据使用on。浏览器或服务器给对方发送数据,只需要触发相应的事件。
2.2、基于express框架的socketio
安装express
npm i express
app.js
const app = require('express')();const server = require('http').Server(app);
const io = require('socket.io')(server);
server.listen(3000,()=>{
console.log('监听3000');
});
app.get('/', (req, res) => {
res.sendFile(__dirname + '/index.html');
});
io.on('connection', (socket) => {
socket.on('clientData', (data) => {
console.log(data);
});
});
index.html
<!DOCTYPE html><htmllang="en">
<head>
<metacharset="UTF-8">
<title>Title</title>
</head>
<body>
hello
<scriptsrc="/socket.io/socket.io.js"></script>
<script>
let socket = io('http://localhost:3000');
socket.emit('clientData', { name: 'jack' });
</script>
</body>
</html>
三、使用socketio开发聊天室
导入静态资源
目录结构
1、服务端搭建
app.js
/*启动聊天室服务端的程序
*/
const app = require('express')();
const server = require('http').Server(app);
const io = require('socket.io')(server);
server.listen(3000,()=>{
console.log('服务器启动成功');
});
//处理静态资源,把public目录设置为静态资源
app.use(require('express').static('public'));
app.get('/', (req, res) => {
//重定向到首页
res.redirect('/index.html');
});
io.on('connection', (socket) => {
console.log('新用户连接');
});
2、登录功能
2.1、登录功校验
js/index.js
/*聊天室主要功能
*/
//连接socketio
let socket = io('http://localhost:3000');
/*
登录功能
*/
//选择头像,在li上注册一个点击事件,加now类 addClass:添加一个类 siblings:移除其他li上的类
$('#login_avatar li').on('click', function () {
$(this).addClass('now').siblings().removeClass('now');
});
//点击按钮登录
$('#loginBtn').on('click',function () {
//获取用户名,去空格
let username = $('#username').val().trim();
if (!username) {
alert('请输入用户名');
return
}
//获取头像 li.now表选中的li attr('src')拿到头像具体路径
let avatar = $('#login_avatar li.now img').attr('src');
//和服务器通信,把用户名和头像传输到服务器
socket.emit('login',{
username: username,
avatar: avatar
});
});
//监听登录失败的请求
socket.on('loginError',data=>{
alert('登录失败,用户名已经存在');
});
//监听登录成功的请求
socket.on('loginSuccess',data=>{
alert('登录成功');
});
app.js
//保存所有登录过的用户const users = [];
//监听用户连接
io.on('connection', (socket) => {
console.log('新用户连接');
//监听客户端发送的login请求
socket.on('login',data=>{
//判断是否重名,从数组中找是否有这个名字
let user = users.find(item => item.username === data.username);
if (user) {
//该用户存在,登录失败
socket.emit('loginError',{msg:'登录失败'});
}else {
//该用户不存在,登录成功 先把用户存到数组中
users.push(data);
socket.emit('loginSuccess',data);
}
});
});
2.2、登录成功显示个人信息
js/index.js
//监听登录成功的请求socket.on('loginSuccess',data=>{
//显示聊天框,隐藏聊天窗口 fadeOut,fadeIn淡出淡入效果
$('.login_box').fadeOut();
$('.container').fadeIn();
//设置登录成功后的用户信息
$('.avatar_url').attr('src',data.avatar);
$('.user-list .username').text(data.username);
});
2.3、广播新用户加入群聊
app.js
//监听用户连接io.on('connection', (socket) => {
console.log('新用户连接');
//监听客户端发送的login请求
socket.on('login',data=>{
//判断是否重名,从数组中找是否有这个名字
let user = users.find(item => item.username === data.username);
if (user) {
//该用户存在,登录失败
socket.emit('loginError',{msg:'登录失败'});
}else {
//该用户不存在,登录成功 先把用户存到数组中
users.push(data);
socket.emit('loginSuccess',data);
//广播消息,有人加入到聊天室 io.emit广播事件
io.emit('addUser',data);
}
});
});
js/index.js
//监听新用户加入的请求socket.on('addUser', data => {
//添加一条系统消息
$('.box-bd').append(
`<div class="system">
<p class="message_system">
<span class="content">${data.username}加入了群聊</span>
</p>
</div>`
)
});
2.4、显示用户列表和用户数量
app.js
//监听用户连接io.on('connection', (socket) => {
console.log('新用户连接');
//监听客户端发送的login请求
socket.on('login',data=>{
//判断是否重名,从数组中找是否有这个名字
let user = users.find(item => item.username === data.username);
if (user) {
//该用户存在,登录失败
socket.emit('loginError',{msg:'登录失败'});
}else {
//该用户不存在,登录成功 先把用户存到数组中
users.push(data);
socket.emit('loginSuccess',data);
//广播消息,有人加入到聊天室 io.emit广播事件
io.emit('addUser',data);
//显示目前聊天室的人数
io.emit('userList',users);
}
});
});
js/index.js
/监听用户列表消息socket.on('userList', data => {
//把userList中的数据动态显示到左侧菜单
$('.user-list ul').html(''); //每次循环完把列表设为空,否则会重复叠加
data.forEach(item => {
$('.user-list ul').append(
`<li class="user">
<div class="avatar"><img src="https://juejin.im/post/5ed5c649e51d45788325f846/${item.avatar}" alt=""/></div>
<div class="name">${item.username}</div>
</li>`
)
}
)
//显示用户人数
$('#userCount').text(data.length);
});
3、离开聊天室功能
app.js
//监听用户断开连接 disconnect断开连接事件socket.on('disconnect', () => {
//把当前用户信息从users中删除掉,findIndex找到当前用户的下标
let idx = users.findIndex(item => item.username === socket.username);
//删除
users.splice(idx,1);
//广播有人离开聊天室
io.emit('delUser',{
username:socket.username,
avatar: socket.avatar
})
//更新userList
io.emit('userList', users);
})
js/index.js
//监听删除用户消息socket.on('delUser', data => {
//添加一条系统消息
$('.box-bd').append(
`<div class="system">
<p class="message_system">
<span class="content">${data.username}离开了群聊</span>
</p>
</div>`
)
});
4、聊天功能
4.1、基本聊天功能实现
app.js
//监听聊天信息socket.on('sendMessage',data=>{
console.log(data);
//广播消息,如有数据库在此把数据存储
io.emit('receiveMessage',data);
})
js/index.js
//定义两个全局变量保存username和avatarvar username;
var avatar;
//登录成功后保存用户信息
username = data.username;
avatar = data.avatar;
//聊天功能
$('.btn-send').on('click', () => {
//获取聊天内容
let content = $('#content').val().trim();
//制空聊天区
$('#content').val('');
//判断是否空数据
if (!content) {
return alert('请输入内容');
}
//发给服务器
socket.emit('sendMessage', {
msg: content,
username: username,
avatar: avatar
})
})
//监听聊天消息
socket.on('receiveMessage', data => {
//把接受到的消息显示在聊天窗口
//判断消息是自己或别人的
if (data.username === username) {
//自己的消息
$('.box-bd').append(
` <div class="message-box">
<div class="my message">
<img class="avatar" src="https://juejin.im/post/5ed5c649e51d45788325f846/${data.avatar}" alt=""/>
<div class="content">
<div class="bubble">
<div class="bubble_cont">${data.msg}</div>
</div>
</div>
</div>
</div>`
);
} else {
//别人的消息
$('.box-bd').append(
`<div class="message-box">
<div class="other message">
<img class="avatar" src="https://juejin.im/post/5ed5c649e51d45788325f846/${data.avatar}" alt=""/>
<div class="content">
<div class="nickname">${data.username}</div>
<div class="bubble">
<div class="bubble_cont">${data.msg}</div>
</div>
</div>
</div>
</div>`
);
}
})
4.2、在发送完消息自动滚动到底部
Element.scrollIntoView() 方法让当前的元素滚动到浏览器窗口的可视区域内。
- 如果为true,元素的顶端将和其所在滚动区的可视区域的顶端对齐。
- 如果为false,元素的底端将和其所在滚动区的可视区域的底端对齐。
js/index.js
functionscrollIntoView() {//使用scrollIntoView()滚动到底部,children(':last')表示找到最后一个子元素,get(0)获取元素的dom对象
$('.box-bd').children(':last').get(0).scrollIntoView(false);
}
每个发消息的监听都使用scrollIntoView方法,包括系统通知
4.3、发送图片
app.js
//发送图片 change表示该文件的选择$('#file').on('change', function () {
//拿到上传的文件
var file = this.files[0];
//把文件发送的服务器,使用H5的功能fileReader,读取上传的文件
var fr = new FileReader();
fr.readAsDataURL(file);
fr.onload = function () {
socket.emit('sendImage', {
username: username,
avatar: avatar,
img: fr.result
})
}
})
//监听图片信息
socket.on('receiveImage', data => {
//把接受到的消息显示在聊天窗口
//判断消息是自己或别人的
if (data.username === username) {
//自己的消息
$('.box-bd').append(
` <div class="message-box">
<div class="my message">
<img class="avatar" src="https://juejin.im/post/5ed5c649e51d45788325f846/${data.avatar}" alt=""/>
<div class="content">
<div class="bubble">
<div class="bubble_cont">
<img src="https://juejin.im/post/5ed5c649e51d45788325f846/${data.img}">
</div>
</div>
</div>
</div>
</div>`
);
} else {
//别人的消息
$('.box-bd').append(
`<div class="message-box">
<div class="other message">
<img class="avatar" src="https://juejin.im/post/5ed5c649e51d45788325f846/${data.avatar}" alt=""/>
<div class="content">
<div class="nickname">${data.username}</div>
<div class="bubble">
<div class="bubble_cont">
<img src="https://juejin.im/post/5ed5c649e51d45788325f846/${data.img}">
</div>
</div>
</div>
</div>
</div>`
);
}
//等待图片加载完再滚动
$('.box-bd img:last').on('load', function () {
scrollIntoView();
});
});
js/index.js
//接受图片信息socket.on('sendImage',data=>{
//广播消息,如有数据库在此把数据存储
io.emit('receiveImage',data);
})
4.3、发送表情
使用Jquery emoji 插件
引入
在index.html引入css
<linkrel="stylesheet"href="lib/jquery-mCustomScrollbar/css/jquery.mCustomScrollbar.min.css"/><linkrel="stylesheet"href="lib/jquery-emoji/css/jquery.emoji.css"/>
引入js
<scriptsrc="lib/jquery-mCustomScrollbar/script/jquery.mCustomScrollbar.min.js"></script><scriptsrc="lib/jquery-emoji/js/jquery.emoji.min.js"></script>
emoji()参数参考
js/index.js
//发送表情//初始化jQuery-emoji插件
$('.face').on('click', function () {
$('#content').emoji({
//触发表情的按钮
button: '.face',
showTab: false,
animation: 'slide',
position: 'topRight',
icons: [{
name: "QQ表情",
path: "../lib/jquery-emoji/img/qq/",
maxNum: 91,
excludeNums: [41, 45, 54],
file: ".gif"
}]
});
})
注意:把index.html输入内容区域的textarea类改为div类,否则不支持表情的显示,但是div本身不支持编辑内容,再加上contenteditable,index.html改完还要改一下index.js里获取内容的方式,用原来的val获取不到div的内容,改用html()
项目运行截图
GitHub地址:github.com/812467457/w…
以上是 websocket+nodejs实现聊天室 的全部内容, 来源链接: utcz.com/a/19734.html