自十月底,html5 宣布定稿之后,新一輪的關于html的討論便開始了,現在這里,我也為大家介紹一種html5標準中提到的新技術 websocket,以及他的 php 實現范例。
WebSocket是HTML5開始提供的一種瀏覽器與服務器間進行全雙工通訊的網絡技術。WebSocket通信協議于2011年被IETF定為標準RFC 6455,WebSocketAPI被W3C定為標準。
在WebSocket API中,瀏覽器和服務器只需要做一個握手的動作,然后,瀏覽器和服務器之間就形成了一條快速通道。兩者之間就直接可以數據互相傳送。
—————- form wiki
傳統的web程序,都遵循這樣的執行方式,瀏覽器發送一個請求,然后服務器端接收這個請求,處理完成瀏覽器的請求之后,再將處理完成的結果返回給瀏覽器,然后瀏覽器處理返回的html,將其呈現給用戶。如下圖所示:
即使后來出現了ajax這樣的新的技術,它實際也是使用javascript的api來向服務器發送請求,然后等待相應的數據。也就是說,在瀏覽器和服務器的交互中,我們每想得到一個得到新的數據(更新頁面,獲得服務器端的最新狀態)就必須要發起一個http請求,建立一條TCP/IP鏈接,當這次請求的數據被瀏覽器接收到之后,就斷開這條TCP/IP連接。
新的websocket技術,在瀏覽器端發起一條請求之后,服務器端與瀏覽器端的請求進行握手應答之后,就建立起一條長久的,雙工的長連接,基于這條連接,我們不必做一些輪詢就能隨時獲得服務器端的狀態,服務器也不必等待瀏覽器端的請求才能向用戶推送數據,可以在這條連接上隨時向以建立websocket連接的用戶 push 數據。
這里是 websocket 協議的 RFC 文檔。
我這里的實現是基于 php sockets的實現,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); |
這篇文章中對這里的代碼的有些地方做了些相關的解釋
這個類主要實現了對websocket的握手回應,將每一個連接成功的websocket加入到一個數組中之后,就能夠在服務器端對多個websocket 客戶端進行處理。
對websocket的握手請求,在接收到的報文中 會得到一個 Sec-WebSocket-Key 字段,要把“Sec-WebSocket-Key”加上一個魔幻字符串“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”。使用SHA-1加密,之后進行BASE-64編碼,將結果做為“Sec-WebSocket-Accept”頭的值,返回給客戶端。這樣就完成了與客戶端之間的握手。
之后,就能在服務器端監聽客戶端發來的請求了,同時也可以操作在服務端的socket句柄,來向瀏覽器推送消息。