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()。