帶有 Socket.io 的 Node.js Websocket 示例
什麼是 Websockets?
在過去的幾年裡,一種新型的通信方式開始出現在 Web 和移動應用程序中,稱為 websockets。該協議期待已久,終於在 2011 年被 IETF 標準化,為廣泛使用鋪平了道路。
這個新協議開闢了一條更快、更有效的與客戶端的通信線路。與 HTTP 一樣,websockets 運行在 TCP 連接之上,但它們要快得多,因為我們不必每次要發送消息時都打開新連接,因為只要服務器保持連接狀態,連接就會保持活動狀態或客戶想要的。
更好的是,由於連接永遠不會中斷,我們終於可以使用全雙工通信,這意味著我們可以將數據推送到客戶端,而不必等待他們從服務器請求數據 .這允許來回傳輸數據,這對於實時聊天應用程序甚至遊戲等應用來說非常理想。
Websockets 是如何工作的?
從本質上講,websocket 只是一個允許全雙工通信的 TCP 連接,這意味著連接的任何一方都可以向另一方發送數據,甚至在同一時間。
為了建立這個連接,協議實際上將握手作為一個普通的 HTTP 請求發起,然後使用升級請求 HTTP 標頭進行“升級”,如下所示:
GET /ws/chat HTTP/1.1
Host: chat.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: q1PZLMeDL4EwLkw4GGhADm==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 15
Origin: http://example.com
然後服務器發回一個 HTTP 101“交換協議”響應,確認連接將被升級。建立此連接後,將切換到雙向二進制協議,此時可以發送應用程序數據。
為了保持連接打開,協議所要做的就是發送一些 ping/pong 數據包,告訴對方它們仍然存在。為了關閉連接,發送一個簡單的“關閉連接”數據包。
一些 Websocket 示例
在我們可用的 Node.js 的許多不同 websocket 庫中,我選擇在本文中使用 socket.io,因為它似乎是最流行的,並且在我看來也是最容易使用的。雖然每個庫都有自己獨特的 API,但它們也有許多相似之處,因為它們都建立在相同的協議之上,因此希望您能夠將下面的代碼翻譯成您想要使用的任何庫。
對於 HTTP 服務器,我將使用 Express,它是目前最流行的 Node 服務器。請記住,如果您不需要 Express 的所有功能,也可以只使用普通的 http 模塊。不過,由於大多數應用程序都將使用 Express,我們也將使用 Express。
注意 :在這些示例中,我刪除了大部分樣板代碼,因此其中一些代碼無法直接使用。在大多數情況下,您可以參考第一個示例來獲取樣板代碼。
建立連接
為了在客戶端和服務器之間建立連接,服務器必須做兩件事:
- 連接到 HTTP 服務器以處理 websocket 連接
- 提供
socket.io.js
客戶端庫作為靜態資源
在下面的代碼中,您可以看到第 (1) 項在第 3 行完成。第 (2) 項由 socket.io
為您完成(默認情況下) 庫並在路徑 /socket.io/socket.io.js
上提供 .默認情況下,所有 websocket 連接和資源都在 /socket.io
中提供 路徑。
服務器
var app = require('express')();
var server = require('http').Server(app);
var io = require('socket.io')(server);
app.get('/', function(req, res) {
res.sendFile(__dirname + '/index.html');
});
server.listen(8080);
客戶端也需要做兩件事:
- 從服務器加載庫
- 調用
.connect()
到服務器地址和 websocket 路徑
客戶
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io.connect('/');
</script>
如果您將瀏覽器導航到 http://localhost:8080
並使用瀏覽器的開發工具檢查幕後的 HTTP 請求,您應該能夠看到正在執行的握手,包括 GET 請求和生成的 HTTP 101 切換協議響應。
從服務器發送數據到客戶端
好的,現在開始一些更有趣的部分。在本例中,我們將向您展示從服務器向客戶端發送數據的最常用方式。在這種情況下,我們將向一個頻道發送一條消息,該頻道可以被客戶端訂閱和接收。因此,例如,客戶端應用程序可能正在偵聽“公告”頻道,該頻道將包含有關係統範圍事件的通知,例如用戶加入聊天室時。
在服務器上,這是通過等待建立新連接,然後調用 socket.emit()
來完成的 向所有連接的客戶端發送消息的方法。
服務器
io.on('connection', function(socket) {
socket.emit('announcements', { message: 'A new user has joined!' });
});
客戶
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io.connect('/');
socket.on('announcements', function(data) {
console.log('Got announcement:', data.message);
});
</script>
從客戶端發送數據到服務器
但是當我們想以另一種方式發送數據時,從客戶端到服務器,我們會怎麼做呢?它與上一個示例非常相似,同時使用 socket.emit()
和 socket.on()
方法。
服務器
io.on('connection', function(socket) {
socket.on('event', function(data) {
console.log('A client sent us this dumb message:', data.message);
});
});
客戶
免費電子書:Git Essentials
查看我們的 Git 學習實踐指南,其中包含最佳實踐、行業認可的標準以及隨附的備忘單。停止谷歌搜索 Git 命令並真正學習 它!
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io.connect('/');
socket.emit('event', { message: 'Hey, I have an important message!' });
</script>
計算連接用戶數
這是一個很好的學習示例,因為它展示了 socket.io
的更多功能 (如 disconnect
event),很容易實現,適用於很多webapp。我們將使用 connection
和 disconnect
統計我們網站上的活躍用戶數量的事件,我們將使用當前計數更新所有用戶。
服務器
var numClients = 0;
io.on('connection', function(socket) {
numClients++;
io.emit('stats', { numClients: numClients });
console.log('Connected clients:', numClients);
socket.on('disconnect', function() {
numClients--;
io.emit('stats', { numClients: numClients });
console.log('Connected clients:', numClients);
});
});
客戶
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io.connect('/');
socket.on('stats', function(data) {
console.log('Connected clients:', data.numClients);
});
</script>
一個更簡單的方法來跟踪服務器上的用戶數是使用這個:
var numClients = io.sockets.clients().length;
但顯然這方面存在一些問題,因此您可能必須自己跟踪客戶數量。
房間和命名空間
隨著您的應用程序變得越來越複雜,您可能需要對 websocket 進行更多自定義,例如向特定用戶或一組用戶發送消息。或者,您可能需要在應用程序的不同部分之間嚴格分離邏輯。這就是房間和命名空間發揮作用的地方。
注意 :這些特性不是 websocket 協議的一部分,而是由 socket.io
加在上面的 .
默認情況下,socket.io
使用根命名空間(/
) 發送和接收數據。以編程方式,您可以通過 io.sockets
訪問此命名空間 , 儘管它的許多方法在 io
上都有快捷方式 .所以這兩個調用是等價的:
io.sockets.emit('stats', { data: 'some data' });
io.emit('stats', { data: 'some data' });
要創建自己的命名空間,您只需執行以下操作:
var iosa = io.of('/stackabuse');
iosa.on('connection', function(socket){
console.log('Connected to Stack Abuse namespace'):
});
iosa.emit('stats', { data: 'some data' });
此外,客戶端必須顯式連接到您的命名空間:
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io('/stackabuse');
</script>
現在在這個命名空間內發送的任何數據都將與默認的 /
分開 命名空間,無論使用哪個通道。
更進一步,在每個命名空間中,您可以加入和離開“房間”。這些房間在命名空間之上提供了另一層分離,因為客戶端只能添加到服務器端的房間 ,它們還提供了一些額外的安全性。因此,如果您想確保用戶不會窺探某些數據,您可以使用房間來隱藏它。
要添加到房間,您必須 .join()
它:
io.on('connection', function(socket){
socket.join('private-message-room');
});
然後從那裡您可以向屬於給定房間的每個人發送消息:
io.to('private-message-room').emit('some event');
最後,調用 .leave()
停止從房間接收事件消息:
socket.leave('private-message-room');
結論
這只是一個實現 websockets 協議的庫,還有更多的庫,它們都有自己獨特的功能和優勢。我建議您嘗試其他一些(例如 node-websockets),以便您了解那裡的內容。
只需幾行代碼,您就可以創建一些非常強大的應用程序,所以我很想看看您能想出什麼!
有一些很酷的想法,或者已經使用 websockets 創建了一些應用程序?請在評論中告訴我們!