国产av日韩一区二区三区精品,成人性爱视频在线观看,国产,欧美,日韩,一区,www.成色av久久成人,2222eeee成人天堂

目錄
websocket
簡(jiǎn)介
與http的關(guān)係
握手
資料傳輸
PHP 實(shí)作 websocket 伺服器
文件描述符
創(chuàng)建伺服器socket
服務(wù)器邏輯
客戶端
創(chuàng)建客戶端
頁(yè)面功能
用戶名異步處理
小結(jié)
聊天室擴(kuò)展方向
總結(jié)
首頁(yè) php教程 php手冊(cè) 網(wǎng)頁(yè)即時(shí)聊天之PHP實(shí)現(xiàn)websocket

網(wǎng)頁(yè)即時(shí)聊天之PHP實(shí)現(xiàn)websocket

Nov 30, 2016 pm 11:59 PM

前言

websocket作為HTML5裡的一個(gè)新的一直很受人們關(guān)注,因?yàn)樗娴姆浅??,打破了http“請(qǐng)求響應(yīng)”的慣常思維,實(shí)現(xiàn)了服務(wù)器向客戶端主動(dòng)積極的消息,本文介紹瞭如何使用PHP和JS應(yīng)用websocket實(shí)現(xiàn)一個(gè)網(wǎng)頁(yè)即時(shí)聊天室;

之前寫(xiě)過(guò)一篇文章講述如何使用ajax長(zhǎng)輪詢實(shí)現(xiàn)網(wǎng)頁(yè)實(shí)時(shí)聊天,見(jiàn)鏈接:網(wǎng)頁(yè)實(shí)時(shí)聊天之js和jQuery實(shí)現(xiàn)ajax長(zhǎng)輪詢,但是輪詢和服務(wù)器的pending都是無(wú)所謂的消耗,websocket才是新趨勢(shì)。

最近艱難地「擠」了一點(diǎn)時(shí)間,完善了很早就做的websocket「請(qǐng)求原樣返回」伺服器,用js完善了下客戶端功能,把過(guò)程和思路分享給大家,順便也普及一下websocket相關(guān)的知識(shí),當(dāng)然現(xiàn)在討論websocket的文章也特別多,有些理論性的東西我也略過(guò)了,給出參考文章供大家選擇閱讀。

正文開(kāi)始前,先貼一張聊天室的效果圖(請(qǐng)不要在意CSS渣的頁(yè)面):

然後當(dāng)然是源碼: 我是連結(jié) - github - 枕邊書(shū)


websocket

簡(jiǎn)介

WebSocket 不是一門(mén)技術(shù),而是一種全新的協(xié)定。它應(yīng)用 TCP 的 Socket(套接字),為網(wǎng)路應(yīng)用定義了一個(gè)新的重要的能力:客戶端和伺服器端的雙全工傳輸和雙向通訊。是繼 Java applets、 XMLHttpRequest、 Adob????e Flash,、ActiveXObject、 各類(lèi) Comet 技術(shù)之後,伺服器推送客戶端訊息的新趨勢(shì)。

與http的關(guān)係

在網(wǎng)路分層上,websocket 與http 協(xié)議都是應(yīng)用層的協(xié)議,它們都是基於tcp 傳輸層的,但是websocket 在建立連接時(shí),是藉用http 的101 switch protocol 來(lái)達(dá)到協(xié)議轉(zhuǎn)換(Upgrade)的,從HTTP 協(xié)定切換成WebSocket 通訊協(xié)定,這個(gè)動(dòng)作協(xié)定中稱(chēng)為「握手」;

握手成功後,websocket 就使用自己的協(xié)議規(guī)定的方式進(jìn)行通訊,跟 http 就沒(méi)有關(guān)係了。

握手

以下是一個(gè)我自己的瀏覽器發(fā)送的典型的握手 http 頭:?

伺服器收到握手請(qǐng)求後,提取出請(qǐng)求頭中的“Sec-WebSocket-Key” 字段,追回一個(gè)固定的字符串'258EAFA5-E914-47DA-95CA-C5AB0DC85B11', 然後進(jìn)行sha1 加密,最後轉(zhuǎn)換為base64 編碼,作為key 以「Sec-WebSocket-Accept」 欄位返回給客戶端,客戶端匹配此key 後,便建立了連接,完成了握手;

資料傳輸

websocket 有自己規(guī)定的資料傳輸格式,稱(chēng)為 幀(Frame),下圖是一個(gè)資料幀的結(jié)構(gòu),其中單位為bit:

  0                   1                   2                   3
  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 +-+-+-+-+-------+-+-------------+-------------------------------+
 |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
 |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
 |N|V|V|V|       |S|             |   (if payload len==126/127)   |
 | |1|2|3|       |K|             |                               |
 +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
 |     Extended payload length continued, if payload len == 127  |
 + - - - - - - - - - - - - - - - +-------------------------------+
 |                               |Masking-key, if MASK set to 1  |
 +-------------------------------+-------------------------------+
 | Masking-key (continued)       |          Payload Data         |
 +-------------------------------- - - - - - - - - - - - - - - - +
 :                     Payload Data continued ...                :
 + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
 |                     Payload Data continued ...                |
 +---------------------------------------------------------------+

