淺談 WebSocket 協定:實作一個簡單的即時聊天室吧!

更新於 發佈於 閱讀時間約 24 分鐘
raw-image

1.1 What is WebSocket?

WebSocket 是 HTML5 提供的一種網路傳輸協定,是瀏覽器(Client)與伺服器(Server)交換資料的方式之一。

與我們較為熟知的 HTTPHTTPS 協定,同樣位於 OSI 模型應用層,且基於傳輸層的 TCP 協定。其最大不同在於,WebSocket 協定只需連線一次,就能保持雙向溝通,不需重複發送 Request,因此回應更即時,進而提升 Web 通訊速度。

1.2 Why WebSocket?

  • HTTP 協定只能由 Client 端「單向」發送請求,無法由 Server 端主動發送請求;WebSocket 協定則允許 Server 端主動向 Client 端推播資料,實現「雙向溝通」機制
  • 透過 HTTP 1.1 持久連接(keep-alive),有效解決 HTTP 輪詢(Polling)產生的效能問題,節省伺服器資源
  • 實際範例:訊息推播、即時聊天室、共同編輯等功能
raw-image

1.3 How to use WebSocket?

WebSocket 的建立是由瀏覽器透過 JavaScript 指令,發起一個 HTTP Request 來實現。請求網址會長這樣:

ws://example.com/wsapi
  • 若經過 SSL 加密(WebSocket Secure),則加密連接網址會變成:
ws://example.com/wsapi
  • WebSocket 進行一次雙方握手過程,Client 端發送資訊:
GET ws://localhost:8080 HTTP/1.1             // 透過 HTTP/1.1 進行交握
Origin: http://localhost:8080 // Client 端 URL
Host: localhost:8080
Upgrade: websocket // 表示發送 WebSocket 類型請求
Connection: Upgrade // 表示 Client 希望連接升級
Sec-WebSocket-Key: xqBt3ImNzJbYqRINxEFlkg== // base64 編碼密文,瀏覽器在每次握手隨機生成
Sec-WebSocket-Protocol: chat, superchat // 用來區分同 URL 下不同 Server 需要之協議
Sec-WebSocket-Version: 13 // 支持的 WebSocket 版本號
  • Server 端回應資訊:
HTTP/1.1 101  Switching Protocols    // 表示成功建立連接
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: K7DJLdLooIwIG/MOpvWFB3y3FE8= // Server 端回覆對應的加密
Sec-WebSocket-Protocol: chat // Server 端使用的協議
Sec-WebSocket-Location: ws://localhost:8080

可參考 MDN 上提供的 WebSocket API 使用範例,建立簡單的 WebSocket 連線。 接下來會分幾個步驟來實作 WebSocket,其中又分 Server 和 Client 兩個面向進行:

  • Step1. 建置 WebSocket Server
  • Step2. 從 Client 發送請求給 WebSocket Server 建立連線
  • Step3. Server 處理來自 Client 的訊息與回應
  • Step4. Client 處理來自 Server 的訊息與回應
  • Step5. 實戰練習:實現 Server 與多個 Client 連線溝通 那麼就開始吧!

1.3.1 Server 端:建置 WebSocket 環境

這裡以 Node.js 為例,使用 Express 框架搭配 WebSocket 套件來建立環境。

關於如何使用 Express 來架設 Server,可參考過去寫的筆記:[week 17] 後端中階 — 使用 Node.js + Express 框架建立一個靜態網頁

  • 首先是安裝套件
$ npm install express
$ npm install ws
  • 安裝完套件後,在專案中建立 server.js 檔案,作為專案的進入點:
// import library
const express = require('express')
const ServerSocket = require('ws').Server // 引用 Server

// 指定一個 port
const PORT = 8080

// 建立 express 物件並用來監聽 8080 port
const server = express()
.listen(PORT, () => console.log(`[Server] Listening on https://localhost:${PORT}`))

// 建立實體,透過 ServerSocket 開啟 WebSocket 的服務
const wss = new ServerSocket({ server })

// Connection opened
wss.on('connection', ws => {
console.log('[Client connected]')

// Connection closed
ws.on('close', () => {
console.log('Close connected')
})
})
  • 即可輸入下方指令來運行 Server:
$ node server.js

出現以下 log 代表 Server 正在監聽 8080 port:

raw-image

1.3.2 Client 端:與 WebSocket Server 連線

處理完 Server 後,接下來要從 Client 端連結 WebSocket 服務。這裡需另起一個專案,包含 index.html 和 index.js 兩個檔案。

  • UI 畫面:新增一個 index.html 檔案
