• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            Fork me on GitHub
            隨筆 - 215  文章 - 13  trackbacks - 0
            <2016年12月>
            27282930123
            45678910
            11121314151617
            18192021222324
            25262728293031
            1234567


            專注即時通訊及網游服務端編程
            ------------------------------------
            Openresty 官方模塊
            Openresty 標準模塊(Opm)
            Openresty 三方模塊
            ------------------------------------
            本博收藏大部分文章為轉載,并在文章開頭給出了原文出處,如有再轉,敬請保留相關信息,這是大家對原創作者勞動成果的自覺尊重?。∪鐬槟鷰聿槐悖堄诒静┫铝粞?,謝謝配合。

            常用鏈接

            留言簿(1)

            隨筆分類

            隨筆檔案

            相冊

            Awesome

            Blog

            Book

            GitHub

            Link

            搜索

            •  

            積分與排名

            • 積分 - 215473
            • 排名 - 118

            最新評論

            閱讀排行榜

            http://www.jb51.net/article/48019.htm
            這篇文章主要介紹了php使用websocket示例,需要的朋友可以參考下

            下面我畫了一個圖演示 client 和 server 之間建立 websocket 連接時握手部分,這個部分在 node 中可以十分輕松的完成,因為 node 提供的 net 模塊已經對 socket 套接字做了封裝處理,開發者使用的時候只需要考慮數據的交互而不用處理連接的建立。而 php 沒有,從 socket 的連接、建立、綁定、監聽等,這些都需要我們自己去操作,所以有必要拿出來再說一說。



            ① 和 ② 實際上就是一個 HTTP 的請求和響應,只不過我們在處理的過程中我們拿到的是沒有經過解析的字符串。如:

             

            復制代碼 代碼如下:

            GET /chat HTTP/1.1
            Host: server.example.com
            Origin: http://www.jb51.com

             

            我們往常看到的請求是這個樣子,當這東西到了服務器端,我們可以通過一些代碼庫直接拿到這些信息。

            一、php 中處理 websocket

            WebSocket 連接是由客戶端主動發起的,所以一切要從客戶端出發。第一步是要解析拿到客戶端發過來的 Sec-WebSocket-Key 字符串。

             

            復制代碼 代碼如下:

            GET /chat HTTP/1.1
            Host: server.example.com
            Upgrade: websocket
            Connection: Upgrade
            Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
            Origin: http://www.jb51.com
            Sec-WebSocket-Protocol: chat, superchat
            Sec-WebSocket-Version: 13

             

            client 請求的格式

            首先 php 建立一個 socket 連接,監聽端口的信息。

            1. socket 連接的建立

            關于 socket 套接字的建立,相信很多大學修過計算機網絡的人都知道了,下面是一張連接建立的過程:



             

             

            復制代碼 代碼如下:

            // 建立一個 socket 套接字
            $master = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
            socket_set_option($master, SOL_SOCKET, SO_REUSEADDR, 1);
            socket_bind($master$address$port);
            socket_listen($master);

             

            相比 node,這個地方的處理實在是太麻煩了,上面幾行代碼并未建立連接,只不過這些代碼是建立一個 socket 套接字必須要寫的東西。由于處理過程稍微有復雜,所以我把各種處理寫進了一個類中,方便管理和調用。

             

            復制代碼 代碼如下:

            //demo.php
            Class WS {
                var $master;  // 連接 server 的 client
                var $sockets = array(); // 不同狀態的 socket 管理
                var $handshake = false// 判斷是否握手
                function __construct($address$port){
                    // 建立一個 socket 套接字
                    $this->master = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)   
                        or die("socket_create() failed");
                    socket_set_option($this->master, SOL_SOCKET, SO_REUSEADDR, 1)  
                        or die("socket_option() failed");
                    socket_bind($this->master, $address$port)                    
                        or die("socket_bind() failed");
                    socket_listen($this->master, 2)                               
                        or die("socket_listen() failed");
                    $this->sockets[] = $this->master;
                    // debug
                    echo("Master socket  : ".$this->master."\n");
                    while(true) {
                        //自動選擇來消息的 socket 如果是握手 自動選擇主機
                        $write = NULL;
                        $except = NULL;
                        socket_select($this->sockets, $write$exceptNULL);
                        foreach ($this->sockets as $socket) {
                            //連接主機的 client 
                            if ($socket == $this->master){
                                $client = socket_accept($this->master);
                                if ($client < 0) {
                                    // debug
                                    echo "socket_accept() failed";
                                    continue;
                                } else {
                                    //connect($client);
                                    array_push($this->sockets, $client);
                                    echo "connect client\n";
                                }
                            } else {
                                $bytes = @socket_recv($socket,$buffer,2048,0);
                                if($bytes == 0) return;
                                if (!$this->handshake) {
                                    // 如果沒有握手,先握手回應
                                    //doHandShake($socket, $buffer);

                                    echo "shakeHands\n";
                                } else {
                                    // 如果已經握手,直接接受數據,并處理
                                    $buffer = decode($buffer);
                                    //process($socket, $buffer); 
                                    echo "send file\n";
                                }
                            }
                        }
                    }
                }
            }

             

            上面這段代碼是經過我調試了的,沒太大的問題,如果想測試的話,可以在 cmd 命令行中鍵入 php /path/to/demo.php;當然,上面只是一個類,如果要測試的話,還得新建一個實例。

             

            復制代碼 代碼如下:

            $ws = new WS('localhost', 4000);

             

            客戶端代碼可以稍微簡單點:

             

            復制代碼 代碼如下:

            var ws = new WebSocket("ws://localhost:4000");
            ws.onopen = function(){
                console.log("握手成功");
            };
            ws.onerror = function(){
                console.log("error");
            };

             

            運行服務器代碼,當客戶端連接的時候,我們可以看到:

            2. 提取 Sec-WebSocket-Key 信息

             

            復制代碼 代碼如下:

            function getKey($req) {
                $key = null;
                if (preg_match("/Sec-WebSocket-Key: (.*)\r\n/", $req$match)) { 
                    $key = $match[1]; 
                }
                return $key;
            }

             

            這里比較簡單,直接正則匹配,websocket 信息頭一定包含 Sec-WebSocket-Key,所以我們匹配起來也比較快捷~

            3. 加密 Sec-WebSocket-Key

             

            復制代碼 代碼如下:

            function encry($req){
                $key = $this->getKey($req);
                $mask = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
                return base64_encode(sha1($key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true));
            }

             

            將 SHA-1 加密后的字符串再進行一次 base64 加密。如果加密算法錯誤,客戶端在進行校檢的時候會直接報錯:

            4. 應答 Sec-WebSocket-Accept

             

            復制代碼 代碼如下:

            function dohandshake($socket$req){
                // 獲取加密key
                $acceptKey = $this->encry($req);
                $upgrade = "HTTP/1.1 101 Switching Protocols\r\n" .
                           "Upgrade: websocket\r\n" .
                           "Connection: Upgrade\r\n" .
                           "Sec-WebSocket-Accept: " . $acceptKey . "\r\n" .
                           "\r\n";
                // 寫入socket
                socket_write(socket,$upgrade.chr(0), strlen($upgrade.chr(0)));
                // 標記握手已經成功,下次接受數據采用數據幀格式
                $this->handshake = true;
            }

             

            這里千萬要注意,每一個請求和相應的格式,最后有一個空行,也就是 \r\n,開始測試的時候把這東西給弄丟了,糾結了半天。



             

            當客戶端成功校檢key后,會觸發 onopen 函數:



             

            5. 數據幀處理

            復制代碼 代碼如下:

            // 解析數據幀
            function decode($buffer)  {
                $len = $masks = $data = $decoded = null;
                $len = ord($buffer[1]) & 127;
                if ($len === 126)  {
                    $masks = substr($buffer, 4, 4);
                    $data = substr($buffer, 8);
                } else if ($len === 127)  {
                    $masks = substr($buffer, 10, 4);
                    $data = substr($buffer, 14);
                } else  {
                    $masks = substr($buffer, 2, 4);
                    $data = substr($buffer, 6);
                }
                for ($index = 0; $index < strlen($data); $index++) {
                    $decoded .= $data[$index] ^ $masks[$index % 4];
                }
                return $decoded;
            }

             

            這里涉及的編碼問題在前文中已經提到過了,這里就不贅述,php 對字符處理的函數太多了,也記得不是特別清楚,這里就沒有詳細的介紹解碼程序,直接把客戶端發送的數據原樣返回,可以算是一個聊天室的模式吧。

            復制代碼 代碼如下:
            // 返回幀信息處理
            function frame($s) {
                $a = str_split($s, 125);
                if (count($a) == 1) {
                    return "\x81" . chr(strlen($a[0])) . $a[0];
                }
                $ns = "";
                foreach ($a as $o) {
                    $ns .= "\x81" . chr(strlen($o)) . $o;
                }
                return $ns;
            }
            // 返回數據
            function send($client$msg){
                $msg = $this->frame($msg);
                socket_write($client$msgstrlen($msg));
            }
            客戶端代碼:
            復制代碼 代碼如下:

            var ws = new WebSocket("ws://localhost:4000");
            ws.onopen = function(){
                console.log("握手成功");
            };
            ws.onmessage = function(e){
                console.log("message:" + e.data);
            };
            ws.onerror = function(){
                console.log("error");
            };
            ws.send("李靖");

             

            在連通之后發送數據,服務器原樣返回:

            二、注意問題

            1. websocket 版本問題

            客戶端在握手時的請求中有Sec-WebSocket-Version: 13,這樣的版本標識,這個是一個升級版本,現在的瀏覽器都是使用的這個版本。而以前的版本在數據加密的部分更加麻煩,它會發送兩個key:

            復制代碼 代碼如下:

            GET /chat HTTP/1.1
            Host: server.example.com
            Upgrade: websocket
            Connection: Upgrade
            Origin: http://www.jb51.net
            Sec-WebSocket-Protocol: chat, superchat
            Sec-WebSocket-Key1: xxxx
            Sec-WebSocket-Key2: xxxx

             

            如果是這種版本(比較老,已經沒在使用了),需要通過下面的方式獲取

            復制代碼 代碼如下:

            function encry($key1,$key2,$l8b){ //Get the numbers preg_match_all('/([\d]+)/', $key1, $key1_num); preg_match_all('/([\d]+)/', $key2, $key2_num);
            $key1_num = implode($key1_num[0]);
            $key2_num = implode($key2_num[0]);
            //Count spaces
            preg_match_all('/([ ]+)/', $key1$key1_spc);
            preg_match_all('/([ ]+)/', $key2$key2_spc);
            if($key1_spc==0|$key2_spc==0){ $this->log("Invalid key");return; }
            //Some math
            $key1_sec = pack("N",$key1_num / $key1_spc);
            $key2_sec = pack("N",$key2_num / $key2_spc);
            return md5($key1_sec.$key2_sec.$l8b,1);
            }

             

            只能無限吐槽這種驗證方式!相比 nodeJs 的 websocket 操作方式:

            復制代碼 代碼如下:

            //服務器程序
            var crypto = require('crypto');
            var WS = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
            require('net').createServer(function(o){
            var key;
            o.on('data',function(e){
            if(!key){
            //握手
            key = e.toString().match(/Sec-WebSocket-Key: (.+)/)[1];
            key = crypto.createHash('sha1').update(key + WS).digest('base64');
            o.write('HTTP/1.1 101 Switching Protocols\r\n');
            o.write('Upgrade: websocket\r\n');
            o.write('Connection: Upgrade\r\n');
            o.write('Sec-WebSocket-Accept: ' + key + '\r\n');
            o.write('\r\n');
            }else{
            console.log(e);
            };
            });
            }).listen(8000);

             

            2. 數據幀解析代碼

            本文沒有給出 decodeFrame 這樣數據幀解析代碼,前文中給出了數據幀的格式,解析純屬體力活。

            您可能感興趣的文章:

            posted on 2016-08-18 10:52 思月行云 閱讀(534) 評論(0)  編輯 收藏 引用 所屬分類: PHP
            九九久久99综合一区二区| 中文字幕无码久久精品青草 | 日韩精品无码久久久久久| 久久久一本精品99久久精品88| 亚洲性久久久影院| 国产精品免费福利久久| 国产精品成人久久久久三级午夜电影| 99久久精品免费国产大片| 久久综合色老色| 国产精品久久久久影院嫩草| 久久中文字幕视频、最近更新| 亚洲综合精品香蕉久久网| 国产激情久久久久影院小草| 国产精品久久久久蜜芽| 久久久久夜夜夜精品国产| 久久婷婷国产剧情内射白浆| 久久综合狠狠综合久久激情 | 99久久香蕉国产线看观香| 国产精品久久久久一区二区三区| 久久综合色老色| 色综合久久天天综线观看| 久久九九亚洲精品| 久久亚洲精品无码AV红樱桃| 久久夜色精品国产亚洲av| 91精品久久久久久无码| 亚洲国产精品无码久久久不卡| 久久亚洲国产精品123区| 亚洲国产二区三区久久| 久久久免费精品re6| 亚洲色欲久久久综合网东京热| 久久涩综合| 久久成人18免费网站| 97超级碰碰碰碰久久久久| 九九精品99久久久香蕉| 看久久久久久a级毛片| 人妻精品久久久久中文字幕一冢本| 亚洲国产日韩欧美综合久久| 日韩va亚洲va欧美va久久| 2021少妇久久久久久久久久| 久久99精品久久久久久久不卡| 麻豆一区二区99久久久久|