动态

详情 返回 返回

《 Socket.IO》 解決 WebSocket 通信! - 动态 详情

大家好呀,我是小菜~

本文主要介紹 Socket.IO

微信公眾號已開啓,小菜良記,沒關注的同學們記得關注哦!

在介紹 Socket.IO 之前, 我們先考慮一個問題, 如果這個時候有個需求, 類似實現人工客服的功能該如何實現?
image

在線客服,需求理解起來很簡單,就相當於一個 web 的聊天頁面,也就是客户端能夠 即時拉取到服務端的響應

當然, 作為接口工程師, 這並不是一個很難解決的問題, 我們可以提供一個獲取聊天記錄的接口, 通過該接口我們可以獲取到

對方已經發送到消息. 那麼問題又來了, 如何保證能夠 即時 的獲取到聊天記錄呢? 想必這也不是問題, 前端可以通過定時器

的方式, 將間隔時間縮短到 100 毫秒, 這樣子就已經實現了近實時的獲取消息

setInterval(function () { 
    // do something
},100)

當我們寫完以上代碼上線後, 卻通過監控可以發現, 上線後的服務器指標明顯比之前有所提升

服務器是十分珍貴的資源, 那麼為什麼會發生這種情況呢? 回過頭一想, 會發生這種情況也無可厚非, 每 100 毫秒就請求一

次後端, 如果有聊天記錄產生, 那麼這種請求就認為是有意義的, 但如果長時間未聊天, 每次請求返回都是空記錄, 那麼這種

頻繁請求就是無意義的. 頻繁請求會使服務器壓力增大, 並且浪費帶寬流量.

那麼有沒有別的方式可以解決?

我們也許可以使用 SSE 方式, SSE 並不是一個什麼比較新穎的概念, 它出現的時間也很早

SSE 全稱 Server-Sent Events,指的是網頁自動獲取來自服務器的更新,也就是自動化獲取服務端推送至網頁的數據,這是一個 H5 的屬性,除了 IE,其他標準瀏覽器基本都兼容

這種方式不需要客户端定時去獲取,而是服務端向客户端聲明要發送流信息,然後連續不斷地發送過來


儘管這種方式不需要定時輪詢, 但是它只能單工通信,建立連接後,只能由服務端發往客户端,且需要佔用一個連接,如果需要客户端向服務端通信,那麼需要額外再打開一個連接!

如果連接數過多會導致什麼問題?

TCP 的連接數是有限的, SYN DDOS 洪水攻擊, 就是利用 TCP 半連接的問題來攻擊服務器

因此這也不是一種優雅的實現方式

其實到這裏, 我們解決的思路已經很明確了, 就是在不浪費帶寬的情況下如何讓服務端將最新的消息以最快的速度發送給客

户端. 但是明顯 HTTP 協議不適用, 它是會在服務端收到請求後才會做出迴應. 因此為了解決這個問題, 那麼就需要就需要講

到一種通信協議, 那就是 WebSocket

WebSocket 是一種計算機通信協議,通過單個 TCP 連接提供全雙工通信信道。

建立一個 WebSocket 連接,客户端會發送一個 WebSocket 握手請求,服務器為此返回一個 WebSocket 握手響應,如下

圖所示。


相比於傳統 HTTP 的每次 請求-應答 都要客户端與服務端建立連接的模式, websocket 是一種 長連接 的模式, 一旦建

立起 websocekt 連接, 除非 client 或者 server 中有一端主動斷開連接, 否則每次數據傳輸之前都不需要 HTTP 那樣請求數

客户端請求

Upgrade: websocket
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
  • Upgrade 是為了表明這是一個 websocekt 類型請求, 意在告訴 server 需要將通信協議切換到 websocekt
  • Sec-WebSocket-Key是 client 發送的一個 base64 編碼的密文 ,要求服務器用 Sec-WebSocket-Accept 頭部中的密鑰散列作為響應。這是為了防止緩存代理重新發送以前的 WebSocket 對話,並且不提供任何身份驗證、隱私或完整性。

    服務端響應

Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat

握手從 HTTP 請求/響應開始,允許服務器在同一端口處理 HTTP 連接和 WebSocket 連接。一旦連接建立起來,通信就

切換到不符合 HTTP 協議的雙向二進制協議。

圖源網, 侵刪

到這裏其實方案已經出來了, 但是我們這篇文章的標題確實 Socket.IO, 既然都有了 Websocket, 為什麼我們講的是 Socket.IO ?