<html>
<head>
</head>
<body>
<!-- Connect or Disconnect WebSocket Server -->
<button id="connect">Connect</button>
<button id="disconnect">Disconnect</button>

<!-- Send Message to Server -->
<div>
Message: <input type="text" id="sendMsg" ><button id="sendBtn">Send</button>
</div>

<!-- Import index.js after UI rendered -->
<script src='./index.js'></script>
</body>
</html>

渲染後如下圖所示:

raw-image
  • 邏輯處理:新增一個 index.js 檔案
var ws

// 監聽 click 事件
document.querySelector('#connect')?.addEventListener('click', (e) => {
console.log('[click connect]')
connect()
})

document.querySelector('#disconnect')?.addEventListener('click', (e) => {
console.log('[click disconnect]')
disconnect()
})

document.querySelector('#sendBtn')?.addEventListener('click', (e) => {
const msg = document.querySelector('#sendMsg')
sendMessage(msg?.value)
})

function connect() {
// Create WebSocket connection
ws = new WebSocket('ws://localhost:8080')
// 在開啟連線時執行
ws.onopen = () => console.log('[open connection]')
}

function disconnect() {
ws.close()
// 在關閉連線時執行
ws.onclose = () => console.log('[close connection]')
}
// 安裝套件
$ npm install http-server
// 運行 http-server
$ http-server [path] [options]

// 預設為 8080 port,這裡指定 3000 port 為例
$ http-server -p 3000

實際運行如下方所示 :

raw-image

建立好 WebSocket 連結之後,就可以進行雙向溝通了!

1.3.3 Server 端:處理接發送 message

Server 端分別能使用 send 發送訊息,以及透過監聽 message 事件接收來自 Client 的訊息:

// Connection opened
wss.on('connection', ws => {
console.log('[Client connected]')

// Listen for messages from client
ws.on('message', data => {
console.log('[Message from client]: ', data)
// Send message to client
ws.send('[Get message from server]')
})

// ...
})

1.3.4 Client 端:處理接發送 message

同樣的,Client 端也能使用 send 送出訊息,以及透過 onmessage 接收 Server 端的訊息:

// 監聽 click 事件
document.querySelector('#sendBtn')?.addEventListener('click', (e) => {
const msg = document.querySelector('#sendMsg')
sendMessage(msg?.value)
})

// Listen for messages from Server
function sendMessage(msg) {
// Send messages to Server
ws.send(msg)
// Listen for messages from Server
ws.onmessage = event => console.log('[send message]', event)
}

1.4 實戰練習:實作一個簡單的即時聊天室

先前提到 WebSocket 常應用於即時聊天室等功能,也就是實現 Server 同時與多個 Client 連線。那該如何在 ClientA 傳送訊息給 Server 的同時,讓 ClientB 也接收到來自 Server 回傳的訊息呢?

這時就要仰賴「廣播功能」,首先透過 ws 提供的方法 clients 取得目前所有連線中的 Clients 資訊,再使用 forEach 迴圈送出訊息給每個 Client:

// Connection opened
wss.on('connection', ws => {
console.log('[Client connected]')

// Listen for messages from client
ws.on('message', data => {
console.log('[Message from client]: ', data)
// Get clients who connected
let clients = wss.clients
// Use loop for sending messages to each client
clients.forEach(client => {
client.send('[Broadcast][Get message from server]')
})
})

// ...
})

至於如何區分各個 Client,可參考這篇提供的方法:《webSocketServer node.js how to differentiate clients》,直接賦值 id 一個隨機變數。

或是參考 github 上的這篇討論 《unique identifier for each client request to websocket server》,直接取 request header 中的 'sec-websocket-key' 給每個 Client 新屬性 id:

wss.on('connection', (ws, req) => {
var id = req.headers['sec-websocket-key']
// Do something...
})

總之目的是取得各個使用者獨一無二的識別證,透過這些識別證,又能夠對照到我們所熟悉的遊戲 ID 或是帳號。

經整理後的程式碼如下:

  • Server 端:server.js
// import library
const express = require('express')
const ServerSocket = require('ws').Server // 引用 Server

const PORT = 8080

// 建立 express 物件並用來監聽 8080 port
const server = express()
.listen(PORT, () => console.log(`[Server] Listening on https://localhost:${PORT}`))

// 建立實體,透過 ServerSocket 開啟 WebSocket 的服務
const wss = new ServerSocket({ server })

