websocket(示例php實現(xiàn))

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

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

傳統(tǒng)的HTTP協(xié)議是一個請求-響應協(xié)議,瀏覽器不主動請求论悴,服務器是沒法主動發(fā)數(shù)據(jù)給瀏覽器的掖棉。

傳統(tǒng)服務器推送方式

Ajax 輪詢

瀏覽器通過JavaScript啟動一個定時器,然后以固定的間隔給服務器發(fā)請求膀估,詢問服務器有沒有新消息幔亥。

缺點

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

服務器反推

本質(zhì)上也是輪詢察纯,但是在沒有消息的情況下帕棉,服務器先拖一段時間,等到有消息了再回復饼记。暫時地解決了實時性問題香伴。

缺點

  • 以多線程模式運行的服務器會讓大部分線程大部分時間都處于掛起狀態(tài),極大地浪費服務器資源具则。
  • 一個HTTP連接在長時間沒有數(shù)據(jù)傳輸?shù)那闆r下即纲,鏈路上的任何一個網(wǎng)關都可能關閉這個連接。 長期占用連接博肋,喪失了無狀態(tài)高并發(fā)的特點低斋。

WebSocket協(xié)議

WebSocket并不是全新的協(xié)議蜂厅,而是利用了HTTP協(xié)議來建立TCP連接。

請求

WebSocket連接必須由瀏覽器發(fā)起拔稳,因為請求協(xié)議是一個標準的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表示這個連接將要被轉(zhuǎn)換為WebSocket連接;
  • Sec-WebSocket-Key是用于標識這個連接礁遵,并非用于加密數(shù)據(jù)轻绞;
  • Sec-WebSocket-Version指定了WebSocket的協(xié)議版本。

響應

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

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

響應代碼101表示本次連接的HTTP協(xié)議即將被更改政勃,更改后的協(xié)議就是Upgrade: websocket指定的WebSocket協(xié)議

WebSocket、HTTP 與 TCP 區(qū)別

HTTP兼砖、WebSocket 等應用層協(xié)議奸远,都是基于 TCP 協(xié)議來傳輸數(shù)據(jù)的。 所以連接和斷開讽挟,都要遵循 TCP 協(xié)議中的三次握手和四次握手 懒叛,只是在連接之后發(fā)送的內(nèi)容不同,或者是斷開的時間不同耽梅。

對于 WebSocket 來說薛窥,它必須依賴 HTTP 協(xié)議進行一次握手 ,握手成功后眼姐,數(shù)據(jù)就直接從 TCP 通道傳輸诅迷,與 HTTP 無關了。

Socket 與 WebScoket

Socket是應用層與TCP/IP協(xié)議族通信的中間軟件抽象層众旗,它是一組接口罢杉。在設計模式中,Socket其實就是一個門面模式贡歧,它把復雜的TCP/IP協(xié)議族隱藏在Socket接口后面滩租,對用戶來說,一組簡單的接口就是全部艘款,讓Socket去組織數(shù)據(jù)持际,以符合指定的協(xié)議。

image

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

WebSocket 則不同展鸡,它是一個完整的 應用層協(xié)議,包含一套標準的 API埃难。

從使用上來說莹弊,WebSocket 更易用,而 Socket 更靈活涡尘。

HTML5 與 WebSocket

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

注意事項

長連接應用必須加心跳细疚,否則連接可能由于長時間未通訊被路由節(jié)點強行斷開。

消息堆積

心跳作用主要有兩個

1川梅、客戶端定時給服務端發(fā)送點數(shù)據(jù)疯兼,防止連接由于長時間沒有通訊而被某些節(jié)點的防火墻關閉導致連接斷開的情況。

2贫途、服務端可以通過心跳來判斷客戶端是否在線吧彪,如果客戶端在規(guī)定時間內(nèi)沒有發(fā)來任何數(shù)據(jù),就認為客戶端下線潮饱。這樣可以檢測到客戶端由于極端情況(斷電来氧、斷網(wǎng)等)下線的事件。

心跳間隔建議值:

建議客戶端發(fā)送心跳間隔小于60秒香拉,比如55秒啦扬。

HTML5 WebSocket

WebSocket 屬性

Socket.readyState

只讀屬性 readyState 表示連接狀態(tài),可以是以下值:

0 - 表示連接尚未建立凫碌。
1 - 表示連接已建立扑毡,可以進行通信。
2 - 表示連接正在進行關閉盛险。
3 - 表示連接已經(jīng)關閉或者連接不能打開瞄摊。

Socket.bufferedAmount

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

WebSocket 事件

假定我們使用了以上代碼創(chuàng)建了 Socket 對象:

Socket.onopen   連接建立時觸發(fā)
Socket.onmessage    客戶端接收服務端數(shù)據(jù)時觸發(fā)
Socket.onerror  通信發(fā)生錯誤時觸發(fā)
Socket.onclose  連接關閉時觸發(fā)

WebSocket 方法

假定我們使用了以上代碼創(chuàng)建了 Socket 對象:

