websocket中发生数据丢失_tcp协议;websocket协议;同源策略和跨域
tcp協(xié)議
為什么連接的時候是三次握手,關(guān)閉的時候卻是四次握手?
答:因?yàn)楫?dāng)Server端收到Client端的SYN連接請求報文后,可以直接發(fā)送SYN+ACK報文。其中ACK報文是用來應(yīng)答的,SYN報文是用來同步的。但是關(guān)閉連接時,當(dāng)Server端收到FIN報文時,很可能并不會立即關(guān)閉SOCKET,所以只能先回復(fù)一個ACK報文,告訴Client端,"你發(fā)的FIN報文我收到了"。只有等到我Server端所有的報文都發(fā)送完了,我才能發(fā)送FIN報文,因此不能一起發(fā)送。故需要四步握手。
為什么TIME_WAIT狀態(tài)需要經(jīng)過2MSL(最大報文段生存時間)才能返回到CLOSE狀態(tài)?
答:雖然按道理,四個報文都發(fā)送完畢,我們可以直接進(jìn)入CLOSE狀態(tài)了,但是我們必須假象網(wǎng)絡(luò)是不可靠的,有可以最后一個ACK丟失。所以TIME_WAIT狀態(tài)就是用來重發(fā)可能丟失的ACK報文。在Client發(fā)送出最后的ACK回復(fù),但該ACK可能丟失。Server如果沒有收到ACK,將不斷重復(fù)發(fā)送FIN片段。所以Client不能立即關(guān)閉,它必須確認(rèn)Server接收到了該ACK。Client會在發(fā)送出ACK之后進(jìn)入到TIME_WAIT狀態(tài)。Client會設(shè)置一個計(jì)時器,等待2MSL的時間。如果在該時間內(nèi)再次收到FIN,那么Client會重發(fā)ACK并再次等待2MSL。所謂的2MSL是兩倍的MSL(Maximum Segment Lifetime)。MSL指一個片段在網(wǎng)絡(luò)中最大的存活時間,2MSL就是一個發(fā)送和一個回復(fù)所需的最大時間。如果直到2MSL,Client都沒有再次收到FIN,那么Client推斷ACK已經(jīng)被成功接收,則結(jié)束TCP連接。
為什么不能用兩次握手進(jìn)行連接?
答:3次握手完成兩個重要的功能,既要雙方做好發(fā)送數(shù)據(jù)的準(zhǔn)備工作(雙方都知道彼此已準(zhǔn)備好),也要允許雙方就初始序列號進(jìn)行協(xié)商,這個序列號在握手過程中被發(fā)送和確認(rèn)。
現(xiàn)在把三次握手改成僅需要兩次握手,死鎖是可能發(fā)生的。作為例子,考慮計(jì)算機(jī)S和C之間的通信,假定C給S發(fā)送一個連接請求分組,S收到了這個分組,并發(fā) 送了確認(rèn)應(yīng)答分組。按照兩次握手的協(xié)定,S認(rèn)為連接已經(jīng)成功地建立了,可以開始發(fā)送數(shù)據(jù)分組。可是,C在S的應(yīng)答分組在傳輸中被丟失的情況下,將不知道S 是否已準(zhǔn)備好,不知道S建立什么樣的序列號,C甚至懷疑S是否收到自己的連接請求分組。在這種情況下,C認(rèn)為連接還未建立成功,將忽略S發(fā)來的任何數(shù)據(jù)分 組,只等待連接確認(rèn)應(yīng)答分組。而S在發(fā)出的分組超時后,重復(fù)發(fā)送同樣的分組。這樣就形成了死鎖。
如果已經(jīng)建立了連接,但是客戶端突然出現(xiàn)故障了怎么辦?
TCP還設(shè)有一個保活計(jì)時器,顯然,客戶端如果出現(xiàn)故障,服務(wù)器不能一直等下去,白白浪費(fèi)資源。服務(wù)器每收到一次客戶端的請求后都會重新復(fù)位這個計(jì)時器,時間通常是設(shè)置為2小時,若兩小時還沒有收到客戶端的任何數(shù)據(jù),服務(wù)器就會發(fā)送一個探測報文段,以后每隔75秒鐘發(fā)送一次。若一連發(fā)送10個探測報文仍然沒反應(yīng),服務(wù)器就認(rèn)為客戶端出了故障,接著就關(guān)閉連接。
websocket協(xié)議
Websocket協(xié)議
Websocket是html5提出的一個協(xié)議規(guī)范,參考rfc6455。
websocket約定了一個通信的規(guī)范,通過一個握手的機(jī)制,客戶端(瀏覽器)和服務(wù)器(webserver)之間能建立一個類似tcp的連接,從而方便C-S之間的通信。在websocket出現(xiàn)之前,web交互一般是基于http協(xié)議的短連接或者長連接。
WebSocket是為解決客戶端與服務(wù)端實(shí)時通信而產(chǎn)生的技術(shù)。websocket協(xié)議本質(zhì)上是一個基于tcp的協(xié)議,是先通過HTTP/HTTPS協(xié)議發(fā)起一條特殊的http請求進(jìn)行握手后創(chuàng)建一個用于交換數(shù)據(jù)的TCP連接,此后服務(wù)端與客戶端通過此TCP連接進(jìn)行實(shí)時通信。
Websocket和HTTP協(xié)議的關(guān)系
同樣作為應(yīng)用層的協(xié)議,WebSocket在現(xiàn)代的軟件開發(fā)中被越來越多的實(shí)踐,和HTTP有很多相似的地方,這里將它們簡單比較:
相同點(diǎn)
都是基于TCP的應(yīng)用層協(xié)議。
都使用Request/Response模型進(jìn)行連接的建立。
在連接的建立過程中對錯誤的處理方式相同,在這個階段WS可能返回和HTTP相同的返回碼。
都可以在網(wǎng)絡(luò)中傳輸數(shù)據(jù)。
不同點(diǎn)
WS使用HTTP來建立連接,但是定義了一系列新的header域,這些域在HTTP中并不會使用。
WS的連接不能通過中間人來轉(zhuǎn)發(fā),它必須是一個直接連接。
WS連接建立之后,通信雙方都可以在任何時刻向另一方發(fā)送數(shù)據(jù)。
WS連接建立之后,數(shù)據(jù)的傳輸使用幀來傳遞,不再需要Request消息。
WS的數(shù)據(jù)幀有序。
協(xié)議
1. 握手
客戶端發(fā)起握手。
GET /chat HTTP/1.1Host: server.example.comUpgrade: websocketConnection: UpgradeSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==Origin: http://example.com Sec-WebSocket-Protocol: chat, superchatSec-WebSocket-Version: 13從上面的協(xié)議中可以看到websocket基于HTTP協(xié)議的GET。而且只支持GET.
Upgrade:upgrade是HTTP1.1中用于定義轉(zhuǎn)換協(xié)議的header域。它表示,如果服務(wù)器支持的話,客戶端希望使用現(xiàn)有的「網(wǎng)絡(luò)層」已經(jīng)建立好的這個「連接(此處是TCP連接)」,切換到另外一個「應(yīng)用層」(此處是WebSocket)協(xié)議。
Connection:HTTP1.1中規(guī)定Upgrade只能應(yīng)用在「直接連接」中,所以帶有Upgrade頭的HTTP1.1消息必須含有Connection頭,因?yàn)镃onnection頭的意義就是,任何接收到此消息的人(往往是代理服務(wù)器)都要在轉(zhuǎn)發(fā)此消息之前處理掉Connection中指定的域(不轉(zhuǎn)發(fā)Upgrade域)。
Sec-WebSocket-*:第7行標(biāo)識了客戶端支持的子協(xié)議的列表(關(guān)于子協(xié)議會在下面介紹),第8行標(biāo)識了客戶端支持的WS協(xié)議的版本列表,第5行用來發(fā)送給服務(wù)器使用(服務(wù)器會使用此字段組裝成另一個key值放在握手返回信息里發(fā)送客戶端)。
服務(wù)端響應(yīng)
HTTP/1.1 101 Switching ProtocolsUpgrade: websocketSec-Websocket-Accept: ZEs+c+VBk8Aj01+wJGN7Y15796g=Connection: UpgradeSec-WebSocket-Protocol: chat, superchatSec-Websocket-Accept 是一個校驗(yàn)。用客戶端發(fā)來的sec_key 服務(wù)器通過sha1計(jì)算拼接商GUID【258EAFA5-E914-47DA-95CA-C5AB0DC85B11 】 。然后再base64encode
數(shù)據(jù)傳輸
客戶端和服務(wù)器連接成功后,就可以進(jìn)行通信了,通信協(xié)議格式是WebSocket格式,服務(wù)器端采用Tcp Socket方式接收數(shù)據(jù),進(jìn)行解析,協(xié)議格式如下:
這里使用的是數(shù)據(jù)存儲的位(bit),當(dāng)進(jìn)行加密的時候,最終要的一位就是最左邊的第一個。
FIN :1bit ,表示是消息的最后一幀,如果消息只有一幀那么第一幀也就是最后一幀。
RSV1,RSV2,RSV3:每個1bit,必須是0,除非擴(kuò)展定義為非零。如果接受到的是非零值但是擴(kuò)展沒有定義,則需要關(guān)閉連接。
Opcode:4bit,解釋Payload數(shù)據(jù),規(guī)定有以下不同的狀態(tài),如果是未知的,接收方必須馬上關(guān)閉連接。狀態(tài)如下:0x0(附加數(shù)據(jù)幀) 0x1(文本數(shù)據(jù)幀) 0x2(二進(jìn)制數(shù)據(jù)幀) 0x3-7(保留為之后非控制幀使用) 0xB-F(保留為后面的控制幀使用) 0x8(關(guān)閉連接幀) 0x9(ping) 0xA(pong)
Mask:1bit,掩碼,定義payload數(shù)據(jù)是否進(jìn)行了掩碼處理,如果是1表示進(jìn)行了掩碼處理。
Masking-key域的數(shù)據(jù)即是掩碼密鑰,用于解碼PayloadData。客戶端發(fā)出的數(shù)據(jù)幀需要進(jìn)行掩碼處理,所以此位是1。Payload length:7位,7 + 16位,7+64位,payload數(shù)據(jù)的長度,如果是0-125,就是真實(shí)的payload長度,如果是126,那么接著后面的2個字節(jié)對應(yīng)的16位無符號整數(shù)就是payload數(shù)據(jù)長度;如果是127,那么接著后面的8個字節(jié)對應(yīng)的64位無符號整數(shù)就是payload數(shù)據(jù)的長度。
Masking-key:0到4字節(jié),如果MASK位設(shè)為1則有4個字節(jié)的掩碼解密密鑰,否則就沒有。
Payload data:任意長度數(shù)據(jù)。包含有擴(kuò)展定義數(shù)據(jù)和應(yīng)用數(shù)據(jù),如果沒有定義擴(kuò)展則沒有此項(xiàng),僅含有應(yīng)用數(shù)據(jù)。
服務(wù)端簡單實(shí)現(xiàn)
// 封裝ws 協(xié)議的數(shù)據(jù)包function build($msg) { ? $frame = []; ? $frame[0] = '81'; // 81 就是 10000001 第一位1表示最后一個數(shù)據(jù)段,最后一位1表示這是文本數(shù)據(jù) ? $len = strlen($msg); ? if ($len < 126) { ? ? //7位長度 第一個是掩碼 默認(rèn)是0 ? ? //小于126的時候 也是 01111110 數(shù)據(jù)包第二個字節(jié)表示長度 ? ? ? $frame[1] = $len < 16 ? '0' . dechex($len) : dechex($len); ? } else if ($len < 65025) { ? ? //7位 + 16位 01111110 00000000 00000000 ? ? ? $s = dechex($len); ? ? ? $frame[1] = '7e' . str_repeat('0', 4 - strlen($s)) . $s; ? } else { ? ? //7位 + 64位 01111111 00000000 00000000 ? ? ? $s = dechex($len); ? ? ? $frame[1] = '7f' . str_repeat('0', 16 - strlen($s)) . $s; ? } ? $data = ''; ? $l = strlen($msg); ? for ($i = 0; $i < $l; $i++) { ? ? ? $data .= dechex(ord($msg{$i})); ? } ? //最后是數(shù)據(jù)內(nèi)容 ? $frame[2] = $data; ? $data = implode('', $frame); ? return pack("H*", $data);}//拆包function parse($buffer) { ? ? ? $decoded = ''; ? ? ? $len = ord($buffer[1]) & 127; ? ? ? if ($len === 126) { ? ? ? ? ? $masks = substr($buffer, 4, 4); ? ? ? ? ? $data = substr($buffer, 8); ? ? ? } else if ($len === 127) { ? ? ? ? ? $masks = substr($buffer, 10, 4); ? ? ? ? ? $data = substr($buffer, 14); ? ? ? } else { ? ? ? ? ? $masks = substr($buffer, 2, 4); ? ? ? ? ? $data = substr($buffer, 6); ? ? ? } ? ? ? for ($index = 0; $index < strlen($data); $index++) { ? ? ? ? ? $decoded .= $data[$index] ^ $masks[$index % 4]; ? ? ? } ? ? ? return $decoded; ? }$socket = stream_socket_server("tcp://0.0.0.0:8888", $errno, $errstr);if (!$socket) { ? echo "$errstr ($errno)\n"; ? die;}while (1) { ? $conn = stream_socket_accept($socket); ? $data = stream_get_contents($conn,500); ? $data = explode("\r\n",$data); ? $secKey = ""; ? foreach ($data as $key=>$val) { ? ? ? if (strpos($val,"WebSocket-Key:") >= 1 ) { ? ? ? ? ? $key = explode(":",$val ); ? ? ? ? ? $secKey = $key[1]; ? ? ? } ? } ? //固定key 算法 base64(sha1(key+258EAFA5-E914-47DA-95CA-C5AB0DC85B11)) ? $hashkey = ? base64_encode(sha1(trim($secKey)."258EAFA5-E914-47DA-95CA-C5AB0DC85B11",true)); ? $ws = "HTTP/1.1 101 Switching Protocols\r\n"; ? $ws .= "Upgrade: websocket\r\n"; ? $ws .= "Connection: Upgrade\r\n"; ? $ws .= "Sec-WebSocket-Version:13\r\n"; ? $ws .= "Sec-WebSocket-Accept: $hashkey\r\n"; ? $ws .= "Sec-WebSocket-Protocol: chat\r\n"; ? $ws .= "\r\n"; ? if (!$conn) { ? ? ? continue; ? } ? fwrite($conn, $ws); ? fwrite($conn,build("hello")); ? fclose($conn); ? break;}fclose($socket);
客戶端
var websocket = new WebSocket("ws://127.0.0.1:8888","chat")websocket.onopen = function(){}websocket.onmessage = function(){}websocket.onclose = function(){}同源策略和跨域
同源策略
| http://www.a.com/a.jshttp://www.a.com/b.js | 同一域名下 | 允許 |
| http://www.a.com/lab/a.jshttp://www.a.com/script/b.js | 同一域名下不同文件夾 | 允許 |
| http://www.a.com:8000/a.jshttp://www.a.com/b.js | 同一域名,不同端口 | 不允許 |
| http://www.a.com/a.jshttps://www.a.com/b.js | 同一域名,不同協(xié)議 | 不允許 |
| http://www.a.com/a.jshttp://70.32.92.74/b.js | 域名和域名對應(yīng)ip | 不允許 |
| http://www.a.com/a.jshttp://script.a.com/b.js | 主域相同,子域不同 | 不允許 |
| http://www.a.com/a.jshttp://a.com/b.js | 同一域名,不同二級域名(同上) | 不允許(cookie這種情況下也不允許訪問) |
| http://www.cnblogs.com/a.jshttp://www.a.com/b.js | 不同域名 | 不允許 |
特別注意兩點(diǎn):
第一,如果是協(xié)議和端口造成的跨域問題“前臺”是無能為力的,
第二:在跨域問題上,域僅僅是通過“URL的首部”來識別而不會去嘗試判斷相同的ip地址對應(yīng)著兩個域或兩個域是否在同一個ip上。
“URL的首部”指window.location.protocol +window.location.host,也可以理解為“Domains, protocols and ports must match”。
比較常見的解決方案
方式一:使用ajax的jsonp
方式二:使用jQuery的jsonp插件
方式三:使用cors
方式四:配置nginx
總結(jié)
以上是生活随笔為你收集整理的websocket中发生数据丢失_tcp协议;websocket协议;同源策略和跨域的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 怎么切换用户_走进通信:4G手机跟基站是
- 下一篇: #中regex的命名空间_Python命