具體每個(gè)字段是什麼意思,有興趣的可以看一下這篇文章The WebSocket Protocol 5.數(shù)據(jù)幀感覺(jué)自己對(duì)二進(jìn)制的操作還不是很靈活,也就沒(méi)有挑戰(zhàn)自己寫(xiě)算法解析數(shù)據(jù)了,下面的數(shù)據(jù)幀解析和封裝都是使用的網(wǎng)路上的演算法。

不過(guò),我工作中寫(xiě)支付網(wǎng)關(guān)中還是會(huì)經(jīng)常用到數(shù)據(jù)的進(jìn)制操作的,這個(gè)一定是要仔細(xì)研究總結(jié)一下的,嗯,先記下。


PHP 實(shí)作 websocket 伺服器

PHP 實(shí)作 websocket 的話,主要是應(yīng)用 PHP 的 socket 函式庫(kù):

PHP 的 socket 函數(shù)庫(kù)跟 C 語(yǔ)言的 socket 函數(shù)非常類(lèi)似,以前翻過(guò)一遍 APUE, 所以覺(jué)得還挺好理解。在 PHP 手冊(cè)中看一遍 socket 函數(shù),我想大家也能對(duì) php 的 socket 程式設(shè)計(jì)有一定的認(rèn)知。

下面會(huì)在程式碼中對(duì)所用函數(shù)進(jìn)行簡(jiǎn)單的註解。

文件描述符

忽然提及'文件描述符',大家可能會(huì)有些奇怪。

但作為伺服器,是必須要對(duì)已經(jīng)連接的 socket 進(jìn)行儲(chǔ)存和識(shí)別的。每一個(gè) socket 代表一個(gè)用戶,如何關(guān)聯(lián)和查詢用戶資訊與 socket 的對(duì)應(yīng)就是一個(gè)問(wèn)題了,這裡便應(yīng)用了關(guān)於文件描述符的一點(diǎn)小技巧。

我們知道linux 是'萬(wàn)物皆文件'的,C 語(yǔ)言的socket 的實(shí)現(xiàn)便是一個(gè)個(gè)的'文件描述符' ,這個(gè)文件描述符一般是打開(kāi)文件的順序遞增的int 數(shù)值,從0 一直遞增(當(dāng)然系統(tǒng)是有限制的)。每一個(gè) socket 都對(duì)應(yīng)一個(gè)文件,讀寫(xiě) socket 都是操作對(duì)應(yīng)的文件,所以也能像檔案系統(tǒng)一樣應(yīng)用 read 和 write 函數(shù)。

tips: linux 中, 標(biāo)準(zhǔn)輸入對(duì)應(yīng)的是檔案描述符 0;標(biāo)準(zhǔn)輸出對(duì)應(yīng)的檔案描述子是 1;標(biāo)準(zhǔn)錯(cuò)誤對(duì)應(yīng)的檔案描述子是 2;所以我們可以使用 0 1 2對(duì)輸入輸出重定向。

那麼類(lèi)似 C socket 的 PHP socket 自然也繼承了這一點(diǎn),它所建立的 socket 也是型別於 int 值為 4 5 之類(lèi)的資源型別。 我們可以使用 (int) 或 intval() 函數(shù)把 socket 轉(zhuǎn)換成一個(gè)唯一的ID,從而可以實(shí)現(xiàn)用一個(gè) ’類(lèi)索引數(shù)組‘ 來(lái)存儲(chǔ) socket 資源和對(duì)應(yīng)的用戶資訊;

結(jié)果類(lèi)似:

$connected_sockets = array(
    (int)$socket => array(
        'resource' => $socket,
        'name' => $name,
        'ip' => $ip,
        'port' => $port,
        ...
    )
)

創(chuàng)建伺服器socket

下面是一段創(chuàng)建伺服器 socket 的程式碼:

// 創(chuàng)建一個(gè) TCP socket, 此函數(shù)的可選值在官方文檔中寫(xiě)得十分詳細(xì),這里不再提了
$this->master = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
// 設(shè)置IP和端口重用,在重啟服務(wù)器后能重新使用此端口;
socket_set_option($this->master, SOL_SOCKET, SO_REUSEADDR, 1);
// 將IP和端口綁定在服務(wù)器socket上;
socket_bind($this->master, $host, $port);
// listen函數(shù)使主動(dòng)連接套接口變?yōu)楸贿B接套接口,使得此 socket 能被其他 socket 訪問(wèn),從而實(shí)現(xiàn)服務(wù)器功能。后面的參數(shù)則是自定義的待處理socket的最大數(shù)目,并發(fā)高的情況下,這個(gè)值可以設(shè)置大一點(diǎn),雖然它也受系統(tǒng)環(huán)境的約束。
socket_listen($this->master, self::LISTEN_SOCKET_NUM);

