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