網址:http://blog.csdn.net/edwingu/article/details/44040961
WebSocket protocol 是HTML5一種新的協議。它實現了瀏覽器與服務器全雙工通信(full-duplex)。
原理
以前網站為了實現即時通訊,所用的技術都是輪詢(polling)。輪詢是在特定的的時間間隔(如每隔1秒),由瀏覽器對服務器發出HTTP request,然后由服務器返回最新的數據給客服端的瀏覽器。這種傳統的HTTP request 的模式帶來很明顯的缺點 – 瀏覽器需要不斷的向服務器發出請求,然而HTTP request 的header是非常長的,里面包含的有用數據可能只是一個很小的值,這樣會占用很多的帶寬。
在 WebSocket API,瀏覽器和服務器只需要要做一個握手的動作,然后,瀏覽器和服務器之間就形成了一條快速通道。兩者之間就直接可以數據互相傳送。
握手協議
在實現websocket連線過程中,需要通過瀏覽器發出websocket連線請求,然后服務器發出回應,這個過程通常稱為“握手” (handshaking)。
該方案處在草案階段,目前在使用的有兩個版本,一個是以chrome為首的使用的version 13(目前最新),該版本出現在RFC6455中。另一個是以safari(包括桌面和移動版本)為首的使用的draft-ietf-hybi版。
chrome版–新版
safari版–舊版
以下分別介紹兩個版本的握手方法
Chrome版
客戶端請求web socket連接時,會向服務器端發送握手請求
客戶端請求:
var ws = new WebSocket('ws://192.168.0.10:8080');
- 1
- 2
- 1
- 2
請求內容大致如下:
GET / HTTP/1.1 Host: 192.168.0.10:8080 Connection: Upgrade Pragma: no-cache Cache-Control: no-cache Upgrade: websocket Origin: null Sec-WebSocket-Version: 13 User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36 Accept-Encoding: gzip, deflate, sdch Accept-Language: zh-CN,zh;q=0.8 Sec-WebSocket-Key: VR+OReqwhymoQ21dBtoIMQ== Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
請求包說明:
* 必須是有效的http request 格式;
* HTTP request method 必須是GET,協議應不小於1.1 如: Get / HTTP/1.1;
* 必須包括Upgrade頭域,並且其值為”websocket”;
* 必須包括”Connection” 頭域,並且其值為”Upgrade”;
* 必須包括”Sec-WebSocket-Key”頭域,其值采用base64編碼的隨機16字節長的字符序列;
* 如果請求來自瀏覽器客戶端,還必須包括Origin頭域 。 該頭域用於防止未授權的跨域腳本攻擊,服務器可以從Origin決定是否接受該WebSocket連接;
* 必須包括”Sec-webSocket-Version” 頭域,當前值必須是13;
* 可能包括”Sec-WebSocket-Protocol”,表示client(應用程序)支持的協議列表,server選擇一個或者沒有可接受的協議響應之;
* 可能包括”Sec-WebSocket-Extensions”, 協議擴展, 某類協議可能支持多個擴展,通過它可以實現協議增強;
* 可能包括任意其他域,如cookie.
服務器端響應如下:
HTTP/1.1 101 Web Socket Protocol Handshake Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: Y+Te7S7wQJC0FwXumEdGbv9/Mek=
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
應答包說明:
*必須包括Upgrade頭域,並且其值為”websocket”;
*必須包括Connection頭域,並且其值為”Upgrade”;
*必須包括Sec-WebSocket-Accept頭域,其值是將請求包“Sec-WebSocket-Key”的值,與”258EAFA5-E914-47DA-95CA-C5AB0DC85B11″這個字符串進行拼接,然后對拼接后的字符串進行sha-1運算,再進行base64編碼,就是“Sec-WebSocket-Accept”的值;
*應答包中冒號后面有一個空格;
*最后需要兩個空行作為應答包結束。
<?php //獲取請求包中Sec-WebSocket-Key的值 //$req為請求包內容 if(preg_match("/Sec-WebSocket-Key: (.*)\r\n/",$req,$match)){ $key=$match[1]; } ?>
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
<?php //計算Sec-WebSocket-Accept值算法 private function websocket_accept_key($strkey){ $strkey .= "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; $hash_data = base64_encode(sha1($strkey,true)); return $hash_data; } ?>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
<?php //組裝應答包代碼 $hash_data = $this->websocket_accept_key($strkey); $upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" . "Upgrade: websocket\r\n" . "Connection: Upgrade\r\n" . "Sec-WebSocket-Accept: " . $hash_data . "\r\n" . "\r\n"; ?>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
握手成功!
Safari版
請求內容大致如下:
GET / HTTP/1.1 Upgrade: WebSocket Connection: Upgrade Host: 192.168.0.10:8080 Origin: file:// Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5 Sec-WebSocket-Key2: 12998 5 Y3 1 .P00 ^n:ds[4U
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
請求包說明:
*必須包括”Sec-WebSocket-Key1”頭域,由可見字符組成;
*必須包括”Sec-WebSocket-Key2”頭域,由可見字符組成;
*在請求包的最后,會有一行請求正文,它不屬於任何頭域,我們可以理解為”Sec-WebSocket-Key3”,因為它需要參與到計算應答體;
*請求正文的上一行是一行空行;
*請求正文大部分情況下是非可見字符,俗稱亂碼,但這不影響后面的計算。
服務器端響應如下:
HTTP/1.1 101 WebSocket Protocol Handshake Upgrade: WebSocket Connection: Upgrade Sec-WebSocket-Origin: file:// Sec-WebSocket-Location: ws://192.168.0.10:8080/ 8jKS'y:G*Co,Wxa-
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
應答包說明:
*必須包括”Upgrade”頭域,其值為WebSocket;
*必須包括”Connection”頭域,其值為Upgrade;
*必須包括”Sec-WebSocket-Origin”頭域;
*必須包括”Sec-WebSocket-Location”頭域;
*應答正文的上一行是一行空行;
*應答正文后面沒有任何結束符或者換行符。
<?php //獲取請求包中Sec-WebSocket-Key1和Sec-WebSocket-Key2值 //$req為請求包內容 if(preg_match("/Sec-WebSocket-Key1: (.*)\r\n/",$req,$match)){ $key1=$match[1]; } if(preg_match("/Sec-WebSocket-Key2: (.*)\r\n/",$req,$match)){ $key2=$match[1]; } ?>
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
<?php //獲取請求正文內容 $arr = explode("\r\n", $req); $body = end($arr); ?>
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
<?php /* 計算應答正文內容 1.把Sec-WebSocket-Key1中的數字從左到右提取出來,上面的例子是:4146546015; 2.把Sec-WebSocket-Key2中的數字從左到右提取出來,上面的例子是:1299853100; 3.計算Sec-WebSocket-Key1中的空格數,上面的例子是:5(冒號后面的空格不算); 4.計算Sec-WebSocket-Key2中的空格數,上面的例子是:5(冒號后面的空格不算); 5.將提取出來的數字除以空格數,去整,分別得到829309203 和 259970620; 6.將上一步計算得到的數字分別以Big-Endian的方式打包,拼接,然后再與請求正文的8個字節拼接,計算其MD5值。 */ public function websocket_accept_key_76($key1, $key2, $body){ $tmp = array(); $kv1 = 0; $kv2 = 0; $c1 = 0; $c2 = 0; $key1Len = strlen($key1); $key2Len = strlen($key2); for($i = 0; $i < $key1Len; $i++){ if($key1[$i]>='0' && $key1[$i]<='9') $kv1 = $kv1*10+($key1[$i]-'0'); else if($key1[$i]==' ') $c1++; } for($i = 0; $i < $key2Len; $i++){ if($key2[$i]>='0' && $key2[$i]<='9') $kv2 = $kv2*10+($key2[$i]-'0'); else if($key2[$i]==' ') $c2++; } $kv1 = $kv1/$c1; $kv2 = $kv2/$c2; $key1_sec = pack("N",$kv1); $key2_sec = pack("N",$kv2); $hash_data = md5($key1_sec.$key2_sec.$body,true); return $hash_data; } ?>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
獲得的這個就是應答體正文,通常是非可見字符–“亂碼”。
然后就是返回應答包給瀏覽器了
<?php //組裝應答包代碼 $hash_data = $this->websocket_accept_key_76($strkey); $upgrade = ""HTTP/1.1 101 WebSocket Protocol Handshake\r\n" . "Upgrade: WebSocket\r\n" . "Connection: Upgrade\r\n" . "Sec-WebSocket-Origin: $origin\r\n". "Sec-WebSocket-Location: ws://$host/\r\n". "\r\n" . $hash_data; ?>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
握手成功!