Socket.IO

在大家往下看之前先清楚這麼一個觀點:

Socket.IO 不是替代, 而是升級

Socket.IO 是一個庫, 説到庫其實我們都不陌生, 庫是對已有的功能進行封裝, 沒錯, 它是構建在 WebSocket 協議之上, 並提供額外的保證, 既然它是構建在 websocekt 之上, 説明它同樣具有客户機與服務器之間延遲通信的功能.

Socket.IO可用於實現以下幾種通信方式:

  • HTML 5中的WebSocket通信
  • 可在Flash中使用的WebSocket通信
  • XHR輪詢
  • JSONP輪詢
  • Forever Iframe

Socket.IO確保在實現這些通信方式時,客户端與服務器端可以使用相同的API。並具備以下特性:

  • HTTP 長輪詢回退

如果不能建立 WebSocket 連接,連接將退回到 HTTP 長輪詢。

  • 自動重新連接

在某些特定條件下,服務器和客户端之間的 WebSocket 連接可能會被中斷,雙方都不知道鏈接的斷開狀態。而 Socket.IO 包含一個 heartbeat 機制的原因,該機制定期檢查連接的狀態.當客户端最終斷開連接時,它會自動重新連接,並且會出現指數級的回退延遲,以免壓垮服務器

  • 數據包緩衝

當客户端斷開連接時,數據包將自動緩衝,並在重新連接時發送

既然 Socket.IO 如此的美妙, 那麼它該如何使用呢? 那麼接下來就讓我們創建一個自己的聊天室吧 !

本案例採用 NodeJS 環境搭建, 極其簡單, 有條件的可以上手一試

聊天室

準備前提:

  • 確保安裝了 Node.js 環境
  • 準備一個空文件夾

準備步驟很簡單, 接下來我們就開始創建我們自己的聊天室

1. 創建 package.json 文件

我們在空目錄下創建 package.json 文件, 內容如下:

{  
    "name": "c-chat",  
    "version": "0.0.1",  
    "description": "my first chat app",  
    "dependencies": {}
}

在當前目錄執行命令 npm install express 安裝web應用開發框架

2. 創建 index.js & index.html

在空目錄下創建 index.js 文件, 內容如下:

const app = require('express')();
const http = require('http').Server(app);

app.get('/', (req, res) => {
   res.sendFile(__dirname + '/index.html');
});

http.listen(port, () => {
   console.log(`${port} 端口監聽成功`);
});

接着創建 index.html 文件, 內容如下

<!DOCTYPE html>
<html>
 <head>
   <title>my chat</title>
   <style>
     body { margin: 0; padding-bottom: 3rem; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; }

     #form { background: rgba(0, 0, 0, 0.15); padding: 0.25rem; position: fixed; bottom: 0; left: 0; right: 0; display: flex; height: 3rem; box-sizing: border-box; backdrop-filter: blur(10px); }
     #input { border: none; padding: 0 1rem; flex-grow: 1; border-radius: 2rem; margin: 0.25rem; }
     #input:focus { outline: none; }
     #form > button { background: #333; border: none; padding: 0 1rem; margin: 0.25rem; border-radius: 3px; outline: none; color: #fff; }

     #messages { list-style-type: none; margin: 0; padding: 0; }
     #messages > li { padding: 0.5rem 1rem; }
     #messages > li:nth-child(odd) { background: #efefef; }
   </style>
 </head>
 <body>
   <ul id="messages"></ul>
   <form id="form" action="">
     <input id="input" /><button>Send</button>
   </form>
 </body>
</html>

為了測試 http 服務與我們的頁面是否有效, 我們可以利用 node index.js 啓動項目來驗證
image

到這裏, 我們就已經能夠成功訪問到我們的頁面, 接下來就開始通過 socket.io 來實現我們的聊天功能

3. 安裝 socket.io 庫

npm install socket.io 首先就需要執行以上命令來安裝 socket.io

現在離目標已經實現一大半了

我們只需要修改部分內容便可以看到我們想要的效果

服務端
const { Server } = require("socket.io");
const io = new Server(server);

以上代碼是為了引入 socket.io庫, 並創建 websocket 服務, 然後便可以建立 socket 監聽

io.on('connection', (socket) => {console.log('連接建立成功');});

在一個Socket.IO服務器創建之後,當客户端與服務器端建立連接時,觸發Socket.IO服務器的connection事件,可以通過監聽該事件並指定事件回調函數的方法指定當客户端與服務器端建立連接時所需執行的處理

