'websocket協議及使用'

"
"
websocket協議及使用

WebSocket 是 HTML5 開始提供的一種在單個 TCP 連接上進行全雙工通訊的協議。

在 WebSocket API 中,瀏覽器和服務器只需要做一個握手的動作,然後,瀏覽器和服務器之間就形成了一條快速通道。兩者之間就直接可以數據互相傳送。

傳統的HTTP協議是一個請求-響應協議,瀏覽器不主動請求,服務器是沒法主動發數據給瀏覽器的。

傳統服務器推送方式

Ajax 輪詢

瀏覽器通過JavaScript啟動一個定時器,然後以固定的間隔給服務器發請求,詢問服務器有沒有新消息。

缺點

  • 實時性不夠
  • 頻繁的請求會給服務器帶來極大的壓力。

服務器反推

本質上也是輪詢,但是在沒有消息的情況下,服務器先拖一段時間,等到有消息了再回復。暫時地解決了實時性問題。

缺點

  • 以多線程模式運行的服務器會讓大部分線程大部分時間都處於掛起狀態,極大地浪費服務器資源。
  • 一個HTTP連接在長時間沒有數據傳輸的情況下,鏈路上的任何一個網關都可能關閉這個連接。 長期佔用連接,喪失了無狀態高併發的特點。

WebSocket協議

WebSocket並不是全新的協議,而是利用了HTTP協議來建立TCP連接。

1 請求

WebSocket連接必須由瀏覽器發起,因為請求協議是一個標準的HTTP請求。

格式如下:

GET ws://localhost:3000/ws/chat HTTP/1.1
Host: localhost
Upgrade: websocket
Connection: Upgrade
Origin: http://localhost:3000
Sec-WebSocket-Key: client-random-string
Sec-WebSocket-Version: 13

WebSocket請求和普通的HTTP請求不同:

  • GET請求的地址不是類似/path/,而是以ws://開頭的地址;
  • 請求頭Upgrade: websocket和Connection: Upgrade表示這個連接將要被轉換為WebSocket連接;
  • Sec-WebSocket-Key是用於標識這個連接,並非用於加密數據;
  • Sec-WebSocket-Version指定了WebSocket的協議版本。

2 響應

服務器如果接受該請求,就會返回如下響應:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: server-random-string

響應代碼101表示本次連接的HTTP協議即將被更改,更改後的協議就是Upgrade: websocket指定的WebSocket協議

WebSocket、HTTP 與 TCP 區別

HTTP、WebSocket 等應用層協議,都是基於 TCP 協議來傳輸數據的。 所以連接和斷開,都要遵循 TCP 協議中的三次握手和四次握手 ,只是在連接之後發送的內容不同,或者是斷開的時間不同。

對於 WebSocket 來說,它必須依賴 HTTP 協議進行一次握手 ,握手成功後,數據就直接從 TCP 通道傳輸,與 HTTP 無關了。

Socket 與 WebScoket

Socket是應用層與TCP/IP協議族通信的中間軟件抽象層,它是一組接口。在設計模式中,Socket其實就是一個門面模式,它把複雜的TCP/IP協議族隱藏在Socket接口後面,對用戶來說,一組簡單的接口就是全部,讓Socket去組織數據,以符合指定的協議。

"
websocket協議及使用

WebSocket 是 HTML5 開始提供的一種在單個 TCP 連接上進行全雙工通訊的協議。

在 WebSocket API 中,瀏覽器和服務器只需要做一個握手的動作,然後,瀏覽器和服務器之間就形成了一條快速通道。兩者之間就直接可以數據互相傳送。

傳統的HTTP協議是一個請求-響應協議,瀏覽器不主動請求,服務器是沒法主動發數據給瀏覽器的。

傳統服務器推送方式

Ajax 輪詢

瀏覽器通過JavaScript啟動一個定時器,然後以固定的間隔給服務器發請求,詢問服務器有沒有新消息。

缺點

  • 實時性不夠
  • 頻繁的請求會給服務器帶來極大的壓力。

服務器反推

本質上也是輪詢,但是在沒有消息的情況下,服務器先拖一段時間,等到有消息了再回復。暫時地解決了實時性問題。

缺點

  • 以多線程模式運行的服務器會讓大部分線程大部分時間都處於掛起狀態,極大地浪費服務器資源。
  • 一個HTTP連接在長時間沒有數據傳輸的情況下,鏈路上的任何一個網關都可能關閉這個連接。 長期佔用連接,喪失了無狀態高併發的特點。