// Connection opened
wss.on('connection', (ws, req) => {
ws.id = req.headers['sec-websocket-key'].substring(0, 8)
ws.send(`[Client ${ws.id} is connected!]`)

// Listen for messages from client
ws.on('message', data => {
console.log('[Message from client] data: ', data)
// Get clients who has connected
let clients = wss.clients
// Use loop for sending messages to each client
clients.forEach(client => {
client.send(`${ws.id}: ` + data)
})
})

// Connection closed
ws.on('close', () => {
console.log('[Close connected]')
})
})
  • Client 端:index.js
var ws

// 監聽 click 事件
document.querySelector('#connect')?.addEventListener('click', (e) => {
connect()
})

document.querySelector('#disconnect')?.addEventListener('click', (e) => {
disconnect()
})

document.querySelector('#sendBtn')?.addEventListener('click', (e) => {
const msg = document.querySelector('#sendMsg')
sendMessage(msg?.value)
})

function connect() {
// Create WebSocket connection
ws = new WebSocket('ws://localhost:8080')

// ws = new WebSocket('ws://192.168.17.35:58095')
// 在開啟連線時執行
ws.onopen = () => {
console.log('[open connection]')
// Listen for messages from Server
ws.onmessage = event => {
console.log(`[Message from server]:\n %c${event.data}` , 'color: blue')
}
}
}

function sendMessage(msg) {
// Send messages to Server
ws.send(msg)
}

function disconnect() {
ws.close()
// 在關閉連線時執行
ws.onclose = () => console.log('[close connection]')
}
  • 實際運行如下圖,完成具備廣播功能的即時聊天室!
raw-image

1.5 WebSocket API

在實際練習後,這裡整理一些常用的 WebSocket API

  • new WebSocket():和 Server 建立 WebSocket 連線

1.5.1 WebSocket Event

在建立 WebSocket 連線之後,即可透過 addEventListener() 監聽各種 Events,做出不同的對應方法,可參考 MDN 上的 Events

  • open:成功和 Server 建立連線時觸發
  • message:接收到 Server 端訊息時觸發
  • close:關閉連線時觸發
  • error:連線發生錯誤時觸發

1.5.2 WebSocket Methods

同樣在建立 WebSocket 連線後,可使用以下 WebSocket 提供的方法進行互動:

  • send():發送訊息給 Server
  • close():關閉連線

2.1 比較:Socket / WebSocket / Socket.IO

Socket、Websocket、Socket.io 這三者都和網路即時通訊有關,名稱也有 87 像,但實際上是不同的東西,以下做簡單的整理介紹:

raw-image

2.1.1 Socket:傳輸與應用層之間的接口

  • 是應用層與 TCP/IP 協議之間的抽象層介面,是一組 API 接口
  • 使用者可透過 Socket 來操作 TCP/IP 協議通信
  • 傳輸方式多為 stream
  • 傳輸載體類型為 binary

2.1.2 WebSocket:應用層協議

  • 為了改善 Web 即時雙向通訊而創造出來的協議
  • 瀏覽器底層使用 Socket 作為通訊介面
  • 載體類型有二種:binary(二進位)或 text(文字),只能擇一使用

2.1.3 Socket.IO:一套 JavaScript 函式庫

  • 是一套用來建立即時通訊應用的 JavaScript 函式庫,透過 Socket.IO 能夠實現 Server 與多個 Client 之間的溝通
  • 是建立在 WebSocket 之上的套件,並支持代理和負載平衡器
  • 由兩部分組成,包含 Server 端的 node.js 和 Client 端的 JavaScript
  • 核心是 engine.io,透過 WebSocket 和 HTTP long-polling 方式實現伺服器和客戶端之間的連線

3. 小結

這次同樣是工作上接手新專案,需要研究 WebSocket 相關技術,於是又把過去一些網路基礎知識給複習一遍,每次都對網路協議多了一層認識。

對自己而言算是比較陌生的領域,即使大概知道功用是什麼,實際操作起來又是另一回事,但學習新知識果然還是很快樂,期許自己能夠早日上手。

4. Reference