客户端

在 index.html 頁面, 我們添加以下代碼來引入 socket.io.js, 並創建 socket 對象

<script src="/socket.io/socket.io.js"></script>
<script>
  var socket = io();
</script>

到這裏為止就是加載 socket.io-client 所需的全部操作,該客户端公開了一個 io 全局(以及端點 GET/ socket.io/socket.io.js ) ,然後進行連接。我們可以重啓下服務, 打開瀏覽器, 然後查看控制枱結果
image
可以看到連接已經成功建立了. 接下來就是最重要的環節了, 雙方需要進行消息發送了, 在 IO 中任何可以被編碼為 JSON 的對象都可以發送,並且還支持二進制數據

客户端

index.html 中需要修改的代碼如下:

<script>
  var socket = io();

  var messages = document.getElementById('messages');
  var form = document.getElementById('form');
  var input = document.getElementById('input');

  form.addEventListener('submit', function(e) {
    e.preventDefault();
    if (input.value) {
      socket.emit('chat message', input.value);
      input.value = '';
    }
  });

  socket.on('chat message', function(msg) {
    var item = document.createElement('li');
    item.textContent = msg;
    messages.appendChild(item);
    window.scrollTo(0, document.body.scrollHeight);
  });
</script>

可以通過 emit 方法往服務端發送消息, 其中 chat message 為發送的目標地址

在emit方法中,使用三個參數

socket.emit(event, data, callback)

  • event參數值為一個用於指定事件名的字符串, 也就是目標主題
  • data參數值代表該事件中攜帶的數據,該數據將被對方接收,數據可以為一個字符串,也可以為一個對象
  • callback參數值為一個參數,用於指定一個當對方確認接收到數據時調用的回調函數
服務端

index.js 文件中需要修改的代碼如下:

io.on('connection', (socket) => {
  socket.on('chat message', (msg) => {
    console.log('message: ' + msg);
  });
});

通過 socket.on() 的方式監聽目標地址, 這有些類似於發佈/訂閲模式, 雙方訂閲同一個地址, 然後往這個通道中傳遞消息
在服務端我們同樣可以使用 emit 方法往客户端發送消息, 我們可以利用 socket.emit() 進行發送

附: 完整代碼

index.html

index.js

到這裏就徹底結束了, 來吧, 夥計們, 現在重新啓動項目, 然後打開兩個瀏覽器訪問 localhost:3000 地址, 來嘗試和自己對話吧 !

命名空間

上面我們已經簡單的實現了一個聊天室的功能, 主要利用到以下 api

  • socket.on() 監聽事件
  • socket.emit() 消息發送

這兩個是最基礎的用法, 下面我們説一個擴展使用, 那就是命名空間

如果開發者想在一個特定的應用程序中完全控制消息與事件的發送,只需要使用一個默認的"/"命名空間就足夠了。但是如果開發者需要將應用程序作為第三方服務提供給其他應用程序,則需要為一個用於與客户端連接的socket端口定義一個獨立的命名空間。

在Socket.IO中,使用Socket.IO服務器對象的of方法定義命名空間,代碼如下所示(代碼中的io代表一個Socket.IO服務器對象)。

io.of(namespace)

下面我們看下如何使用:

  • 服務端
io.of("/chat").on("connection", (socket) => {
  // 訂閲對應的主題
  socket.on("chat message", (msg) => {
    console.log("message: " + msg);
    socket.emit("chat message", msg);
  });
});
  • 客户端
var socket = io('http://localhost:3000/chats');

我們以上例子中定義了 chat 這個命名空間用於區分不同 socket 連接, 小夥伴們可以發揮想象這個可以應用到什麼場景中 !

總結

SOCKET 是用來讓不同電腦之間,不同進程之間互相通信的一套接口。Socket, 直譯過來可以是“插座”,而在中文中往往會叫“套接字”。雙方要建立連接, 首先就會申請一個 套接字 來傳輸消息

不要空談,不要貪懶,和小菜一起做個吹着牛X做架構的程序猿吧~點個關注做個伴,讓小菜不再孤單。咱們下文見!

今天的你多努力一點,明天的你就能少説一句求人的話!

我是小菜,一個和你一起變強的男人。 💋

微信公眾號已開啓,小菜良記,沒關注的同學們記得關注哦!

Add a new 评论

Some HTML is okay.