Socket.send()    使用連接發(fā)送數(shù)據(jù) 
Socket.close()   關閉連接

參考:
WebSocket

注意是事項

注意:長連接應用必須加心跳换帜,否則連接可能由于長時間未通訊被路由節(jié)點強行斷開。

心跳作用主要有兩個:

1鹤啡、客戶端定時給服務端發(fā)送點數(shù)據(jù)惯驼,防止連接由于長時間沒有通訊而被某些節(jié)點的防火墻關閉導致連接斷開的情況。

2、服務端可以通過心跳來判斷客戶端是否在線祟牲,如果客戶端在規(guī)定時間內(nèi)沒有發(fā)來任何數(shù)據(jù)隙畜,就認為客戶端下線。這樣可以檢測到客戶端由于極端情況(斷電说贝、斷網(wǎng)等)下線的事件议惰。

心跳間隔建議值:

建議客戶端發(fā)送心跳間隔小于60秒,比如55秒乡恕。

代碼演示

服務端代碼:

這里采用php方式來進行演示言询,其他語言也是類似,這里不在敘述几颜。

<?php

use Workerman\Worker;

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

use \Workerman\Lib\Timer;

// 創(chuàng)建一個Worker監(jiān)聽2000端口倍试,使用websocket協(xié)議通訊
$ws_worker = new Worker("websocket://0.0.0.0:2000");

// 進程數(shù)設置為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;
            }

            // 上次通訊時間間隔大于心跳間隔谆趾,則認為客戶端已經(jīng)下線,關閉連接
            if (HEARTBEAT_TIME > 0 && $time_now - $connection->lastMessageTime > HEARTBEAT_TIME) {
                $connection->close("Network connection timeout!");
            }

            // 如果心跳間隔設置為0的話叛本,可以服務端主動發(fā)送ping
            if (HEARTBEAT_TIME == 0) {
                $connection->send('ping');
            }

        }
    });
};


$ws_worker->onMessage = function ($connection, $data) {
    global $ws_worker;
    // 假設消息格式為
    // uid:message 時是對 uid 發(fā)送 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發(fā)送
    else {

        // 可以向執(zhí)行的uid發(fā)送消息
        sendMessageByUid($recv_uid, $message);
    }

};

/**
 * 直接將消息發(fā)送給用戶推送數(shù)據(jù)
 * @param $message
 */
function broadcast($message)
{
    global $ws_worker;
    foreach ($ws_worker->uidConnections as $connection) {
        $connection->send($message);
    }
}

/**
 * 針對uid推送數(shù)據(jù)
 * @param $uid
 * @param $message
 */
function sendMessageByUid($uid, $message)
{
    global $ws_worker;

    // 這里可以自定義自己的邏輯業(yè)務
    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("數(shù)據(jù)發(fā)送中...");
                };

                ws.onmessage = function (e) {
                    var received_msg = e.data;
                    console.log("數(shù)據(jù)已接收:" + 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>

效果

image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市来候,隨后出現(xiàn)的幾起案子跷叉,更是在濱河造成了極大的恐慌,老刑警劉巖营搅,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件云挟,死亡現(xiàn)場離奇詭異,居然都是意外死亡转质,警方通過查閱死者的電腦和手機园欣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來休蟹,“玉大人沸枯,你說我怎么就攤上這事÷腹” “怎么了绑榴?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長盈魁。 經(jīng)常有香客問我翔怎,道長,這世上最難降的妖魔是什么备埃? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任姓惑,我火速辦了婚禮褐奴,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘于毙。我一直安慰自己敦冬,他們只是感情好,可當我...
    茶點故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布唯沮。 她就那樣靜靜地躺著脖旱,像睡著了一般。 火紅的嫁衣襯著肌膚如雪介蛉。 梳的紋絲不亂的頭發(fā)上萌庆,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天,我揣著相機與錄音币旧,去河邊找鬼践险。 笑死,一個胖子當著我的面吹牛吹菱,可吹牛的內(nèi)容都是我干的巍虫。 我是一名探鬼主播,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼鳍刷,長吁一口氣:“原來是場噩夢啊……” “哼占遥!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起输瓜,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤瓦胎,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后尤揣,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體搔啊,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年芹缔,在試婚紗的時候發(fā)現(xiàn)自己被綠了坯癣。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡最欠,死狀恐怖示罗,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情芝硬,我是刑警寧澤蚜点,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站拌阴,受9級特大地震影響绍绘,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一陪拘、第九天 我趴在偏房一處隱蔽的房頂上張望厂镇。 院中可真熱鬧,春花似錦左刽、人聲如沸捺信。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽迄靠。三九已至,卻和暖如春喇辽,著一層夾襖步出監(jiān)牢的瞬間掌挚,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工菩咨, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留吠式,地道東北人。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓旦委,卻偏偏與公主長得像奇徒,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子缨硝,可洞房花燭夜當晚...
    茶點故事閱讀 43,697評論 2 351

推薦閱讀更多精彩內(nèi)容