這樣,我們就得到一個(gè)服務(wù)器 socket,當(dāng)有客戶端連接到此 socket 上時(shí),它將改變狀態(tài)為可讀,那就看接下來(lái)服務(wù)器的處理邏輯了。

服務(wù)器邏輯

這里著重講一下 socket_select($read, $write, $except, $tv_sec [, $tv_usec]):

select 函數(shù)使用傳統(tǒng)的 select 模型,可讀、寫(xiě)、異常的 socket 會(huì)被分別放入 $socket, $write, $except 數(shù)組中,然后返回 狀態(tài)改變的 socket 的數(shù)目,如果發(fā)生了錯(cuò)誤,函數(shù)將會(huì)返回 false.

需要注意的是最后兩個(gè)時(shí)間參數(shù),它們只有單位不同,可以搭配使用,用來(lái)表示 socket_select 阻塞的時(shí)長(zhǎng),為0時(shí)此函數(shù)立即返回,可以用于輪詢機(jī)制。 為 NULL 時(shí),函數(shù)會(huì)一直阻塞下去, 這里我們置 $tv_sec 參數(shù)為null,讓它一直阻塞,直到有可操作的 socket 返回。

下面是服務(wù)器的主要邏輯:

$write = $except = NULL;
$sockets = array_column($this->sockets, 'resource'); // 獲取到全部的 socket 資源
$read_num = socket_select($sockets, $write, $except, NULL);

foreach ($sockets as $socket) {
        // 如果可讀的是服務(wù)器 socket, 則處理連接邏輯;            
        if ($socket == $this->master) {
            socket_accept($this->master);
            // socket_accept() 接受 請(qǐng)求 “正在 listen 的 socket(像我們的服務(wù)器 socket )” 的連接, 并一個(gè)客戶端 socket, 錯(cuò)誤時(shí)返回 false;
             self::connect($client);
             continue;
            }
        // 如果可讀的是其他已連接 socket ,則讀取其數(shù)據(jù),并處理應(yīng)答邏輯
        } else {
            // 函數(shù) socket_recv() 從 socket 中接受長(zhǎng)度為 len 字節(jié)的數(shù)據(jù),并保存在 $buffer 中。
            $bytes = @socket_recv($socket, $buffer, 2048, 0);

            if ($bytes < 9) {
                // 當(dāng)客戶端忽然中斷時(shí),服務(wù)器會(huì)接收到一個(gè) 8 字節(jié)長(zhǎng)度的消息(由于其數(shù)據(jù)幀機(jī)制,8字節(jié)的消息我們認(rèn)為它是客戶端異常中斷消息),服務(wù)器處理下線邏輯,并將其封裝為消息廣播出去
                $recv_msg = $this->disconnect($socket);
            } else {
                // 如果此客戶端還未握手,執(zhí)行握手邏輯
                if (!$this->sockets[(int)$socket]['handshake']) {
                    self::handShake($socket, $buffer);
                    continue;
                } else {
                    $recv_msg = self::parse($buffer);
                }
            }

            // 廣播消息
            $this->broadcast($msg);
        }
    }
}

這里只是服務(wù)器處理消息的基礎(chǔ)代碼,日志記錄和異常處理都略過(guò)了,而且還有些數(shù)據(jù)幀解析和封裝的方法,各位也不一定看愛(ài),有興趣的可以去 github 上支持一下我的源碼~~

此外,為了便于服務(wù)器與客戶端的交互,我自己定義了 json 類(lèi)型的消息格式,形似:

$msg = [
    'type' => $msg_type, // 有普通消息,上下線消息,服務(wù)器消息
    'from' => $msg_resource, // 消息來(lái)源
    'content' => $msg_content, // 消息內(nèi)容
    'user_list' => $uname_list, // 便于同步當(dāng)前在線人數(shù)與姓名
    ];

客戶端

創(chuàng)建客戶端

前端我們使用 js 調(diào)用 Websocket 方法很簡(jiǎn)單就能創(chuàng)建一個(gè) websocket 連接,服務(wù)器會(huì)為幫我們完成連接、握手的操作,js 使用事件機(jī)制來(lái)處理瀏覽器與服務(wù)器的交互:

// 創(chuàng)建一個(gè) websocket 連接
var ws = new WebSocket("ws://127.0.0.1:8080");

// websocket 創(chuàng)建成功事件
ws.onopen = function () {
};

// websocket 接收到消息事件
ws.onmessage = function (e) {
    var msg = JSON.parse(e.data);
}

