WebSocket協議 與 IO多路複用

最近在把 Facebook Message 接入客服系統,因爲與 Facebook Message 對接的收發消息都是經過調用 http 接口來實現的,若是想實現即時通信,還須要在中間加一個 WebSocket 來轉發消息。以下圖:php

其中用到了 WebSocket 協議和 IO多路複用相關的知識。在這裏作一個學習記錄。nginx

爲何須要 WebSocket 協議

  • 由於 HTTP 協議有一個缺陷:通訊只能先由客戶端發起,而後服務器再做出響應,並不能由服務器主動向客戶端推送消息。
  • WebSocket 協議最大的特色是,服務器能夠主動向客戶端推送信息,客戶端也能夠主動向服務器發送信息。

WebSocket 與 socket 的之間關係

  • WebSocket 是一個網絡通訊協議,是屬於網絡七層模型中的應用層的協議,一樣屬於應用層的協議還有 HTTP 協議、FTP協議、SMTP協議等等。
  • 而 socket 是操做系統提供的一套接口,利用這一套接口就能夠編寫程序實現進程之間的通訊、網絡通訊等功能。

一個 WebSocket 鏈接是如何創建起來的

WebSocket 鏈接的初期是基於 HTTP 協議的,假如 WebSocket 的地址是這個:wss://www.xxx.com/websocket ,在鏈接 WebSocket 的初期瀏覽器首先會向這個地址發出一個 HTTP GET 請求,請求頭信息截圖以下:web

紅色框標出的是比較重要的請求頭:redis

  • Connection: Upgrade 告訴服務端這個鏈接須要升級。
  • Upgrade: websocket 告訴服務端須要升級到 WebSocket 協議。
  • Sec-WebSocket-Key: d97OXZzuRlSJV/6SrX+uUA== 是瀏覽器隨機生成的一個字符串。

服務端接收到這個 HTTP 請求,會做出響應,響應頭的截圖以下:算法

紅色框標出的是比較重要的響應頭:編程

  • HTTP/1.1 101 Switching Protocols 告訴瀏覽器,服務端已經成功切換了協議。
  • Sec-WebSocket-Accept: axMY+KY1i8F9y9zyUMPhrfuYtPw= 這個是服務端拿到請求頭中的 Sec-WebSocket-Key: d97OXZzuRlSJV/6SrX+uUA==,在 d97OXZzuRlSJV/6SrX+uUA== 後面拼接一個固定的字符串 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 ,對拼接後的字符串作SHA1,獲得16進製表示的字符串,將每兩位看成一個字節進行分隔,獲得字節數組,再對這個字節數組作Base64,獲得最後的結果,把最後的結果放到 Sec-WebSocket-Accept 響應頭裏返回。

瀏覽器也會使用一樣的算法把請求頭中的 Sec-WebSocket-Key 算出一個結果,將這個結果與服務端返回的 Sec-WebSocket-Accept 作對比。就像對暗號同樣,兩邊的暗號相同,WebSocket 鏈接就會被創建起來。這個過程也叫作握手,握手成功後,就能夠愉快的使用這個 WebSocket 鏈接來收發消息了。數組

操做系統提供的 socket 接口

WebSocket 的通訊,實際上是利用了操做系統給咱們提供的一套 socket 編程接口。接下來,我把 Linux 系統中給咱們提供的 socket 頭文件找出來,看看裏面有哪些接口提供給咱們使用,以及每一個接口的做用是什麼。找到 socket.h 頭文件在以下位置:瀏覽器

打開 socket.h 文件:服務器

打開另外一個目錄下的 socket.h 文件:websocket

socket 編程的流程以下:

在 socket 服務端除了用到上面流程圖列出來的函數,還用到了 setsockopt() 函數,這個函數能夠用來設置一些 socket 選項。好比:我在開發調試的過程當中,改完代碼後須要殺掉運行中的 socket 進程,從新運行新編譯出來的 socket。這時候常常會運行失敗,緣由是進程是立馬被殺掉了,可是原來被進程監聽的那個端口會進入 TIME_WAIT 狀態,而不會當即被釋放出來。解決方法有兩個:一、殺掉進程後等一下子,端口被釋放了就能被再次使用了。二、在綁定端口以前,利用 setsockopt() 函數,給端口設置一個 SO_REUSEPORT 選項,這樣殺掉這個進程後立馬從新運行這個進程,也不會運行失敗。

IO 多路複用(IO Multiplexing)

在項目中還用到了IO 多路複用:

  • 什麼是 IO ?答:計算機的輸入和輸出(Input、Output)
  • 什麼是 IO 多路複用?答:網上看到一個例子比較有意思。假如一個班有 50 名學生,老師在黑板上佈置了一道題目讓學生作,
    若是老師按照學號先看 1 號學生作出來沒有,作出來了就檢查他,還沒作出來就在原地等他作出來,而後檢查他,檢查完 1 號學生才輪到 2 號學生......這個就是單進程/單線程。
    若是老師能分身,一共分出 50 個分身,每一個學生旁邊站一個老師......這就是多進程/多線程。
    若是老師站在講臺上,有哪位學生作完了就舉手,老師下去檢查他,檢查完老師又回到講臺上,看有哪位同窗舉手,而後去檢查他......這就是 IO 多路複用。

IO 多路複用有3 種:select、poll、epoll。在項目中用到的是 epoll。接下來,我把 Linux 系統中給咱們提供的 epoll 頭文件找出來,看看裏面有哪些接口提供給咱們使用,以及每一個接口的做用是什麼。找到 epoll.h 頭文件在以下位置:

打開 epoll.h 文件:

epoll 的使用流程以下:

看到網上有文章說 redis 和 nginx 也有使用 epoll,爲了驗證他講的是否是真的。咱們找 redis 和 nginx 的源碼看一看:

果真 redis 和 nginx 的源碼裏面都有使用 epoll。

WebSocket 編程,還有其餘方案

Swoole 擴展:

  • 須要 php-7.1 或更高版本
  • 用法以下:
//建立WebSocket Server對象,監聽0.0.0.0:9502端口
$ws = new Swoole\WebSocket\Server('0.0.0.0', 9502);

//監聽WebSocket鏈接打開事件
$ws->on('open', function ($ws, $request) {
    var_dump($request->fd, $request->server);
    $ws->push($request->fd, "hello, welcome\n");
});

//監聽WebSocket消息事件
$ws->on('message', function ($ws, $frame) {
    echo "Message: {$frame->data}\n";
    $ws->push($frame->fd, "server: {$frame->data}");
});

//監聽WebSocket鏈接關閉事件
$ws->on('close', function ($ws, $fd) {
    echo "client-{$fd} is closed\n";
});

$ws->start();

想了解更多,請參考 Swoole 官方文檔:https://wiki.swoole.com/#/

Workerman:

在學習 WebSocket 的過程當中,還發現了一個純 PHP 實現的框架:Workerman

  • 須要 PHP 5.3.3 或更高版本
  • 用法以下:
<?php
use Workerman\Worker;
require_once __DIR__ . '/Workerman/Autoloader.php';

// 注意:這裏與上個例子不一樣,使用的是websocket協議
$ws_worker = new Worker("websocket://0.0.0.0:2000");

// 啓動4個進程對外提供服務
$ws_worker->count = 4;

// 當收到客戶端發來的數據後返回hello $data給客戶端
$ws_worker->onMessage = function($connection, $data)
{
    // 向客戶端發送hello $data
    $connection->send('hello ' . $data);
};

// 運行worker
Worker::runAll();

想了解更多,請參考 Workerman 官方文檔:http://doc.workerman.net/

相關文章
相關標籤/搜索