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

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

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

Nov 30, 2016 pm 11:59 PM

前言

websocket作為HTML5裡的一個新的一直很受人們關注,因為它真的非???,打破了http“請求響應”的慣常思維,實現(xiàn)了服務器向客戶端主動積極的消息,本文介紹瞭如何使用PHP和JS應用websocket實現(xiàn)一個網(wǎng)頁即時聊天室;

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

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

正文開始前,先貼一張聊天室的效果圖(請不要在意CSS渣的頁面):

然後當然是源碼: 我是連結 - github - 枕邊書


websocket

簡介

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

與http的關係

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

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

握手

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

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

資料傳輸

websocket 有自己規(guī)定的資料傳輸格式,稱為 幀(Frame),下圖是一個資料幀的結構,其中單位為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 ...                |
 +---------------------------------------------------------------+

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

不過,我工作中寫支付網(wǎng)關中還是會經(jīng)常用到數(shù)據(jù)的進制操作的,這個一定是要仔細研究總結一下的,嗯,先記下。


PHP 實作 websocket 伺服器

PHP 實作 websocket 的話,主要是應用 PHP 的 socket 函式庫:

PHP 的 socket 函數(shù)庫跟 C 語言的 socket 函數(shù)非常類似,以前翻過一遍 APUE, 所以覺得還挺好理解。在 PHP 手冊中看一遍 socket 函數(shù),我想大家也能對 php 的 socket 程式設計有一定的認知。

下面會在程式碼中對所用函數(shù)進行簡單的註解。

文件描述符

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

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

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

tips: linux 中, 標準輸入對應的是檔案描述符 0;標準輸出對應的檔案描述子是 1;標準錯誤對應的檔案描述子是 2;所以我們可以使用 0 1 2對輸入輸出重定向。

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

結果類似:

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

創(chuàng)建伺服器socket

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

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

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

服務器邏輯

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

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

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

下面是服務器的主要邏輯:

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

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

            if ($bytes < 9) {
                // 當客戶端忽然中斷時,服務器會接收到一個 8 字節(jié)長度的消息(由于其數(shù)據(jù)幀機制,8字節(jié)的消息我們認為它是客戶端異常中斷消息),服務器處理下線邏輯,并將其封裝為消息廣播出去
                $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);
        }
    }
}

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

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

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

客戶端

創(chuàng)建客戶端

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

// 創(chuàng)建一個 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 錯誤事件
ws.onerror = function () {
};

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

頁面功能

頁面部分主要是讓用戶使用起來方便,這里給消息框 textarea 添加了一個鍵盤監(jiān)控事件,當用戶按下回車鍵時直接發(fā)送消息;

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

還有用戶打開客戶端時生成一個默認唯一用戶名;

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

用戶名異步處理

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

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

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

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


小結

聊天室擴展方向

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

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

總結

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

參考:

websocket協(xié)議翻譯

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

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

嗯,持續(xù)更新。喜歡的可以點個推薦或關注,有錯漏之處,請指正,謝謝。

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

熱AI工具

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Undresser.AI Undress

Undresser.AI Undress

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

AI Clothes Remover

AI Clothes Remover

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

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

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

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

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

Dreamweaver CS6

Dreamweaver CS6

視覺化網(wǎng)頁開發(fā)工具

SublimeText3 Mac版

SublimeText3 Mac版

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