摘要:服務端的開發離不開協議,的出現對于學習通信來說,無疑是非常好的教材。一旦握手成功,一個雙向連接通道就建立了。類型包括文本,二進制,協議層信號等。目前一共有種類型,種保留類型。表示異或,表示取模。
websocket協議學習 概述服務端的開發離不開協議,swoole的出現對于學習通信來說,無疑是非常好的教材。非常推薦大家下載 Swoole Framework,其中包含了多種協議的php實現,例如FTP,HTTP,Websocket等。本文大部分代碼都是受這個項目的啟發,當然學習的同時別忘了star一下這個項目。筆者本身計算機基礎較弱,寫這篇文章的同時也查了不少資料,如果有錯誤歡迎提出批評。
協議分為兩部分:握手,數據傳輸
客戶端發出的握手信息類似:
GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Origin: http://example.com Sec-WebSocket-Protocol: chat, superchat Sec-WebSocket-Version: 13
服務器返回的握手信息類似:
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= Sec-WebSocket-Protocol: chat Sec-WebSocket-Version: 13
兩段信息的第一行大家應該都比較熟悉,是HTTP協議中的Request-Line和Status-Line,RFC2616。下面接著出現的是無序的頭信息,這和HTTP協議相同。 一旦握手成功,一個雙向連接通道就建立了。
連接用于傳輸message, message由一個或多個frame組成。每個frame有一個類型,屬于同一個message的frame的類型都相同。類型包括:文本,二進制,control frame(協議層信號)等。目前一共有6種類型,10種保留類型。
根據上面的客戶端頭信息可以看出,握手和HTTP是兼容的。WS的握手是HTTP的"升級版本"。
客戶端發送的握手請求必須
1. 是一個合法的HTTP請求
2. 方法是GET
3. 頭必須包含HOST字段
4. 頭必須包含Upgrade字段,值為websocket,可以看作是判斷請求為ws的標志。
5. 頭必須包含Connection字段,值為Upgrade。
6. 頭必須包含Sec-WebSocket-Key字段,用于驗證。
7. 如果請求來自瀏覽器,頭必須包含 Origin字段。
8. 頭必須包含Sec-WebSocket-Version字段,值為13
取Sec-WebSocket-Key字段的值,連接一個GUID字符串,"258EAFA5-E914-47DA-95CA-C5AB0DC85B11", sha1 hash一下,再base64_encode,得到的值作為字段Sec-WebSocket-Accept的值返回給客戶端。用php代碼表示:
"Sec-WebSocket-Accept" => base64_encode(sha1($key . static::GUID, true))
同時,返回的狀態設置為101,其他狀態都表示握手沒有成功。 Connection,Upgrade字段作為HTTP升級版必須存在。一個握手返回如下:
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=Frame(幀)的結構如下:
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-------+-+-------------+-------------------------------+ |F|R|R|R| opcode|M| Payload len | Extended payload length | |I|S|S|S| (4) |A| (7) | (16/64) | |N|V|V|V| |S| | (if payload len==126/127) | | |1|2|3| |K| | | +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + | Extended payload length continued, if payload len == 127 | + - - - - - - - - - - - - - - - +-------------------------------+ | |Masking-key, if MASK set to 1 | +-------------------------------+-------------------------------+ | Masking-key (continued) | Payload Data | +-------------------------------- - - - - - - - - - - - - - - - + : Payload Data continued ... : + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Payload Data continued ... | +---------------------------------------------------------------+
FIN: 1 bit, 標記是否是最后一個message的最后一個片段
RSV1, RSV2, RSV3: 各 1 bit, 保留標記,都為0
Opcode:4 bits, 是對payload data的說明,指明這個幀的類型。
0x0 表明為接著上一幀的連續幀
0x1 表明為text frame
0x2 表明為binary frame
0x3-7 保留
0x8 表示連接關閉
0x9 為ping
0xA 為pong
0xB-F 保留
- Mask: 1 bit, 指明Payload data是否被mask,如果為1,那么數據需要根據masking-key來unmask。客戶端發送的幀都是mask的。
- Payload length: 7 bits 或 7+16 bits 或 7+64 bits. 如果值為0-125,那么該值就是payload的長度;如果為126,那么接下來的2個byte表示payload長度(16bit, unsigned); 如果為127,那么接下來的8個bytes表示payload的長度(64bit, unsigned)。
- Masking-key: 0 或 4 bytes, 用于unmask payload data。
- Payload data: 長度為 Payload length, 可以分為 extension data + application data, 擴展數據的長度計算方法是是事先商議好的,剩余的就是應用數據。
masking-key是客戶端隨意指定的32bit長度值。從原始數據到masked數據的方式為:原始數據第i個字節的值 XOR masking-key的第(i%4)個字節的值。XOR表示異或,%表示取模。
片段化的作用當傳遞一個未知長度的數據時,可以不用一下子buffer全部的數據。尤其當數據非常大時,可以分多次buffer,包裝為frame來發送。
嘗試解析一個frame看到這里,我們已經了解了frame的結構,是否想嘗試解析一個frame,官方文檔提供了幾段二進制數據,我們可以用來練習一下。我挑選了其中兩段, 代碼如下:
php> 7) & 0x1; $RSV1 = ($temp >> 6) & 0x1; $RSV2 = ($temp >> 5) & 0x1; $RSV3 = ($temp >> 4) & 0x1; $opcode = $temp & 0xf; echo "First byte: FIN is $FIN, RSV1-3 are $RSV1, $RSV2, $RSV3; Opcode is $opcode "; $temp = ord($data[$offset++]); $mask = ($temp >> 7) & 0x1; $payload_length = $temp & 0x7f; if($payload_length == 126){ $temp = substr($data, $offset, 2); $offset += 2; $temp = unpack("nl", $temp); $payload_length = $temp["l"]; }elseif($payload_length == 127){ $temp = substr($data, $offset, 8); $offset += 8; $temp = unpack("nl", $temp); $payload_length = $temp["l"]; } echo "mask is $mask, payload_length is $payload_length "; if($mask ==0){ $temp = substr($data, $offset); $content = ""; for ($i=0; $i < $payload_length; $i++) { $content .= $temp[$i]; } }else{ $masking_key = substr($data, $offset, 4); $offset += 4; $temp = substr($data, $offset); $content = ""; for ($i=0; $i < $payload_length; $i++) { $content .= chr(ord($temp[$i]) ^ ord($masking_key[$i%4])); } } echo "content is $content "; }
結果輸出如下圖:
到這里其實并不算完,ws協議還有很多很多規則,RFC文檔實在是太長了。比如,如何應對每一種control frame,有詳細的說明;如何關閉連接;協議擴展;錯誤處理;安全相關;一些基本的內容都能在swoole framework中找到對應的代碼。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/21069.html
摘要:是一個基于擴展實現的輕量級高性能的常駐內存型的和應用服務框架高度封裝了,,服務器,以及基于實現可擴展的服務,同時支持包方式安裝部署項目。基于實用,抽象事件處理類,實現與底層的回調的解耦,支持同步異步調用,內置等常用組件等。 swoolefy swoolefy是一個基于swoole擴展實現的輕量級高性能的常駐內存型的API和Web應用服務框架,高度封裝了http,websocket,ud...
摘要:那么,是否就無法用來開發雙向通信的應用呢答案是否定的。內置通信支持,可以與程序基于進行雙向通信。通信協議于年被定為標準,并由補充規范。前言 眾所周知,PHP用于開發基于HTTP協議的網站應用非常便捷。而HTTP協議是一種單向的通信協議,只能接收客戶端的請求,然后響應請求,不能主動向客戶端推送信息。因此,一些實時性要求比較高的應用,如實時聊天、直播應用、在線網頁游戲等,就不適合采用HTTP協議...
摘要:那么,是否就無法用來開發雙向通信的應用呢答案是否定的。內置通信支持,可以與程序基于進行雙向通信。通信協議于年被定為標準,并由補充規范。前言 眾所周知,PHP用于開發基于HTTP協議的網站應用非常便捷。而HTTP協議是一種單向的通信協議,只能接收客戶端的請求,然后響應請求,不能主動向客戶端推送信息。因此,一些實時性要求比較高的應用,如實時聊天、直播應用、在線網頁游戲等,就不適合采用HTTP協議...
摘要:源碼解讀系列一好難都跑不起來怎么破了解一下唄閱讀框架源碼第一步搞定環境小伙伴剛接觸的時候會感覺壓力有點大更直觀的說法是難開發組是不贊成難這個說法的的代碼都是實現的而又是世界上最好的語言的代碼閱讀起來是很輕松的開發組會用源碼解讀系列博客深 date: 2018-8-01 14:22:17title: swoft| 源碼解讀系列一: 好難! swoft demo 都跑不起來怎么破? doc...
摘要:源碼解讀系列一好難都跑不起來怎么破了解一下唄閱讀框架源碼第一步搞定環境小伙伴剛接觸的時候會感覺壓力有點大更直觀的說法是難開發組是不贊成難這個說法的的代碼都是實現的而又是世界上最好的語言的代碼閱讀起來是很輕松的開發組會用源碼解讀系列博客深 date: 2018-8-01 14:22:17title: swoft| 源碼解讀系列一: 好難! swoft demo 都跑不起來怎么破? doc...
閱讀 1038·2021-11-15 18:11
閱讀 3162·2021-09-22 15:33
閱讀 3458·2021-09-01 11:42
閱讀 2654·2021-08-24 10:03
閱讀 3615·2021-07-29 13:50
閱讀 2925·2019-08-30 14:08
閱讀 1274·2019-08-28 17:56
閱讀 2259·2019-08-26 13:57