socket.io是一個websocket庫,包含客戶端的js和服務端的node.js,可以在不同瀏覽器和移動設備上構建實時應用。
一、安裝 socket.io
npm install socket.io
二、通過socket.io創建一個簡單應用
const http = require('http');
const path = require('path');
const express = require('express');
//創建一個應用,注意app其實就是一個函數,類似function(req, res) {}
let app = express();
//創建一個http服務器,既然app是一個函數,那這里就可以傳入。
let server = http.createServer(app);
//注意,websocket的握手是需要依賴http服務的,所以這里要把server傳入進去。
let io = require('socket.io')(server);
app.get('/', function (req, res) {
res.sendFile(path.join(__dirname, 'index.html'));
});
//有新的客戶端連接時觸發
io.on('connection', function (socket) {
//接收到消息時觸發
socket.on('message', function (data) {
console.log('服務端收到 : ', data);
//注意send()方法其實是發送一個 'message' 事件
//客戶端要通過on('message')來響應
socket.send('你好客戶端, ' + data);
});
//發生錯誤時觸發
socket.on('error', function (err) {
console.log(err);
});
});
server.listen(8888);
index.html的代碼:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<input type="text" id="msg">
<input type="button" id="send" value="發送">
<ul id="receive"></ul>
<!-- /socket.io/socket.io.js 這個引用路徑是固定的,socket.io會自動幫我們解析 -->
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io.connect('http://localhost:8888');
//連接成功時觸發
socket.on('connect', function () {
console.log('連接成功');
});
//連接斷開時觸發
socket.on('disconnect', function () {
console.log('連接斷開');
});
//收到消息時觸發
socket.on('message', function (data) {
var node = document.createElement("li");
node.innerHTML = "客戶端收到 : " + data;
document.querySelector("#receive").appendChild(node);
});
document.querySelector("#send").onclick = function () {
var msg = document.querySelector("#msg").value;
socket.send(msg);
};
</script>
</body>
</html>
這樣我們就可以在客戶端建立與服務端的實時消息傳送。注意 send() 方法只是 emit 方法的封裝,等同於 emit('message', args)。
我們通過 emit 方法可以自定義的發送事件,並監聽事件。
const http = require('http');
const path = require('path');
let app = require('express')();
let server = http.createServer(app);
let io = require('socket.io')(server);
app.get('/', function (req, res) {
res.sendFile(path.join(__dirname, 'index.html'));
});
io.on('connection', function (socket) {
socket.on('message', function (data) {
console.log('服務端收到 : ', data);
socket.send('你好客戶端, ' + data);
});
//監聽自定義事件
socket.on('myevent', function (data) {
console.log('客戶端發送了一個自定義事件', data);
});
});
server.listen(8888);
index.html的代碼:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<input type="text" id="msg">
<input type="button" id="send" value="發送">
<input type="button" id="event" value="發送自定義事件">
<ul id="receive"></ul>
<!-- /socket.io/socket.io.js 這個引用路徑是固定的,socket.io會自動幫我們解析 -->
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io.connect('http://localhost:8888');
//連接成功時觸發
socket.on('connect', function () {
console.log('連接成功');
});
//連接斷開時觸發
socket.on('disconnect', function () {
console.log('連接斷開');
});
//收到消息時觸發
socket.on('message', function (data) {
var node = document.createElement("li");
node.innerHTML = "客戶端收到 : " + data;
document.querySelector("#receive").appendChild(node);
});
document.querySelector("#send").onclick = function () {
var msg = document.querySelector("#msg").value;
socket.send(msg);
};
document.querySelector("#event").onclick = function () {
var msg = document.querySelector("#msg").value;
//參數一表示,事件的名稱
//參數二表示,要發送的數據
socket.emit("myevent", msg);
};
</script>
</body>
</html>
emit() 或 send() 還有第三個參數,用來設置消息發送成功后的回執。
const http = require('http');
const path = require('path');
const express = require('express');
let app = express();
let server = http.createServer(app);
let io = require('socket.io')(server);
app.use(express.static(path.join(__dirname)));
app.get('/', function (req, res) {
res.sendFile(path.join(__dirname, 'index.html'));
});
io.on('connection', function (socket) {
socket.on('message', function (data, callback) {
socket.send('服務器發送 : ' + data, function (data) {
console.log(data);
});
//這里callback傳入的參數會傳遞到客戶端的send()回調函數里
callback('服務端的回執');
});
});
server.listen(8888);
index.html的代碼:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<input type="text" id="msg">
<input type="button" id="send" value="發送">
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io.connect('http://localhost:8888');
socket.on('connect', function () {
console.log('連接成功');
});
socket.on('message', function (data, callback) {
console.log('客戶端收到 : ', data);
//這里callback傳入的值會傳遞到服務端的send()回調函數里
callback('客戶端的回執');
});
document.querySelector("#send").onclick = function () {
var msg = document.querySelector("#msg").value;
socket.send(msg, function (data) {
console.log(data);
});
};
</script>
</body>
</html>
三、socket.io命名空間的概念
有些時候我們需要按不同的模塊或功能去傳遞不同的消息,比如在 /user 模塊下推送用戶信息,在 /order 模塊下推送訂單信息,兩者間互不干擾。
這個時候就需要用到命名空間了,socket.io把不同命名空間下的消息和事件分隔開了。
const http = require('http');
const path = require('path');
const express = require('express');
let app = express();
let server = http.createServer(app);
let io = require('socket.io')(server);
app.use(express.static(path.join(__dirname)));
app.get('/', function (req, res) {
res.sendFile(path.join(__dirname, 'index.html'));
});
//通過of()設置命名空間
//注意,如果沒加of(),則默認使用'/'命名空間
io.of('/user').on('connection', function (socket) {
socket.on('message', function (data) {
console.log('/user : ', data);
//注意send()只會發送給當前客戶端
//如果要進行群發
//用 io.of(命名空間).send() 發送命名空間下所有客戶端,包括發送者。
//或者 socket.broadcast.send() 發送命名空間下所有客戶端,不包括發送者。
//io.of('/user').send('服務端發送 : ' + data);
socket.broadcast.send('服務端發送 : ' + data);
});
});
io.of('/order').on('connection', function (socket) {
socket.on('message', function (data) {
console.log('/order : ', data);
socket.broadcast.send('服務端發送 : ' + data);
});
});
server.listen(8888);
user.html的代碼:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<input type="text" id="msg">
<input type="button" id="send" value="發送">
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io.connect('http://localhost:8888/user');
socket.on('connect', function () {
console.log('連接成功');
});
socket.on('message', function (data) {
console.log('客戶端收到 : ', data);
});
document.querySelector("#send").onclick = function () {
var msg = document.querySelector("#msg").value;
//注意客戶端的send()會發送到當前的socket命名空間下
socket.send(msg);
};
</script>
</body>
</html>
order.html的代碼:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<input type="text" id="msg">
<input type="button" id="send" value="發送">
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io.connect('http://localhost:8888/order');
socket.on('connect', function () {
console.log('連接成功');
});
socket.on('message', function (data) {
console.log('客戶端收到 : ', data);
});
document.querySelector("#send").onclick = function () {
var msg = document.querySelector("#msg").value;
//注意客戶端的send()會發送到當前的socket命名空間下
//socket.send(msg);
socket.send(msg);
};
</script>
</body>
</html>
/user 和 /order 不同命名空間下的消息彼此之間無法看到。
四、socket.io房間的概念
房間是一個命名空間下划分的,一個客戶端可以進入多個房間。
如果在命名空間下進行廣播,那該命名空間下的所有客戶端和房間內的客戶端都會收到消息。
如果在房間內進行廣播,則該房間下的所有客戶端會收到消息,房間外的不會影響。
const http = require('http');
const path = require('path');
const express = require('express');
let app = express();
let server = http.createServer(app);
let io = require('socket.io')(server);
app.use(express.static(path.join(__dirname)));
app.get('/', function (req, res) {
res.sendFile(path.join(__dirname, 'index.html'));
});
io.of('/user').on('connection', function (socket) {
let rooms = [];
//加入房間
socket.on('join', function (name) {
socket.join(name, function () {
if (!rooms.includes(name)) {
rooms.unshift(name);
}
console.log(`${socket.id} 加入房間 ${name}`);
console.log(rooms);
});
});
//離開房間
socket.on('leave', function (name) {
socket.leave(name, function () {
rooms = rooms.filter(function (value) {
return value !== name;
});
console.log(`${socket.id} 離開房間 ${name}`);
console.log(rooms);
});
});
//房間內的廣播
socket.on('room_broadcast', function (data) {
//socket.to(rooms[0]).send('房間 ${rooms[0]} 內的廣播 : ' + data); 房間下的所有客戶端,不包括發送者
//io.of(命名空間).in(rooms[0]).send(`房間 ${rooms[0]} 內的廣播 : ${data}`); 房間下的所有客戶端,包括發送者
io.of('/user').in(rooms[0]).send(`房間 ${rooms[0]} 內的廣播 : ${data}`);
});
//命名空間下的廣播
socket.on('namespace_broadcast', function (data) {
//socket.broadcast.send('命名空間下的廣播 : ' + data); 命名空間下所有客戶端,不包括發送者
//io.of(命名空間).send('命名空間下的廣播 : ' + data); 命名空間下所有客戶端,包括發送者
io.of('/user').send('命名空間下的廣播 : ' + data);
});
});
io.of('/order').on('connection', function (socket) {
socket.on('message', function (data) {
io.of('/order').send('命名空間下的廣播 : ' + data);
});
});
server.listen(8888);
user.html的代碼:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<input type="text" id="msg">
<input type="button" id="room_send" value="房間內的廣播">
<input type="button" id="namespace_send" value="命名空間下的廣播">
<input type="button" class="join" room="001" value="加入房間001">
<input type="button" class="join" room="002" value="加入房間002">
<input type="button" class="leave" room="001" value="離開房間001">
<input type="button" class="leave" room="002" value="離開房間002">
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io.connect('http://localhost:8888/user');
socket.on('connect', function () {
console.log('連接成功');
});
socket.on('message', function (data) {
console.log('客戶端收到 : ', data);
});
document.querySelector("#room_send").onclick = function () {
var msg = document.querySelector("#msg").value;
socket.emit("room_broadcast", msg);
};
document.querySelector("#namespace_send").onclick = function () {
var msg = document.querySelector("#msg").value;
socket.emit("namespace_broadcast", msg);
};
var joins = document.querySelectorAll(".join");
for (var ix = 0; ix < joins.length; ix++) {
joins[ix].onclick = function () {
socket.emit('join', this.getAttribute("room"));
};
}
var leaves = document.querySelectorAll(".leave");
for (var ix = 0; ix < leaves.length; ix++) {
leaves[ix].onclick = function () {
socket.emit('leave', this.getAttribute("room"));
};
}
</script>
</body>
</html>
通過 join() 和 leave() 加入房間或離開房間。通過 to() 方法指定向哪個房間發送消息。要發送多個房間,可以調用多次 to()。
