欢迎访问 生活随笔!

生活随笔

当前位置: 首页 > 编程资源 > 编程问答 >内容正文

编程问答

websocket中发生数据丢失_tcp协议;websocket协议;同源策略和跨域

发布时间:2025/3/21 编程问答 74 豆豆
生活随笔 收集整理的这篇文章主要介绍了 websocket中发生数据丢失_tcp协议;websocket协议;同源策略和跨域 小编觉得挺不错的,现在分享给大家,帮大家做个参考.

tcp协议

为什么连接的时候是三次握手,关闭的时候却是四次握手?

答:因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,"你发的FIN报文我收到了"。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。

为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?

答:虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假象网络是不可靠的,有可以最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。在Client发送出最后的ACK回复,但该ACK可能丢失。Server如果没有收到ACK,将不断重复发送FIN片段。所以Client不能立即关闭,它必须确认Server接收到了该ACK。Client会在发送出ACK之后进入到TIME_WAIT状态。Client会设置一个计时器,等待2MSL的时间。如果在该时间内再次收到FIN,那么Client会重发ACK并再次等待2MSL。所谓的2MSL是两倍的MSL(Maximum Segment Lifetime)。MSL指一个片段在网络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间。如果直到2MSL,Client都没有再次收到FIN,那么Client推断ACK已经被成功接收,则结束TCP连接。

为什么不能用两次握手进行连接?

答:3次握手完成两个重要的功能,既要双方做好发送数据的准备工作(双方都知道彼此已准备好),也要允许双方就初始序列号进行协商,这个序列号在握手过程中被发送和确认。

现在把三次握手改成仅需要两次握手,死锁是可能发生的。作为例子,考虑计算机S和C之间的通信,假定C给S发送一个连接请求分组,S收到了这个分组,并发 送了确认应答分组。按照两次握手的协定,S认为连接已经成功地建立了,可以开始发送数据分组。可是,C在S的应答分组在传输中被丢失的情况下,将不知道S 是否已准备好,不知道S建立什么样的序列号,C甚至怀疑S是否收到自己的连接请求分组。在这种情况下,C认为连接还未建立成功,将忽略S发来的任何数据分 组,只等待连接确认应答分组。而S在发出的分组超时后,重复发送同样的分组。这样就形成了死锁。

如果已经建立了连接,但是客户端突然出现故障了怎么办?

TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。

websocket协议

Websocket协议

Websocket是html5提出的一个协议规范,参考rfc6455。

websocket约定了一个通信的规范,通过一个握手的机制,客户端(浏览器)和服务器(webserver)之间能建立一个类似tcp的连接,从而方便C-S之间的通信。在websocket出现之前,web交互一般是基于http协议的短连接或者长连接。

WebSocket是为解决客户端与服务端实时通信而产生的技术。websocket协议本质上是一个基于tcp的协议,是先通过HTTP/HTTPS协议发起一条特殊的http请求进行握手后创建一个用于交换数据的TCP连接,此后服务端与客户端通过此TCP连接进行实时通信。

Websocket和HTTP协议的关系

同样作为应用层的协议,WebSocket在现代的软件开发中被越来越多的实践,和HTTP有很多相似的地方,这里将它们简单比较:

相同点

  • 都是基于TCP的应用层协议。

  • 都使用Request/Response模型进行连接的建立。

  • 在连接的建立过程中对错误的处理方式相同,在这个阶段WS可能返回和HTTP相同的返回码。

  • 都可以在网络中传输数据。

  • 不同点

  • WS使用HTTP来建立连接,但是定义了一系列新的header域,这些域在HTTP中并不会使用。

  • WS的连接不能通过中间人来转发,它必须是一个直接连接。

  • WS连接建立之后,通信双方都可以在任何时刻向另一方发送数据。

  • WS连接建立之后,数据的传输使用帧来传递,不再需要Request消息。

  • WS的数据帧有序。

  • 协议

    1. 握手

    客户端发起握手。

    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

    从上面的协议中可以看到websocket基于HTTP协议的GET。而且只支持GET.

    Upgrade:upgrade是HTTP1.1中用于定义转换协议的header域。它表示,如果服务器支持的话,客户端希望使用现有的「网络层」已经建立好的这个「连接(此处是TCP连接)」,切换到另外一个「应用层」(此处是WebSocket)协议。

    Connection:HTTP1.1中规定Upgrade只能应用在「直接连接」中,所以带有Upgrade头的HTTP1.1消息必须含有Connection头,因为Connection头的意义就是,任何接收到此消息的人(往往是代理服务器)都要在转发此消息之前处理掉Connection中指定的域(不转发Upgrade域)。

    Sec-WebSocket-*:第7行标识了客户端支持的子协议的列表(关于子协议会在下面介绍),第8行标识了客户端支持的WS协议的版本列表,第5行用来发送给服务器使用(服务器会使用此字段组装成另一个key值放在握手返回信息里发送客户端)。

    服务端响应

    HTTP/1.1 101 Switching ProtocolsUpgrade: websocketSec-Websocket-Accept: ZEs+c+VBk8Aj01+wJGN7Y15796g=Connection: UpgradeSec-WebSocket-Protocol: chat, superchat

    Sec-Websocket-Accept 是一个校验。用客户端发来的sec_key 服务器通过sha1计算拼接商GUID【258EAFA5-E914-47DA-95CA-C5AB0DC85B11 】 。然后再base64encode

    数据传输

    客户端和服务器连接成功后,就可以进行通信了,通信协议格式是WebSocket格式,服务器端采用Tcp Socket方式接收数据,进行解析,协议格式如下:

    这里使用的是数据存储的位(bit),当进行加密的时候,最终要的一位就是最左边的第一个。

    • FIN :1bit ,表示是消息的最后一帧,如果消息只有一帧那么第一帧也就是最后一帧。

    • RSV1,RSV2,RSV3:每个1bit,必须是0,除非扩展定义为非零。如果接受到的是非零值但是扩展没有定义,则需要关闭连接。

    • Opcode:4bit,解释Payload数据,规定有以下不同的状态,如果是未知的,接收方必须马上关闭连接。状态如下:0x0(附加数据帧) 0x1(文本数据帧) 0x2(二进制数据帧) 0x3-7(保留为之后非控制帧使用) 0xB-F(保留为后面的控制帧使用) 0x8(关闭连接帧) 0x9(ping) 0xA(pong)

    • Mask:1bit,掩码,定义payload数据是否进行了掩码处理,如果是1表示进行了掩码处理。

      Masking-key域的数据即是掩码密钥,用于解码PayloadData。客户端发出的数据帧需要进行掩码处理,所以此位是1。
    • Payload length:7位,7 + 16位,7+64位,payload数据的长度,如果是0-125,就是真实的payload长度,如果是126,那么接着后面的2个字节对应的16位无符号整数就是payload数据长度;如果是127,那么接着后面的8个字节对应的64位无符号整数就是payload数据的长度。

    • Masking-key:0到4字节,如果MASK位设为1则有4个字节的掩码解密密钥,否则就没有。

    • Payload data:任意长度数据。包含有扩展定义数据和应用数据,如果没有定义扩展则没有此项,仅含有应用数据。

    服务端简单实现

    // 封装ws 协议的数据包function build($msg) {   $frame = [];   $frame[0] = '81'; // 81 就是 10000001 第一位1表示最后一个数据段,最后一位1表示这是文本数据   $len = strlen($msg);   if ($len < 126) {     //7位长度 第一个是掩码 默认是0     //小于126的时候 也是 01111110 数据包第二个字节表示长度       $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}));   }   //最后是数据内容   $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(){}

    同源策略和跨域

    同源策略

    URL说明是否允许通信
    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同一域名,不同协议不允许
    http://www.a.com/a.jshttp://70.32.92.74/b.js域名和域名对应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不同域名不允许

    特别注意两点:

    第一,如果是协议和端口造成的跨域问题“前台”是无能为力的,

    第二:在跨域问题上,域仅仅是通过“URL的首部”来识别而不会去尝试判断相同的ip地址对应着两个域或两个域是否在同一个ip上。

    “URL的首部”指window.location.protocol +window.location.host,也可以理解为“Domains, protocols and ports must match”。

    比较常见的解决方案

    方式一:使用ajax的jsonp

    方式二:使用jQuery的jsonp插件

    方式三:使用cors

    方式四:配置nginx

    总结

    以上是生活随笔为你收集整理的websocket中发生数据丢失_tcp协议;websocket协议;同源策略和跨域的全部内容,希望文章能够帮你解决所遇到的问题。

    如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。