WebSocket協議

WebSocket並不是全新的協議,而是利用了HTTP協議來建立TCP連接。

1 請求

WebSocket連接必須由瀏覽器發起,因為請求協議是一個標準的HTTP請求。

格式如下:

GET ws://localhost:3000/ws/chat HTTP/1.1
Host: localhost
Upgrade: websocket
Connection: Upgrade
Origin: http://localhost:3000
Sec-WebSocket-Key: client-random-string
Sec-WebSocket-Version: 13

WebSocket請求和普通的HTTP請求不同:

  • GET請求的地址不是類似/path/,而是以ws://開頭的地址;
  • 請求頭Upgrade: websocket和Connection: Upgrade表示這個連接將要被轉換為WebSocket連接;
  • Sec-WebSocket-Key是用於標識這個連接,並非用於加密數據;
  • Sec-WebSocket-Version指定了WebSocket的協議版本。

2 響應

服務器如果接受該請求,就會返回如下響應:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: server-random-string

響應代碼101表示本次連接的HTTP協議即將被更改,更改後的協議就是Upgrade: websocket指定的WebSocket協議

WebSocket、HTTP 與 TCP 區別

HTTP、WebSocket 等應用層協議,都是基於 TCP 協議來傳輸數據的。 所以連接和斷開,都要遵循 TCP 協議中的三次握手和四次握手 ,只是在連接之後發送的內容不同,或者是斷開的時間不同。

對於 WebSocket 來說,它必須依賴 HTTP 協議進行一次握手 ,握手成功後,數據就直接從 TCP 通道傳輸,與 HTTP 無關了。

Socket 與 WebScoket

Socket是應用層與TCP/IP協議族通信的中間軟件抽象層,它是一組接口。在設計模式中,Socket其實就是一個門面模式,它把複雜的TCP/IP協議族隱藏在Socket接口後面,對用戶來說,一組簡單的接口就是全部,讓Socket去組織數據,以符合指定的協議。

websocket協議及使用

主機 A 的應用程序要能和主機 B 的應用程序通信,必須通過 Socket 建立連接,而建立 Socket 連接必須需要底層 TCP/IP 協議來建立 TCP 連接。建立 TCP 連接需要底層 IP 協議來尋址網絡中的主機。我們知道網絡層使用的 IP 協議可以幫助我們根據 IP 地址來找到目標主機,但是一臺主機上可能運行著多個應用程序,如何才能與指定的應用程序通信就要通過 TCP 或 UPD 的地址也就是端口號來指定。這樣就可以通過一個 Socket 實例唯一代表一個主機上的一個應用程序的通信鏈路了。

WebSocket 則不同,它是一個完整的 應用層協議,包含一套標準的 API。

從使用上來說,WebSocket 更易用,而 Socket 更靈活。

HTML5 與 WebSocket

WebSocket API 是 HTML5 標準的一部分, 但這並不代表 WebSocket 一定要用在 HTML 中,或者只能在基於瀏覽器的應用程序中使用。

注意事項

長連接應用必須加心跳,否則連接可能由於長時間未通訊被路由節點強行斷開。

  • 客戶端定時給服務端發送點數據,防止連接由於長時間沒有通訊而被某些節點的防火牆關閉導致連接斷開的情況。
  • 服務端可以通過心跳來判斷客戶端是否在線,如果客戶端在規定時間內沒有發來任何數據,就認為客戶端下線。這樣可以檢測到客戶端由於極端情況(斷電、斷網等)下線的事件。

心跳間隔建議值:

建議客戶端發送心跳間隔小於60秒,比如55秒。

HTML5 WebSocket

1 屬性

Socket.readyState

只讀屬性 readyState 表示連接狀態,可以是以下值:

0 - 表示連接尚未建立。
1 - 表示連接已建立,可以進行通信。
2 - 表示連接正在進行關閉。
3 - 表示連接已經關閉或者連接不能打開。

Socket.bufferedAmount

只讀屬性 bufferedAmount 已被 send() 放入正在隊列中等待傳輸,但是還沒有發出的 UTF-8 文本字節數。

2 事件

假定我們使用了以上代碼創建了 Socket 對象:

Socket.onopen\t連接建立時觸發
Socket.onmessage\t客戶端接收服務端數據時觸發
Socket.onerror\t通信發生錯誤時觸發
Socket.onclose\t連接關閉時觸發

3 方法

假定我們使用了以上代碼創建了 Socket 對象:

Socket.send()\t 使用連接發送數據 
Socket.close()\t 關閉連接

參考: https://www.liaoxuefeng.com/wiki/1022910821149312/1103303693824096

代碼演示

服務端代碼:

這裡採用php方式來進行演示,其他語言也是類似,不在敘述。

<?php

use Workerman\\Worker;

require_once __DIR__ . '/../../Workerman/Autoloader.php';

use \\Workerman\\Lib\\Timer;

// 創建一個Worker監聽2000端口,使用websocket協議通訊

$ws_worker = new Worker("websocket://0.0.0.0:2000");

// 進程數設置為1,採用單進程

$ws_worker->count = 1;

// 保存uid到connection的映射(uid是用戶id或者客戶端唯一標識)

$ws_worker->uidConnections = [];

// 設置心跳時間 0 代表服務器主動保活

define('HEARTBEAT_TIME', 10);

// 進程啟動後設置一個每秒運行一次的定時器

$ws_worker->onWorkerStart = function ($worker) {

// 這裡使用一個定時器,間隔時間為1s

Timer::add(12, function () use ($worker) {

$time_now = time();

foreach ($worker->connections as $connection) {

// 有可能該connection還沒收到過消息,則lastMessageTime設置為當前時間

if (empty($connection->lastMessageTime)) {

$connection->lastMessageTime = $time_now;

continue;

}

// 上次通訊時間間隔大於心跳間隔,則認為客戶端已經下線,關閉連接

if (HEARTBEAT_TIME > 0 && $time_now - $connection->lastMessageTime > HEARTBEAT_TIME) {

$connection->close("Network connection timeout!");

}

// 如果心跳間隔設置為0的話,可以服務端主動發送ping

if (HEARTBEAT_TIME == 0) {

$connection->send('ping');

}

}

});

};

$ws_worker->onMessage = function ($connection, $data) {

global $ws_worker;

// 假設消息格式為

// uid:message 時是對 uid 發送 message

// uid 為 all 時是全局廣播

list($recv_uid, $message) = explode(':', $data);

$ws_worker->uidConnections[$recv_uid] = $connection;

// 給connection臨時設置一個lastMessageTime屬性,用來記錄上次收到消息的時間

$connection->lastMessageTime = time();

// 全局廣播

if ($recv_uid == 'all') {

broadcast($message);

} // 給特定uid發送

else {

// 可以向執行的uid發送消息

sendMessageByUid($recv_uid, $message);

}

};

/**

* 直接將消息發送給用戶推送數據

* @param $message

*/

function broadcast($message)

{

global $ws_worker;

foreach ($ws_worker->uidConnections as $connection) {

$connection->send($message);

}

}

/**

* 針對uid推送數據

* @param $uid

* @param $message

*/

function sendMessageByUid($uid, $message)

{

global $ws_worker;

// 這裡可以自定義自己的邏輯業務

if (isset($ws_worker->uidConnections[$uid]) && $uid) {

$ws_worker->uidConnections[$uid]->send($message);

}

}

// 運行worker

Worker::runAll();

客戶端代碼:

這個採用html

<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>WebSocket示例</title>
<script type="text/javascript">
function WebSocketTest() {
if ("WebSocket" in window) {
// 打開一個 web socket
var ws = new WebSocket("ws://localhost:2000");
ws.onopen = function () {
ws.send("100:haha");
console.log("數據發送中...");
};
ws.onmessage = function (e) {
var received_msg = e.data;
console.log("數據已接收:" + received_msg)
};
ws.onclose = function () {
// 關閉 websocket
console.log("連接已關閉...")
};
}
else {
// 瀏覽器不支持 WebSocket
alert("您的瀏覽器不支持 WebSocket!");
}
}
</script>
</head>
<body>
<div id="sse">
<a href="javascript:WebSocketTest()">打開連接</a>
</div>
</body>
</html>
效果
"
websocket協議及使用

WebSocket 是 HTML5 開始提供的一種在單個 TCP 連接上進行全雙工通訊的協議。

在 WebSocket API 中,瀏覽器和服務器只需要做一個握手的動作,然後,瀏覽器和服務器之間就形成了一條快速通道。兩者之間就直接可以數據互相傳送。

傳統的HTTP協議是一個請求-響應協議,瀏覽器不主動請求,服務器是沒法主動發數據給瀏覽器的。

傳統服務器推送方式

Ajax 輪詢

瀏覽器通過JavaScript啟動一個定時器,然後以固定的間隔給服務器發請求,詢問服務器有沒有新消息。

