自十月底,html5 宣布定稿之后,新一輪的關(guān)于html的討論便開始了,現(xiàn)在這里,我也為大家介紹一種html5標(biāo)準(zhǔn)中提到的新技術(shù) websocket,以及他的 php 實(shí)現(xiàn)范例。
WebSocket是HTML5開始提供的一種瀏覽器與服務(wù)器間進(jìn)行全雙工通訊的網(wǎng)絡(luò)技術(shù)。WebSocket通信協(xié)議于2011年被IETF定為標(biāo)準(zhǔn)RFC 6455,WebSocketAPI被W3C定為標(biāo)準(zhǔn)。
在WebSocket API中,瀏覽器和服務(wù)器只需要做一個(gè)握手的動(dòng)作,然后,瀏覽器和服務(wù)器之間就形成了一條快速通道。兩者之間就直接可以數(shù)據(jù)互相傳送。
—————- form wiki
傳統(tǒng)的web程序,都遵循這樣的執(zhí)行方式,瀏覽器發(fā)送一個(gè)請(qǐng)求,然后服務(wù)器端接收這個(gè)請(qǐng)求,處理完成瀏覽器的請(qǐng)求之后,再將處理完成的結(jié)果返回給瀏覽器,然后瀏覽器處理返回的html,將其呈現(xiàn)給用戶。如下圖所示:
![server]()
即使后來出現(xiàn)了ajax這樣的新的技術(shù),它實(shí)際也是使用javascript的api來向服務(wù)器發(fā)送請(qǐng)求,然后等待相應(yīng)的數(shù)據(jù)。也就是說,在瀏覽器和服務(wù)器的交互中,我們每想得到一個(gè)得到新的數(shù)據(jù)(更新頁面,獲得服務(wù)器端的最新狀態(tài))就必須要發(fā)起一個(gè)http請(qǐng)求,建立一條TCP/IP鏈接,當(dāng)這次請(qǐng)求的數(shù)據(jù)被瀏覽器接收到之后,就斷開這條TCP/IP連接。
新的websocket技術(shù),在瀏覽器端發(fā)起一條請(qǐng)求之后,服務(wù)器端與瀏覽器端的請(qǐng)求進(jìn)行握手應(yīng)答之后,就建立起一條長久的,雙工的長連接,基于這條連接,我們不必做一些輪詢就能隨時(shí)獲得服務(wù)器端的狀態(tài),服務(wù)器也不必等待瀏覽器端的請(qǐng)求才能向用戶推送數(shù)據(jù),可以在這條連接上隨時(shí)向以建立websocket連接的用戶 push 數(shù)據(jù)。
這里是 websocket 協(xié)議的 RFC 文檔。
我這里的實(shí)現(xiàn)是基于 php sockets的實(shí)現(xiàn),php socket api.
005 | public $activatedSocketArray; |
006 | public function __construct($address,$port){ |
007 | $this->socket = $this->init($address,$port); |
008 | if($this->socket == null) |
012 | private function init($address,$port){ |
013 | $wssocket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); |
014 | socket_set_option($wssocket, SOL_SOCKET, SO_REUSEADDR, 1); |
015 | socket_bind($wssocket,$address,$port); |
016 | if(socket_listen($wssocket)){ |
017 | $this->p("Socket init success"); |
020 | $this->p("Socket init failure"); |
026 | public function run(){ |
027 | $this->socketArray[] = $this->socket; |
031 | $this->activatedSocketArray = $this->socketArray; |
032 | socket_select($this->activatedSocketArray, $write, $except, null); |
034 | foreach($this->activatedSocketArray as $s){ |
035 | if($s == $this->socket){ |
036 | $client = socket_accept($this->socket); |
037 | socket_recv($client, $buffer, 2048, 0); |
040 | if(socket_write($client, $this->handshakeResponse($buffer))!== false){ |
041 | $this->p('add a socket into the queue'); |
042 | array_push($this->socketArray, $client); |
044 | $this->p('error on handshake'); |
045 | $this->errorLog(socket_last_error()); |
048 | socket_recv($s, $buffer, 2048, 0); |
050 | $frame = $this->parseFrame($buffer); |
052 | $str = $this->decode($buffer); |
053 | $str = $this->frame($str); |
054 | socket_write($s,$str,strlen($str)); |
063 | private function parseFrame($d){ |
064 | $firstByte = ord(substr($d,0,1)); |
066 | $result['FIN'] = $this->getBit($firstByte,1); |
067 | $result['opcode'] = $firstByte & 15; |
068 | $result['length'] = ord(substr($d,1,1)) & 127; |
071 | private function getBit($m,$n){ |
072 | return ($n >> ($m-1)) & 1; |
079 | $a = str_split($s, 125); |
081 | return "\x81" . chr(strlen($a[0])) . $a[0]; |
085 | $ns .= "\x81" . chr(strlen($o)) . $o; |
093 | function decode($buffer) { |
094 | $len = $masks = $data = $decoded = null; |
095 | $len = ord($buffer[1]) & 127; |
098 | $masks = substr($buffer, 4, 4); |
099 | $data = substr($buffer, 8); |
101 | else if ($len === 127) { |
102 | $masks = substr($buffer, 10, 4); |
103 | $data = substr($buffer, 14); |
106 | $masks = substr($buffer, 2, 4); |
107 | $data = substr($buffer, 6); |
110 | for ($index = 0; $index < strlen($data); $index++) { |
111 | $decoded .= $data[$index] ^ $masks[$index % 4]; |
120 | function parseHeaders($requsetHeaders){ |
122 | if (preg_match("/GET (.*) HTTP/" ,$requsetHeaders,$match)) { $resule['reuqest'] = $match[1]; } |
123 | if (preg_match("/Host: (.*)\r\n/" ,$requsetHeaders,$match)) { $result['host'] = $match[1]; } |
124 | if (preg_match("/Origin: (.*)\r\n/" ,$requsetHeaders,$match)) { $result['origin'] = $match[1]; } |
125 | if (preg_match("/Sec-WebSocket-Key: (.*)\r\n/",$requsetHeaders,$match)) { $result['key'] = $match[1]; } |
134 | function getKey($requestKey){ |
135 | return base64_encode(sha1($requestKey . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true)); |
141 | function handshakeResponse($request){ |
142 | $parsedRequest = $this->parseHeaders($request); |
143 | $encryptedKey = $this->getKey($parsedRequest['key']); |
144 | $response = "HTTP/1.1 101 Switching Protocol\r\n" . |
145 | "Upgrade: websocket\r\n" . |
146 | "Connection: Upgrade\r\n" . |
147 | "Sec-WebSocket-Accept: " .$encryptedKey. "\r\n\r\n"; |
155 | function errorLog($ms){ |
162 | private function p($e){ |
166 | $read[] = $this->socket; |
169 | socket_select($read, $write, $except,null); |
173 | $w = new WsServer('localhost',4000); |
這篇文章中對(duì)這里的代碼的有些地方做了些相關(guān)的解釋
這個(gè)類主要實(shí)現(xiàn)了對(duì)websocket的握手回應(yīng),將每一個(gè)連接成功的websocket加入到一個(gè)數(shù)組中之后,就能夠在服務(wù)器端對(duì)多個(gè)websocket 客戶端進(jìn)行處理。
對(duì)websocket的握手請(qǐng)求,在接收到的報(bào)文中 會(huì)得到一個(gè) Sec-WebSocket-Key 字段,要把“Sec-WebSocket-Key”加上一個(gè)魔幻字符串“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”。使用SHA-1加密,之后進(jìn)行BASE-64編碼,將結(jié)果做為“Sec-WebSocket-Accept”頭的值,返回給客戶端。這樣就完成了與客戶端之間的握手。
之后,就能在服務(wù)器端監(jiān)聽客戶端發(fā)來的請(qǐng)求了,同時(shí)也可以操作在服務(wù)端的socket句柄,來向?yàn)g覽器推送消息。