国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁(yè) > 編程 > PHP > 正文

Websocket協(xié)議之php實(shí)現(xiàn)

2020-03-22 19:33:03
字體:
來(lái)源:轉(zhuǎn)載
供稿:網(wǎng)友
  • 前面學(xué)習(xí)了HTML5中websocket的握手協(xié)議、打開(kāi)和關(guān)閉連接等基礎(chǔ)內(nèi)容,最近用php實(shí)現(xiàn)了與瀏覽器websocket的雙向通信。在學(xué)習(xí)概念的時(shí)候覺(jué)得看懂了的內(nèi)容,真正在實(shí)踐過(guò)程中還是會(huì)遇到各種問(wèn)題,網(wǎng)上也有一些關(guān)于php的websocket的實(shí)現(xiàn),但是只有自己親手寫(xiě)過(guò)之后才知道其中的感受。其中,google有一個(gè)開(kāi)源的phpwebsocket類(lèi)(https://code.google.com/p/phpwebsocket/),但是從其握手過(guò)程中可以明顯看出,這還是最初的websocket協(xié)議,請(qǐng)求頭中使用了兩個(gè)KEY,并非version 13(現(xiàn)行版本)。下面是本人實(shí)踐過(guò)程,同時(shí)封裝好了一個(gè)現(xiàn)行版本的php實(shí)現(xiàn)的實(shí)用的websocket類(lèi)。

    一、握手 1、客戶(hù)端發(fā)送請(qǐng)求

    websocket協(xié)議提供給javascript的API就是特別簡(jiǎn)潔易用。


    先看效果,客戶(hù)端和服務(wù)器端握手的結(jié)果如下:

    2、服務(wù)器端

    封裝的類(lèi)為WebSocket,address和port為類(lèi)的屬性。

    (1)建立socket并監(jiān)聽(tīng)
     1     function createSocket() 2     { 3         $this->master=socket_create(AF_INET, SOCK_STREAM, SOL_TCP) 4             or die('socket_create() failed:'.socket_strerror(socket_last_error())); 5              6         socket_set_option($this->master, SOL_SOCKET, SO_REUSEADDR, 1) 7             or die('socket_option() failed'.socket_strerror(socket_last_error())); 8              9         socket_bind($this->master, $this->address, $this->port)10             or die('socket_bind() failed'.socket_strerror(socket_last_error()));11             12         socket_listen($this->master,20)13             or die('socket_listen() failed'.socket_strerror(socket_last_error()));14         15         $this->say('Server Started : '.date('Y-m-d H:i:s'));16         $this->say('Master socket  : '.$this->master);17         $this->say('Listening on   : '.$this->address.' port '.$this->port.'');18         19     }

    然后啟動(dòng)監(jiān)聽(tīng),同時(shí)要維護(hù)連接到服務(wù)器的用戶(hù)的一個(gè)數(shù)組(連接池),每連接一個(gè)用戶(hù),就要push進(jìn)一個(gè),同時(shí)關(guān)閉連接后要?jiǎng)h除相應(yīng)的用戶(hù)的連接。

     1     html' target='_blank'>public function __construct($a, $p) 2     { 3         if ($a == 'localhost') 4             $this->address = $a; 5         else if (preg_match('/^[d.]*$/is', $a)) 6             $this->address = long2ip(ip2long($a)); 7         else 8             $this->address = $p; 9         10         if (is_numeric($p) && intval($p) > 1024 && intval($p) < 65536)11             $this->port = $p;12         else13             die ('Not valid port:' . $p);14         15         $this->createSocket();16         array_push($this->sockets, $this->master);17     }
    (2)建立連接

    維護(hù)用戶(hù)的連接池

    1     public function connect($clientSocket)2     {3         $user = new User();4         $user->id = uniqid();5         $user->socket = $clientSocket;6         array_push($this->users,$user);7         array_push($this->sockets,$clientSocket);8         $this->log($user->socket . ' CONNECTED!' . date('Y-m-d H-i-s'));9     }
    (3)回復(fù)響應(yīng)頭

    首先要獲取請(qǐng)求頭,從中取出Sec-Websocket-Key,同時(shí)還應(yīng)該取出Host、請(qǐng)求方式、Origin等,可以進(jìn)行安全檢查,防止未知的連接。

     1     public function getHeaders($req) 2     { 3         $r = $h = $o = null; 4         if(preg_match('/GET (.*) HTTP/'   , $req, $match)) 5             $r = $match[1]; 6         if(preg_match('/Host: (.*)/'  , $req, $match)) 7             $h = $match[1]; 8         if(preg_match('/Origin: (.*)/', $req, $match)) 9             $o = $match[1];10         if(preg_match('/Sec-WebSocket-Key: (.*)/', $req, $match))11             $key = $match[1];12             13         return array($r, $h, $o, $key);14     }

    之后是得到key然后進(jìn)行websocket協(xié)議規(guī)定的加密算法進(jìn)行計(jì)算,返回響應(yīng)頭,這樣瀏覽器驗(yàn)證正確后就握手成功了。這里涉及的詳細(xì)解析信息過(guò)程參見(jiàn)另一篇博文http://blog.csdn.net/u010487568/article/details/20569027

     1     protected function wrap($msg='', $opcode = 0x1) 2     { 3         //默認(rèn)控制幀為0x1(文本數(shù)據(jù)) 4         $firstByte = 0x80 | $opcode; 5         $encodedata = null; 6         $len = strlen($msg); 7          8         if (0 <= $len && $len <= 125) 9             $encodedata = chr(0x81) . chr($len) . $msg;10         else if (126 <= $len && $len <= 0xFFFF)11         {12             $low = $len & 0x00FF;13             $high = ($len & 0xFF00) >> 8;14             $encodedata = chr($firstByte) . chr(0x7E) . chr($high) . chr($low) . $msg;15         }16         17         return $encodedata;            18     }

    其中我只實(shí)現(xiàn)了發(fā)送數(shù)據(jù)長(zhǎng)度在2的16次方以下個(gè)字符的情況,至于長(zhǎng)度為8個(gè)字節(jié)的超大數(shù)據(jù)暫未考慮。

     1      private function doHandShake($user, $buffer) 2      { 3         $this->log('Requesting handshake...'); 4         $this->log($buffer); 5         list($resource, $host, $origin, $key) = $this->getHeaders($buffer); 6          7         //websocket version 13 8         $acceptKey = base64_encode(sha1($key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true)); 9         10         $this->log('Handshaking...');11         $upgrade  = 'HTTP/1.1 101 Switching Protocol' .12                     'Upgrade: websocket' .13                     'Connection: Upgrade' .14                     'Sec-WebSocket-Accept: ' . $acceptKey . '';  //必須以?xún)蓚€(gè)回車(chē)結(jié)尾15         $this->log($upgrade);16         $sent = socket_write($user->socket, $upgrade, strlen($upgrade));17         $user->handshake=true;18         $this->log('Done handshaking...');19         return true;20     }
    二、數(shù)據(jù)傳輸 1、客戶(hù)端

    客戶(hù)端websocket的API非常容易,直接使用websocket對(duì)象的send方法即可。

    1 ws.send(message);
    2、服務(wù)器端

    客戶(hù)端發(fā)送的數(shù)據(jù)是經(jīng)過(guò)瀏覽器支持的websocket進(jìn)行了mask處理的,而根據(jù)規(guī)定服務(wù)器端返回的數(shù)據(jù)不能進(jìn)行掩碼處理,但是需要按照協(xié)議的數(shù)據(jù)幀規(guī)定進(jìn)行封裝后發(fā)送。因此服務(wù)器需要接收數(shù)據(jù)必須將接收到的字節(jié)流進(jìn)行解碼。

     1     protected function unwrap($clientSocket, $msg='') 2     {  3         $opcode = ord(substr($msg, 0, 1)) & 0x0F; 4         $payloadlen = ord(substr($msg, 1, 1)) & 0x7F; 5         $ismask = (ord(substr($msg, 1, 1)) & 0x80) >> 7; 6         $maskkey = null; 7         $oridata = null; 8         $decodedata = null; 9         10         //關(guān)閉連接11         if ($ismask != 1 || $opcode == 0x8)12         {13             $this->disconnect($clientSocket);14             return null;15         }16         17         //獲取掩碼密鑰和原始數(shù)據(jù)18         if ($payloadlen <= 125 && $payloadlen >= 0)19         {20             $maskkey = substr($msg, 2, 4);21             $oridata = substr($msg, 6);22         }23         else if ($payloadlen == 126)24         {25             $maskkey = substr($msg, 4, 4);26             $oridata = substr($msg, 8);27         }28         else if ($payloadlen == 127)29         {30             $maskkey = substr($msg, 10, 4);31             $oridata = substr($msg, 14);32         }33         $len = strlen($oridata);34         for($i = 0; $i < $len; $i++)35         {36             $decodedata .= $oridata[$i] ^ $maskkey[$i % 4];37         }        38         return $decodedata; 39     }

    其中得到掩碼和控制幀后需要進(jìn)行驗(yàn)證,如果掩碼不為1直接關(guān)閉,如果控制幀為8也直接關(guān)閉。后面的原始數(shù)據(jù)和掩碼獲取是通過(guò)websocket協(xié)議的數(shù)據(jù)幀規(guī)范進(jìn)行的。

    效果如下



    數(shù)據(jù)交互的過(guò)程非常的直接,其中“u”是服務(wù)器發(fā)送給客戶(hù)端的,然后客戶(hù)端發(fā)送一段隨機(jī)字符串給服務(wù)器。

    三、連接關(guān)閉 1、客戶(hù)端
    1 ws.close();
    2、服務(wù)器端

    需要將維護(hù)的用戶(hù)連接池移除相應(yīng)的連接用戶(hù)。

     1     public function disconnect($clientSocket) 2     { 3         $found = null; 4         $n = count($this->users); 5         for($i = 0; $i<$n; $i++) 6         { 7             if($this->users[$i]->socket == $clientSocket) 8             {  9                 $found = $i;10                 break;11             }12         }13         $index = array_search($clientSocket,$this->sockets);14         15         if(!is_null($found))16         { 17             array_splice($this->users, $found, 1);18             array_splice($this->sockets, $index, 1); 19             20             socket_close($clientSocket);21             $this->say($clientSocket.' DISCONNECTED!');22         }23     }

    其中遇到的一個(gè)問(wèn)題就是,如果將上述函數(shù)中的socket_close語(yǔ)句提出到if語(yǔ)句外面的時(shí)候,當(dāng)瀏覽器連接到服務(wù)器后,F(xiàn)5刷新頁(yè)面后會(huì)發(fā)現(xiàn)出錯(cuò):

    后來(lái)發(fā)現(xiàn)是重復(fù)關(guān)閉socket了,這個(gè)是因?yàn)樵趗nwrap函數(shù)中遇到了控制幀直接關(guān)閉的原因。因此需要注意瀏覽器已經(jīng)連接后進(jìn)行刷新的操作。最后提供整個(gè)封裝好的類(lèi),https://github.com/OshynSong/web/blob/master/websocket.class.php

    PHP編程

鄭重聲明:本文版權(quán)歸原作者所有,轉(zhuǎn)載文章僅為傳播更多信息之目的,如作者信息標(biāo)記有誤,請(qǐng)第一時(shí)間聯(lián)系我們修改或刪除,多謝。

發(fā)表評(píng)論 共有條評(píng)論
用戶(hù)名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 土默特右旗| 青阳县| 灌南县| 石狮市| 宕昌县| 茂名市| 平乡县| 武义县| 阜宁县| 雅江县| 桂东县| 滨海县| 建瓯市| 达拉特旗| 阜南县| 平武县| 昌江| 沿河| 夏河县| 恩平市| 灵宝市| 蕉岭县| 饶河县| 台湾省| 泸州市| 大兴区| 锡林郭勒盟| 霍城县| 锡林浩特市| 健康| 长白| 肃南| 光泽县| 仁怀市| 柳州市| 阳西县| 中阳县| 永吉县| 无极县| 和龙市| 花莲市|