- 概念
- 使用
- 聊天室
- panel>(head+body+footer)
- 匿名聊天:emit发送,on接收,创建li,填li,插入ul
- 用户名设置
- 用户发送的第一条消息为用户名
- 消息改为对象:username,内容,创建时间
- 向所有进行广播broadcast.emit,首次消息为欢迎信息
- 私聊
- @ 用户
- 点击用户名出现@,事件冒泡为ul绑定事件,event.target.className
- 服务器接收,判断@ ,sockets对象接收{username:io对象},toSocket确定io对象
- 消息持久化
- Mysql连接,服务器接收,存数据库,消息对象添加id
- 删除消息,根据id,客户端发送del,服务器接收del,服务器处理后发送deled,客户端接收
- 房间
- 设置红绿房间
- 客户端发送房间名,服务器接收房间名,处理是加入或离开
- 服务端设置房间的io对象,接收时存到当前房间的对象中
- 完整聊天室
1. socket.io
- Socket.IO是一个WebSocket库,包括了客户端的js和服务器端的nodejs,它的目标是构建可以在不同浏览器和移动设备上使用的实时应用。
2. socket.io的特点
- 易用性:socket.io封装了服务端和客户端,使用起来非常简单方便。
- 跨平台:socket.io支持跨平台,这就意味着你有了更多的选择,可以在自己喜欢的平台下开发实时应用。
- 自适应:它会自动根据浏览器从WebSocket、AJAX长轮询、Iframe流等等各种方式中选择最佳的方式来实现网络实时应用,非常方便和人性化,而且支持的浏览器最低达IE5.5。
3. 使用
3.1 安装部署
$ cnpm install socket.io express -S
3.2 启动服务
- server.js 启动
$ nodemon server.js
- 安装nodemon:
cnpm i nodemon -g
let express = require('express');
let path = require('path');
let app = express();
app.get('/', (req, res) => {
res.sendFile(path.resolve('index.html'));
})
let server = require('http').createServer(app);
let io = require('socket.io')(server);
io.on('connection', socket => {
console.log('客户端已经连接');
socket.on('message', (msg) => {
console.log(msg);
socket.send('server: ' + msg);
})
})
server.listen(8080,()=>{
console.log('连接成功');
})
3.3 客户端引用
- 服务端运行后会在根目录动态生成socket.io的客户端js文件 客户端可以通过固定路径/socket.io/socket.io.js添加引用
客户端加载socket.io文件后会得到一个全局的对象io
connect函数可以接受一个url参数,url可以socket服务的http完整地址,也可以是相对路径,如果省略则表示默认连接当前路径
- index.html
<script src="/socket.io/socket.io.js"></script>
<script>
window.onload = function () {
let socket = io.connect('/');
// 监听与服务器端的连接事件
socket.on('connect', () => {
console.log('连接成功');
})
socket.on('disconnect', () => {
console.log('连接断开')
})
}
</script>
3.4 事件
- 发送:
socket.send('msg');;接收:socket.on('message', (msg)=>{};
send 函数只是 emit 的封装
node_modules\socket.io\lib\socket.js 源码
function send() {
var args = toArray(arguments);
args.unshift('message');
this.emit.apply(this, args);
return this;
}
* `emit`函数有两个参数
* 第一个参数是自定义的事件名称,发送方发送什么类型的事件名称,接收方就可以通过对应的事件名称来监听接收
* 第二个参数是要发送的数据
- 服务端事件
| name |
含义 |
| connection |
客户端成功连接到服务器 |
| message |
接收到客户端发送的消息 |
| disconnect |
客户端断开连接 |
| error |
监听错误 |
- 客户端事件
| name |
含义 |
| connect |
成功连接到服务器 |
| message |
接收到服务器发送的消息 |
| disconnect |
服务端断开连接 |
| error |
监听错误 |
4. 划分命名空间
- 可以把服务分成多个命名空间,默认'/',不同空间内不能通信
- index.html
<script src="/socket.io/socket.io.js"></script>
<script>
window.onload = function () {
var socket = io.connect('/');
//监听与服务器端的连接成功事件
socket.on('connect', function () {
console.log('连接成功');
socket.send('welcome');
});
socket.on('message', function (message) {
console.log(message);
});
//监听与服务器端断开连接事件
socket.on('disconnect', function () {
console.log('断开连接');
});
var news_socket = io.connect('/news');
//监听与服务器端的连接成功事件
news_socket.on('connect', function () {
console.log('连接成功');
socket.send('welcome');
});
news_socket.on('message', function (message) {
console.log(message);
});
//监听与服务器端断开连接事件
news_socket.on('disconnect', function () {
console.log('断开连接');
});
}
</script>
let express = require('express');
let path = require('path');
let app = express();
app.get('/', (req, res) => {
res.sendFile(path.resolve('index.html'));
})
let server = require('http').createServer(app);
let io = require('socket.io')(server);
io.on('connection', function (socket) { //向客户端发送消息
socket.send('/ 欢迎光临');
//接收到客户端发过来的消息时触发
socket.on('message', function (data) { console.log('/' + data); });
});
io.of('/news').on('connection', function (socket) {
//向客户端发送消息
socket.send('/news 欢迎光临');
//接收到客户端发过来的消息时触发
socket.on('message', function (data) { console.log('/news ' + data); });
});
server.listen(8080, () => {
console.log('服务器连接成功');
})
5. 消息持久化
- mySql数据库添加chat数据库,新加表message,表结构:id,username,content,createAt
- server.js
// 连接mysql数据库
let mysql = require('mysql');
let connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'root',
database: 'chat'
});
connection.connect();
// 向数据库chat的表message添加数据
connection.query(`INSERT INTO message(username,content,createAt) values(?,?,?)`, [messageObj.username, messageObj.content, messageObj.createAt.toLocaleString()], function (err, result) {
messageObj.id = result.insertId;//新插入的ID
io.emit('message', messageObj);
});
// 获取最新20条消息
connection.query(`SELECT * FROM message ORDER BY createAt DESC limit 20`, function (err, rows) {
rows.reverse();
socket.emit('allMessages', rows);
});
// 删除消息
connection.query(`DELETE FROM message WHERE id=?`, [id], function (err, result) {
io.emit('deled', id);
});
6. 服务
- 房间
- 可以把一个命名空间分成多个房间,一个客户端可以同时进入多个房间。
- 如果在大厅里广播 ,那么所有在大厅里的客户端和任何房间内的客户端都能收到消息。
- 所有在房间里的广播和通信都不会影响到房间以外的客户端
- 进入房间
socket.join('chat');
- 离开房间
socket.leave('chat');
-
- 获取房间列表
io.sockets.adapter.rooms
- 获取房间内的客户id值
let roomSockets = io.sockets.adapter.rooms[room].sockets;
- 广播
- 全局广播
- 向大厅和所有人房间内的人广播:
io.emit('message','全局广播');
- 向除了自己外的所有人广播:
socket.broadcast.emit('message', msg);
- 房间内广播
// 向myroom广播一个事件,在此房间内包括自己在内的所有客户端都会收到消息
io.in('myroom').emit('message', msg);
io.of('/news').in('myRoom').emit('message',msg);
* 客户端
// 向myroom广播一个事件,在此房间内除了自己外的所有客户端都会收到消息
socket.broadcast.to('myroom').emit('message', msg);
7. 聊天室1
//express+socket联合使用
//express负责 返回页面和样式等静态资源,socket.io负责 消息通信
let express = require('express');
const path = require('path');
let app = express();
app.get('/', function (req, res) {
res.sendFile(path.resolve(__dirname, 'index.html'));
});
let server = require('http').createServer(app);
let io = require('socket.io')(server);
//监听客户端发过来的连接
//命名是用来实现隔离的
let sockets = {};
io.on('connection', function (socket) {
//当前用户所有的房间
let rooms = [];
let username;//用户名刚开始的时候是undefined
//监听客户端发过来的消息
socket.on('message', function (message) {
if (username) {
//如果说在某个房间内的话那么他说的话只会说给房间内的人听
if (rooms.length > 0) {
for (let i = 0; i < rooms.length; i++) {
//在此处要判断是私聊还是公聊
let result = message.match(/@([^ ]+) (.+)/);
if (result) {
let toUser = result[1];
let content = result[2];
sockets[toUser].send({
username,
content,
createAt: new Date()
});
} else {
io.in(rooms[i]).emit('message', {
username,
content: message,
createAt: new Date()
});
}
}
} else {
//如果此用户不在任何一个房间内的话需要全局广播
let result = message.match(/@([^ ]+) (.+)/);
if (result) {
let toUser = result[1];
let content = result[2];
sockets[toUser].send({
username,
content,
createAt: new Date()
});
} else {
io.emit('message', {
username,
content: message,
createAt: new Date()
});
}
}
} else {
//如果用户名还没有设置过,那说明这是这个用户的第一次发言
username = message;
//在对象中缓存 key是用户名 值是socket
sockets[username] = socket;
socket.broadcast.emit('message', {
username: '系统',
content: `<a>${username}</a> 加入了聊天`,
createAt: new Date()
});
}
});
//监听客户端发过来的join类型的消息,参数是要加入的房间名
socket.on('join', function (roomName) {
let oldIndex = rooms.indexOf(roomName);
if (oldIndex == -1) {
socket.join(roomName);//相当于这个socket在服务器端进入了某个房间
rooms.push(roomName);
}
})
//当客户端告诉服务器说要离开的时候,则如果这个客户端就在房间内,则可以离开这个房间
socket.on('leave', function (roomName) {
let oldIndex = rooms.indexOf(roomName);
if (oldIndex != -1) {
socket.leave(roomName);
rooms.splice(oldIndex, 1);
}
});
socket.on('getRoomInfo', function () {
console.log(io);
//let rooms = io.manager.rooms;
console.log(io);
});
});
// io.of('/goods').on('connection', function (socket) {
// //监听客户端发过来的消息
// socket.on('message', function (message) {
// socket.send('goods:' + message);
// });
// });
server.listen(8080);
/**
* 1. 可以把服务分成多个命名空间,默认/,不同空间内不能通信
* 2. 可以把一个命名空间分成多个房间,一个客户端可以同时进入多个房间。
* 3. 如果在大厅里广播 ,那么所有在大厅里的客户端和任何房间内的客户端都能收到消息。
*/
<!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">
<title>聊天室</title>
<style>
.user {
color: red;
cursor: pointer;
}
</style>
<link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.1/css/bootstrap.css">
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">
<div class="row">
<div class="col-md-6">
<button id="join-red" class="btn btn-danger" onclick="join('red')">进入红房间</button>
<button id="leave-red" class="btn btn-danger" onclick="leave('red')">离开红房间</button>
</div>
<div class="col-md-6">
<button id="join-green" class="btn btn-success" onclick="join('green')">进入绿房间</button>
<button id="leave-green" class="btn btn-success" onclick="leave('green')">离开绿房间</button>
</div>
</div>
</div>
<div class="panel-body">
<ul style="height:400px;overflow-y: auto" class="list-group" id="messages" onclick="selectUser(event)">
</ul>
</div>
<div class="panel-footer">
<div class="row">
<div class="col-md-10">
<input type="text" class="form-control" id="txtMsg">
</div>
<div class="col-md-2">
<button type="button" class="btn btn-primary" onclick="send()">发言</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="/socket.io/socket.io.js"></script>
<script>
let txtMsg = document.getElementById('txtMsg');
let messagesUl = document.getElementById('messages');
const socket = io.connect('/');
socket.on('connect', function () {
console.log('连接成功!');
socket.emit('getAllMessages');
});
function formatMessage(msgObj) {
return `<span class="user">${msgObj.username}</span>:${msgObj.content}<button class="btn btn-danger" onclick="del('${msgObj.id}')">删除</button><span class="pull-right">${new Date(msgObj.createAt).toLocaleString()}</span>`;
}
socket.on('message', function (msgObj) {
let li = document.createElement('li');
li.id = `message_${msgObj.id}`;
li.className = 'list-group-item';
li.innerHTML = formatMessage(msgObj);
messagesUl.appendChild(li);
messagesUl.scrollTop = messagesUl.scrollHeight;
});
socket.on('allMessages', function (messages) {
let lis = messages.map(msgObj => (
`<li class="list-group-item">${formatMessage(msgObj)}</li>`
)).join('');
messagesUl.innerHTML = lis;
messagesUl.scrollTop = messagesUl.scrollHeight;
});
socket.on('disconnect', function () {
console.log('服务端断开连接!')
});
socket.on('error', function (error) {
console.log('客户端发生错误!', error)
});
socket.on('deled', function (id) {
let li = document.getElementById(`message_${id}`);
if (li) {
li.parentNode.removeChild(li);
}
});
function send() {
let value = txtMsg.value;
if (!value) {
return alert('发言内容不能为空!');
}
socket.emit('message', value);
txtMsg.value = '';
}
function del(id) {
//emit第一个参数是消息的类型,del ,还要把id传过去
socket.emit('del', id);
}
function selectUser(event) {
if (event.target.className == 'user') {
txtMsg.value = `@${event.target.innerText} `;
}
}
function join(roomName) {
socket.emit('join', roomName);
}
function leave(roomName) {
socket.emit('leave', roomName);
}
</script>
</body>
</html>
8 聊天室2
- 安装mysql,新建message数据库,新建chat数据表
- server.js
let express = require('express');
let http = require('http');
let path = require('path')
let app = express();
let mysql = require('mysql');
var connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'root',
database: 'chat'
});
connection.connect();
app.use(express.static(__dirname));
app.get('/', function (req, res) {
res.header('Content-Type', "text/html;charset=utf8");
res.sendFile(path.resolve('index.html'));
});
let server = http.createServer(app);
//因为websocket协议是要依赖http协议实现握手的,所以需要把httpserver的实例的传给socket.io
let io = require('socket.io')(server);
const SYSTEM = '系统';
//保存着所有的用户名和它的socket对象的对应关系
let sockets = {};
let mysockets = {};
let messages = [];//从旧往新旧的 slice
//在服务器监听客户端的连接
io.on('connection', function (socket) {
console.log('socket', socket.id)
mysockets[socket.id] = socket;
//用户名,默认为undefined
let username;
//放置着此客户端所在的房间
let rooms = [];
// 私聊的语法 @用户名 内容
socket.on('message', function (message) {
if (username) {
//首先要判断是私聊还是公聊
let result = message.match(/@([^ ]+) (.+)/);
if (result) {//有值表示匹配上了
let toUser = result[1];//toUser是一个用户名 socket
let content = result[2];
let toSocket = sockets[toUser];
if (toSocket) {
toSocket.send({
user: username,
content,
createAt: new Date()
});
} else {
socket.send({
user: SYSTEM,
content: `你私聊的用户不在线`,
createAt: new Date()
});
}
} else {//无值表示未匹配上
//对于客户端的发言,如果客户端不在任何一个房间内则认为是公共广播,大厅和所有的房间内的人都听的到。
//如果在某个房间内,则认为是向房间内广播 ,则只有它所在的房间的人才能看到,包括自己
let messageObj = {
user: username,
content: message,
createAt: new Date()
};
//相当于持久化消息对象
//messages.push(messageObj);
connection.query(`INSERT INTO message(user,content,createAt) VALUES(?,?,?)`, [messageObj.user, messageObj.content, messageObj.createAt], function (err, results) {
console.log(results);
});
if (rooms.length > 0) {
/**
socket.emit('message', {
user: username,
content: message,
createAt: new Date()
});
rooms.forEach(room => {
//向房间内的所有的人广播 ,包括自己
io.in(room).emit('message', {
user: username,
content: message,
createAt: new Date()
});
//如何向房间内除了自己之外的其它人广播
socket.broadcast.to(room).emit('message', {
user: username,
content: message,
createAt: new Date()
});
});
*/
let targetSockets = {};
rooms.forEach(room => {
let roomSockets = io.sockets.adapter.rooms[room].sockets;
console.log('roomSockets', roomSockets);//{id1:true,id2:true}
Object.keys(roomSockets).forEach(socketId => {
if (!targetSockets[socketId]) {
targetSockets[socketId] = true;
}
});
});
Object.keys(targetSockets).forEach(socketId => {
mysockets[socketId].emit('message', messageObj);
});
} else {
io.emit('message', messageObj);
}
}
} else {
//把此用户的第一次发言当成用户名
username = message;
//当得到用户名之后,把socket赋给sockets[username]
sockets[username] = socket;
//socket.broadcast表示向除自己以外的所有的人广播
socket.broadcast.emit('message', { user: SYSTEM, content: `${username}加入了聊天室`, createAt: new Date() });
}
});
socket.on('join', function (roomName) {
if (rooms.indexOf(roomName) == -1) {
//socket.join表示进入某个房间
socket.join(roomName);
rooms.push(roomName);
socket.send({
user: SYSTEM,
content: `你成功进入了${roomName}房间!`,
createAt: new Date()
});
//告诉客户端你已经成功进入了某个房间
socket.emit('joined', roomName);
} else {
socket.send({
user: SYSTEM,
content: `你已经在${roomName}房间了!请不要重复进入!`,
createAt: new Date()
});
}
});
socket.on('leave', function (roomName) {
let index = rooms.indexOf(roomName);
if (index == -1) {
socket.send({
user: SYSTEM,
content: `你并不在${roomName}房间,离开个毛!`,
createAt: new Date()
});
} else {
socket.leave(roomName);
rooms.splice(index, 1);
socket.send({
user: SYSTEM,
content: `你已经离开了${roomName}房间!`,
createAt: new Date()
});
socket.emit('leaved', roomName);
}
});
socket.on('getAllMessages', function () {
//let latestMessages = messages.slice(messages.length - 20);
connection.query(`SELECT * FROM message ORDER BY id DESC limit 20`, function (err, results) {
// 21 20 ........2
socket.emit('allMessages', results.reverse());// 2 .... 21
});
});
});
server.listen(8080);
/**
* socket.send 向某个人说话
* io.emit('message'); 向所有的客户端说话
*
*/
<!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://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u"
crossorigin="anonymous">
<style>
.user {
color: red;
cursor: pointer;
}
</style>
<title>socket.io</title>
</head>
<body>
<div class="container" style="margin-top:30px;">
<div class="row">
<div class="col-xs-12">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="text-center">欢迎来到聊天室</h4>
<div class="row">
<div class="col-xs-6 text-center">
<button id="join-red" onclick="join('red')" class="btn btn-danger">进入红房间</button>
<button id="leave-red" style="display: none" onclick="leave('red')" class="btn btn-danger">离开红房间</button>
</div>
<div class="col-xs-6 text-center">
<button id="join-green" onclick="join('green')" class="btn btn-success">进入绿房间</button>
<button id="leave-green" style="display: none" onclick="leave('green')" class="btn btn-success">离开绿房间</button>
</div>
</div>
</div>
<div class="panel-body">
<ul id="messages" class="list-group" onclick="talkTo(event)" style="height:500px;overflow-y:scroll">
</ul>
</div>
<div class="panel-footer">
<div class="row">
<div class="col-xs-11">
<input onkeyup="onKey(event)" type="text" class="form-control" id="content">
</div>
<div class="col-xs-1">
<button class="btn btn-primary" onclick="send(event)">发言</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="/socket.io/socket.io.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa"
crossorigin="anonymous"></script>
<script>
let contentInput = document.getElementById('content');//输入框
let messagesUl = document.getElementById('messages');//列表
let socket = io('/');//io new Websocket();
socket.on('connect', function () {
console.log('客户端连接成功');
//告诉服务器,我是一个新的客户,请给我最近的20条消息
socket.emit('getAllMessages');
});
socket.on('allMessages', function (messages) {
let html = messages.map(messageObj => `
<li class="list-group-item"><span class="user">${messageObj.user}</span>:${messageObj.content} <span class="pull-right">${new Date(messageObj.createAt).toLocaleString()}</span></li>
`).join('');
messagesUl.innerHTML = html;
messagesUl.scrollTop = messagesUl.scrollHeight;
});
socket.on('message', function (messageObj) {
let li = document.createElement('li');
li.className = "list-group-item";
li.innerHTML = `<span class="user">${messageObj.user}</span>:${messageObj.content} <span class="pull-right">${new Date(messageObj.createAt).toLocaleString()}</span>`;
messagesUl.appendChild(li);
messagesUl.scrollTop = messagesUl.scrollHeight;
});
// click delegate
function talkTo(event) {
if (event.target.className == 'user') {
let username = event.target.innerText;
contentInput.value = `@${username} `;
}
}
//进入某个房间
function join(roomName) {
//告诉服务器,我这个客户端将要在服务器进入某个房间
socket.emit('join', roomName);
}
socket.on('joined', function (roomName) {
document.querySelector(`
document.querySelector(`
});
socket.on('leaved', function (roomName) {
document.querySelector(`
document.querySelector(`
});
//离开某个房间
function leave(roomName) {
socket.emit('leave', roomName);
}
function send() {
let content = contentInput.value;
if (content) {
socket.send(content);
contentInput.value = '';
} else {
alert('聊天信息不能为空!');
}
}
function onKey(event) {
let code = event.keyCode;
if (code == 13) {
send();
}
}
</script>
</body>
</html>