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

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

網頁實時聊天之PHP實現(xiàn)websocket

Nov 30, 2016 pm 11:59 PM

前言

websocket 作為 HTML5 里一個新的特性一直很受人關注,因為它真的非???,打破了 http “請求-響應”的常規(guī)思維,實現(xiàn)了服務器向客戶端主動推送消息,本文介紹如何使用 PHP 和 JS 應用 websocket 實現(xiàn)一個網頁實時聊天室;

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

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

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

然后當然是源碼: 我是源碼鏈接 - github - 枕邊書


websocket

簡介

WebSocket 不是一門技術,而是一種全新的協(xié)議。它應用 TCP 的 Socket(套接字),為網絡應用定義了一個新的重要的能力:客戶端和服務器端的雙全工傳輸和雙向通信。是繼 Java applets、 XMLHttpRequest、 Adobe Flash,、ActiveXObject、 各類 Comet 技術之后,服務器推送客戶端消息的新趨勢。

與http的關系

在網絡分層上,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 后,便建立了連接,完成了握手;

數(shù)據傳輸

websocket 有自己規(guī)定的數(shù)據傳輸格式,稱為 幀(Frame),下圖是一個數(shù)據幀的結構,其中單位為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ù)據幀 感覺自己對二進制的操作還不是很靈活,也就沒有挑戰(zhàn)自己寫算法解析數(shù)據了,下面的數(shù)據幀解析和封裝都是使用的網上的算法。

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


PHP 實現(xiàn) websocket 服務器

PHP 實現(xiàn) websocket 的話,主要是應用 PHP 的 socket 函數(shù)庫:

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

下面會在代碼中對所用函數(shù)進行簡單的注釋。

文件描述符

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

但作為服務器,是必須要對已經連接的 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 自然也繼承了這一點,它創(chuàng)建的 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ù)據,并處理應答邏輯
        } else {
            // 函數(shù) socket_recv() 從 socket 中接受長度為 len 字節(jié)的數(shù)據,并保存在 $buffer 中。
            $bytes = @socket_recv($socket, $buffer, 2048, 0);

            if ($bytes < 9) {
                // 當客戶端忽然中斷時,服務器會接收到一個 8 字節(jié)長度的消息(由于其數(shù)據幀機制,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ù)據幀解析和封裝的方法,各位也不一定看愛,有興趣的可以去 github 上支持一下我的源碼~~

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

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

客戶端

創(chuàng)建客戶端

前端我們使用 js 調用 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ā)送消息也很簡單,直接調用 ws.send(msg) 方法就行了。

頁面功能

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

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

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

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

用戶名異步處理

這里不得不提一下用戶登陸時確定用戶名時的一個小問題,我原來是想在客戶端創(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ā)送用戶名,于是成功在第一時間更新用戶名。


小結

聊天室擴展方向

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

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

總結

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

參考:

websocket協(xié)議翻譯

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

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

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

本站聲明
本文內容由網友自發(fā)貢獻,版權歸原作者所有,本站不承擔相應法律責任。如您發(fā)現(xiàn)有涉嫌抄襲侵權的內容,請聯(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

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

SublimeText3 Mac版

SublimeText3 Mac版

神級代碼編輯軟件(SublimeText3)