缺點

  • 實時性不夠
  • 頻繁的請求會給服務器帶來極大的壓力。

服務器反推

本質上也是輪詢,但是在沒有消息的情況下,服務器先拖一段時間,等到有消息了再回復。暫時地解決了實時性問題。

缺點

  • 以多線程模式運行的服務器會讓大部分線程大部分時間都處於掛起狀態,極大地浪費服務器資源。
  • 一個HTTP連接在長時間沒有數據傳輸的情況下,鏈路上的任何一個網關都可能關閉這個連接。 長期佔用連接,喪失了無狀態高併發的特點。

WebSocket協議

WebSocket並不是全新的協議,而是利用了HTTP協議來建立TCP連接。

1 請求

WebSocket連接必須由瀏覽器發起,因為請求協議是一個標準的HTTP請求。

格式如下:

GET ws://localhost:3000/ws/chat HTTP/1.1
Host: localhost
Upgrade: websocket
Connection: Upgrade
Origin: http://localhost:3000
Sec-WebSocket-Key: client-random-string
Sec-WebSocket-Version: 13

WebSocket請求和普通的HTTP請求不同:

  • GET請求的地址不是類似/path/,而是以ws://開頭的地址;
  • 請求頭Upgrade: websocket和Connection: Upgrade表示這個連接將要被轉換為WebSocket連接;
  • Sec-WebSocket-Key是用於標識這個連接,並非用於加密數據;
  • Sec-WebSocket-Version指定了WebSocket的協議版本。

2 響應

服務器如果接受該請求,就會返回如下響應:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: server-random-string

響應代碼101表示本次連接的HTTP協議即將被更改,更改後的協議就是Upgrade: websocket指定的WebSocket協議

WebSocket、HTTP 與 TCP 區別

HTTP、WebSocket 等應用層協議,都是基於 TCP 協議來傳輸數據的。 所以連接和斷開,都要遵循 TCP 協議中的三次握手和四次握手 ,只是在連接之後發送的內容不同,或者是斷開的時間不同。

對於 WebSocket 來說,它必須依賴 HTTP 協議進行一次握手 ,握手成功後,數據就直接從 TCP 通道傳輸,與 HTTP 無關了。

Socket 與 WebScoket

Socket是應用層與TCP/IP協議族通信的中間軟件抽象層,它是一組接口。在設計模式中,Socket其實就是一個門面模式,它把複雜的TCP/IP協議族隱藏在Socket接口後面,對用戶來說,一組簡單的接口就是全部,讓Socket去組織數據,以符合指定的協議。

websocket協議及使用

主機 A 的應用程序要能和主機 B 的應用程序通信,必須通過 Socket 建立連接,而建立 Socket 連接必須需要底層 TCP/IP 協議來建立 TCP 連接。建立 TCP 連接需要底層 IP 協議來尋址網絡中的主機。我們知道網絡層使用的 IP 協議可以幫助我們根據 IP 地址來找到目標主機,但是一臺主機上可能運行著多個應用程序,如何才能與指定的應用程序通信就要通過 TCP 或 UPD 的地址也就是端口號來指定。這樣就可以通過一個 Socket 實例唯一代表一個主機上的一個應用程序的通信鏈路了。

WebSocket 則不同,它是一個完整的 應用層協議,包含一套標準的 API。

從使用上來說,WebSocket 更易用,而 Socket 更靈活。

HTML5 與 WebSocket

WebSocket API 是 HTML5 標準的一部分, 但這並不代表 WebSocket 一定要用在 HTML 中,或者只能在基於瀏覽器的應用程序中使用。

注意事項

長連接應用必須加心跳,否則連接可能由於長時間未通訊被路由節點強行斷開。

  • 客戶端定時給服務端發送點數據,防止連接由於長時間沒有通訊而被某些節點的防火牆關閉導致連接斷開的情況。
  • 服務端可以通過心跳來判斷客戶端是否在線,如果客戶端在規定時間內沒有發來任何數據,就認為客戶端下線。這樣可以檢測到客戶端由於極端情況(斷電、斷網等)下線的事件。

心跳間隔建議值:

建議客戶端發送心跳間隔小於60秒,比如55秒。

HTML5 WebSocket

1 屬性

Socket.readyState

只讀屬性 readyState 表示連接狀態,可以是以下值:

0 - 表示連接尚未建立。
1 - 表示連接已建立,可以進行通信。
2 - 表示連接正在進行關閉。
3 - 表示連接已經關閉或者連接不能打開。