// websocket 錯(cuò)誤事件
ws.onerror = function () {
};

發(fā)送消息也很簡(jiǎn)單,直接調(diào)用 ws.send(msg) 方法就行了。

頁(yè)面功能

頁(yè)面部分主要是讓用戶使用起來(lái)方便,這里給消息框 textarea 添加了一個(gè)鍵盤(pán)監(jiān)控事件,當(dāng)用戶按下回車(chē)鍵時(shí)直接發(fā)送消息;

function confirm(event) {
    var key_num = event.keyCode;
    if (13 == key_num) {
        send();
    } else {
        return false;
    }
}

還有用戶打開(kāi)客戶端時(shí)生成一個(gè)默認(rèn)唯一用戶名;

然后是一些對(duì)數(shù)據(jù)的解析構(gòu)造,對(duì)客戶端頁(yè)面的更新,這里就不再啰嗦了,感興趣的可以看源碼。

用戶名異步處理

這里不得不提一下用戶登陸時(shí)確定用戶名時(shí)的一個(gè)小問(wèn)題,我原來(lái)是想在客戶端創(chuàng)建一個(gè)連接后直接發(fā)送用戶名到服務(wù)器,可是控制臺(tái)里報(bào)出了 “websocket 仍在連接中或已關(guān)閉” 的錯(cuò)誤信息。

Uncaught DOMException: Failed to execute 'send' on 'WebSocket': Still in CONNECTING state.

考慮到連接可能還沒(méi)處理好,我就實(shí)現(xiàn)了 sleep 方法等了一秒再發(fā)送用戶名,可是錯(cuò)誤仍然存在。

后來(lái)忽然想到 js 的單線程阻塞機(jī)制,才明白使用 sleep 一直阻塞也是沒(méi)有用的,利用好 js 的事件機(jī)制才是正道:于是在服務(wù)器端添加邏輯,在握手成功后,向客戶端發(fā)送握手已成功的消息;客戶端先將用戶名存入一個(gè)全局變量,接收到服務(wù)器的握手成功的提醒消息后再發(fā)送用戶名,于是成功在第一時(shí)間更新用戶名。


小結(jié)

聊天室擴(kuò)展方向

簡(jiǎn)易聊天室已經(jīng)完成,當(dāng)然還要給它帶有希望的美好未來(lái),希望有人去實(shí)現(xiàn):

  • 頁(yè)面美化(信息添加顏色等)
  • 服務(wù)器識(shí)別 '@' 字符而只向某一個(gè) socket 寫(xiě)數(shù)據(jù)實(shí)現(xiàn)聊天室的私聊;
  • 多進(jìn)程(使用 redis 等緩存數(shù)據(jù)庫(kù)來(lái)實(shí)現(xiàn)資源的共享),可參考我以前的一篇文章: 初探PHP多進(jìn)程
  • 消息記錄數(shù)據(jù)庫(kù)持久化(log 日志還是不方便分析)
  • ...

總結(jié)

多讀些經(jīng)典書(shū)籍還是很有用的,有些東西真的是觸類(lèi)旁通,APUE/UNP 還是要再多翻幾遍。此外互聯(lián)網(wǎng)技術(shù)日新月異,挑一些自己喜歡的學(xué)習(xí)一下,跟大家分享一下也是挺舒服的(雖然程序和博客加一塊用了至少10個(gè)小時(shí)...)。

參考:

websocket協(xié)議翻譯

刨根問(wèn)底 HTTP 和 WebSocket 協(xié)議(下)

學(xué)習(xí)WebSocket協(xié)議—從頂層到底層的實(shí)現(xiàn)原理(修訂版)

嗯,持續(xù)更新。喜歡的可以點(diǎn)個(gè)推薦或關(guān)注,有錯(cuò)漏之處,請(qǐng)指正,謝謝。

本網(wǎng)站聲明
本文內(nèi)容由網(wǎng)友自願(yuàn)投稿,版權(quán)歸原作者所有。本站不承擔(dān)相應(yīng)的法律責(zé)任。如發(fā)現(xiàn)涉嫌抄襲或侵權(quán)的內(nèi)容,請(qǐng)聯(lián)絡(luò)admin@php.cn

熱AI工具

Undress AI Tool

Undress AI Tool

免費(fèi)脫衣圖片

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅(qū)動(dòng)的應(yīng)用程序,用於創(chuàng)建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費(fèi)的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費(fèi)的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強(qiáng)大的PHP整合開(kāi)發(fā)環(huán)境

Dreamweaver CS6

Dreamweaver CS6

視覺(jué)化網(wǎng)頁(yè)開(kāi)發(fā)工具

SublimeText3 Mac版

SublimeText3 Mac版

神級(jí)程式碼編輯軟體(SublimeText3)

熱門(mén)話題