留言
avatar-img
留言分享你的想法!
avatar-img
Heidi Liu的沙龍
0會員
7內容數
Heidi Liu的沙龍的其他內容
2023/02/09
這次專案開發是針對 iOS 系統,需要解決畫面縮放的問題,有些關鍵字來回搜尋好多遍,卻還是容易搞混,於是乎乾脆記錄下來,日後如果遇到這類型問題,也會直接更新在這篇。
Thumbnail
2023/02/09
這次專案開發是針對 iOS 系統,需要解決畫面縮放的問題,有些關鍵字來回搜尋好多遍,卻還是容易搞混,於是乎乾脆記錄下來,日後如果遇到這類型問題,也會直接更新在這篇。
Thumbnail
2022/12/16
過去在程式導師實驗課程中,整理過這兩篇筆記探討「測試」是怎麼回事: [week 3] 初探 Jest:如何測試程式? [week 22] React:用 SPA 架構實作一個部落格(三)- 淺談測試 在轉職後的第一家公司,組內曾嘗試在既有專案中撰寫測試,卻因時程緊湊而不了了之。
Thumbnail
2022/12/16
過去在程式導師實驗課程中,整理過這兩篇筆記探討「測試」是怎麼回事: [week 3] 初探 Jest:如何測試程式? [week 22] React:用 SPA 架構實作一個部落格(三)- 淺談測試 在轉職後的第一家公司,組內曾嘗試在既有專案中撰寫測試,卻因時程緊湊而不了了之。
Thumbnail
2021/04/04
「平凡的我啊,哪有時間低頭回望?」 — — 《排球少年 264 話》 各位安安,我是 Heidi,從年前就一直思考,應該如何總結這一年來的學習心得,結果又拖到現在,不知不覺都已經入職快兩個月了XD 這篇是記錄我在 Lidemy 學習半年的結業心得,也會稍微帶到多人協作和求職總結。
Thumbnail
2021/04/04
「平凡的我啊,哪有時間低頭回望?」 — — 《排球少年 264 話》 各位安安,我是 Heidi,從年前就一直思考,應該如何總結這一年來的學習心得,結果又拖到現在,不知不覺都已經入職快兩個月了XD 這篇是記錄我在 Lidemy 學習半年的結業心得,也會稍微帶到多人協作和求職總結。
Thumbnail
看更多
你可能也想看
Thumbnail
沙龍一直是創作與交流的重要空間,這次 vocus 全面改版了沙龍介面,就是為了讓好內容被好好看見! 你可以自由編排你的沙龍首頁版位,新版手機介面也讓每位訪客都能更快找到感興趣的內容、成為你的支持者。 改版完成後可以在社群媒體分享新版面,並標記 @vocus.official⁠ ♥️ ⁠
Thumbnail
沙龍一直是創作與交流的重要空間,這次 vocus 全面改版了沙龍介面,就是為了讓好內容被好好看見! 你可以自由編排你的沙龍首頁版位,新版手機介面也讓每位訪客都能更快找到感興趣的內容、成為你的支持者。 改版完成後可以在社群媒體分享新版面,並標記 @vocus.official⁠ ♥️ ⁠
Thumbnail
每年4月、5月都是最多稅要繳的月份,當然大部份的人都是有機會繳到「綜合所得稅」,只是相當相當多人還不知道,原來繳給政府的稅!可以透過一些有活動的銀行信用卡或電子支付來繳,從繳費中賺一點點小確幸!就是賺個1%~2%大家也是很開心的,因為你們把沒回饋變成有回饋,就是用卡的最高境界 所得稅線上申報
Thumbnail
每年4月、5月都是最多稅要繳的月份,當然大部份的人都是有機會繳到「綜合所得稅」,只是相當相當多人還不知道,原來繳給政府的稅!可以透過一些有活動的銀行信用卡或電子支付來繳,從繳費中賺一點點小確幸!就是賺個1%~2%大家也是很開心的,因為你們把沒回饋變成有回饋,就是用卡的最高境界 所得稅線上申報
Thumbnail
一、什麼是WinSocket? 也就是大家常說的WinSock全名為Windows Sockets API (WSA),它會透過TCP/IP通訊協定來做網路的通訊,會用在Windows操作系統上來開發網路應用的API(應用程式介面)   二、WinSocket用在哪裡?   Socket可以
Thumbnail
一、什麼是WinSocket? 也就是大家常說的WinSock全名為Windows Sockets API (WSA),它會透過TCP/IP通訊協定來做網路的通訊,會用在Windows操作系統上來開發網路應用的API(應用程式介面)   二、WinSocket用在哪裡?   Socket可以
Thumbnail
前段時間我們有介紹「【Python 軍火庫🧨 - websockets】雙向溝通的渠道」, 這種方式可以達到基本的連線沒問題,但隨著資安意識的抬頭, 我們的websocket連線也會需要在通道之上進行加密, 那麼我們將根據使用情境來教您如何選用適當的連線。 Server端 我們的Serve
Thumbnail
前段時間我們有介紹「【Python 軍火庫🧨 - websockets】雙向溝通的渠道」, 這種方式可以達到基本的連線沒問題,但隨著資安意識的抬頭, 我們的websocket連線也會需要在通道之上進行加密, 那麼我們將根據使用情境來教您如何選用適當的連線。 Server端 我們的Serve
Thumbnail
Websocket是一種網路傳輸的協定,讓建立一次handshake的過程就可以相互傳遞資料,而非同步的過程能夠讓處理事情更有效率,這篇文章將帶你深入瞭解Websocket如何運作、以及其特點與優勢。
Thumbnail
Websocket是一種網路傳輸的協定,讓建立一次handshake的過程就可以相互傳遞資料,而非同步的過程能夠讓處理事情更有效率,這篇文章將帶你深入瞭解Websocket如何運作、以及其特點與優勢。
Thumbnail
嗨,開發者們!在網頁應用的世界中,即時通訊是一個越來越重要的特點。WebSocket提供了一個高效的方式來支持這種通訊,並且能夠在客戶端和伺服器之間提供雙向的即時通訊。本篇文章將會帶你走進WebSocket的世界,並學習如何在Gin框架中實現它。
Thumbnail
嗨,開發者們!在網頁應用的世界中,即時通訊是一個越來越重要的特點。WebSocket提供了一個高效的方式來支持這種通訊,並且能夠在客戶端和伺服器之間提供雙向的即時通訊。本篇文章將會帶你走進WebSocket的世界,並學習如何在Gin框架中實現它。
Thumbnail
今天來聊聊我們常常使用的網頁,背後怎麼讓我們用的更順暢的一門傳輸方式,HTTP Keep-Alive,透過一點點的小改變,讓傳輸效率提升,減少非必要的浪費。 當我們溝通的過程中沒有KeepAlive的狀況下,會有多次往來的狀況,這樣對雙方來說開銷成本是非常大的,因此為了改善這個現象,加入了一個機制,
Thumbnail
今天來聊聊我們常常使用的網頁,背後怎麼讓我們用的更順暢的一門傳輸方式,HTTP Keep-Alive,透過一點點的小改變,讓傳輸效率提升,減少非必要的浪費。 當我們溝通的過程中沒有KeepAlive的狀況下,會有多次往來的狀況,這樣對雙方來說開銷成本是非常大的,因此為了改善這個現象,加入了一個機制,
Thumbnail
這篇文章主要介紹 TCP 可靠性傳輸服務、連接管理、流量控制及擁塞控制等...
Thumbnail
這篇文章主要介紹 TCP 可靠性傳輸服務、連接管理、流量控制及擁塞控制等...
Thumbnail
Introduction to WebRTC 由於之後想要做的專題是跟即時視訊相關,在開始作業前想對webrtc有些基本的了解,結果就是一踏入了解就發現這個水深的可怕,之後每天除了寫一點go,也會花時間學習這個協定。WebRTC是個用於瀏覽器之間溝通的協定,透過NAT Traversal(STUN、
Thumbnail
Introduction to WebRTC 由於之後想要做的專題是跟即時視訊相關,在開始作業前想對webrtc有些基本的了解,結果就是一踏入了解就發現這個水深的可怕,之後每天除了寫一點go,也會花時間學習這個協定。WebRTC是個用於瀏覽器之間溝通的協定,透過NAT Traversal(STUN、
Thumbnail
What is WebSocket? WebSocket 是 HTML5 提供的一種網路傳輸協定,是瀏覽器(Client)與伺服器(Server)交換資料的方式之一。 與我們較為熟知的 HTTP 或 HTTPS 協定,同樣位於 OSI 模型的應用層,且基於傳輸層的 TCP 協定。
Thumbnail
What is WebSocket? WebSocket 是 HTML5 提供的一種網路傳輸協定,是瀏覽器(Client)與伺服器(Server)交換資料的方式之一。 與我們較為熟知的 HTTP 或 HTTPS 協定,同樣位於 OSI 模型的應用層,且基於傳輸層的 TCP 協定。
Thumbnail
你想像有一個黑盒子,它會跟你說輸入一個input,而會得出什麼Output,而你要把這個Input傳遞給黑盒子是透過http的方式
Thumbnail
你想像有一個黑盒子,它會跟你說輸入一個input,而會得出什麼Output,而你要把這個Input傳遞給黑盒子是透過http的方式
Thumbnail
從瀏覽器到伺服器之間究竟發生了哪些事情?才讓我們可以正常的瀏覽網頁呢?
Thumbnail
從瀏覽器到伺服器之間究竟發生了哪些事情?才讓我們可以正常的瀏覽網頁呢?
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News