Socket.bufferedAmount

只讀屬性 bufferedAmount 已被 send() 放入正在隊列中等待傳輸,但是還沒有發出的 UTF-8 文本字節數。

2 事件

假定我們使用了以上代碼創建了 Socket 對象:

Socket.onopen\t連接建立時觸發
Socket.onmessage\t客戶端接收服務端數據時觸發
Socket.onerror\t通信發生錯誤時觸發
Socket.onclose\t連接關閉時觸發

3 方法

假定我們使用了以上代碼創建了 Socket 對象:

Socket.send()\t 使用連接發送數據 
Socket.close()\t 關閉連接

參考: https://www.liaoxuefeng.com/wiki/1022910821149312/1103303693824096

代碼演示

服務端代碼:

這裡採用php方式來進行演示,其他語言也是類似,不在敘述。

<?php

use Workerman\\Worker;

require_once __DIR__ . '/../../Workerman/Autoloader.php';

use \\Workerman\\Lib\\Timer;

// 創建一個Worker監聽2000端口,使用websocket協議通訊

$ws_worker = new Worker("websocket://0.0.0.0:2000");

// 進程數設置為1,採用單進程

$ws_worker->count = 1;

// 保存uid到connection的映射(uid是用戶id或者客戶端唯一標識)

$ws_worker->uidConnections = [];

// 設置心跳時間 0 代表服務器主動保活

define('HEARTBEAT_TIME', 10);

// 進程啟動後設置一個每秒運行一次的定時器

$ws_worker->onWorkerStart = function ($worker) {

// 這裡使用一個定時器,間隔時間為1s

Timer::add(12, function () use ($worker) {

$time_now = time();

foreach ($worker->connections as $connection) {

// 有可能該connection還沒收到過消息,則lastMessageTime設置為當前時間

if (empty($connection->lastMessageTime)) {

$connection->lastMessageTime = $time_now;

continue;

}

// 上次通訊時間間隔大於心跳間隔,則認為客戶端已經下線,關閉連接

if (HEARTBEAT_TIME > 0 && $time_now - $connection->lastMessageTime > HEARTBEAT_TIME) {

$connection->close("Network connection timeout!");

}

// 如果心跳間隔設置為0的話,可以服務端主動發送ping

if (HEARTBEAT_TIME == 0) {

$connection->send('ping');

}

}

});

};

$ws_worker->onMessage = function ($connection, $data) {

global $ws_worker;

// 假設消息格式為

// uid:message 時是對 uid 發送 message

// uid 為 all 時是全局廣播

list($recv_uid, $message) = explode(':', $data);

$ws_worker->uidConnections[$recv_uid] = $connection;

// 給connection臨時設置一個lastMessageTime屬性,用來記錄上次收到消息的時間

$connection->lastMessageTime = time();

// 全局廣播

if ($recv_uid == 'all') {

broadcast($message);

} // 給特定uid發送

else {

// 可以向執行的uid發送消息

sendMessageByUid($recv_uid, $message);

}

};

/**

* 直接將消息發送給用戶推送數據

* @param $message

*/

function broadcast($message)

{

global $ws_worker;

foreach ($ws_worker->uidConnections as $connection) {

$connection->send($message);

}

}

/**

* 針對uid推送數據

* @param $uid

* @param $message

*/

function sendMessageByUid($uid, $message)

{

global $ws_worker;

// 這裡可以自定義自己的邏輯業務

if (isset($ws_worker->uidConnections[$uid]) && $uid) {

$ws_worker->uidConnections[$uid]->send($message);

}

}

// 運行worker

Worker::runAll();

客戶端代碼:

這個採用html

<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>WebSocket示例</title>
<script type="text/javascript">
function WebSocketTest() {
if ("WebSocket" in window) {
// 打開一個 web socket
var ws = new WebSocket("ws://localhost:2000");
ws.onopen = function () {
ws.send("100:haha");
console.log("數據發送中...");
};
ws.onmessage = function (e) {
var received_msg = e.data;
console.log("數據已接收:" + received_msg)
};
ws.onclose = function () {
// 關閉 websocket
console.log("連接已關閉...")
};
}
else {
// 瀏覽器不支持 WebSocket
alert("您的瀏覽器不支持 WebSocket!");
}
}
</script>
</head>
<body>
<div id="sse">
<a href="javascript:WebSocketTest()">打開連接</a>
</div>
</body>
</html>
效果
websocket協議及使用

"

相關推薦

推薦中...