WebSocket和Socket

WebSocket和Socket

tags:WebSocket和Socket


引言:好多朋友想知道WebSocket和Socket的联系和区别,下面就是你们想要的

先来一张之前收集的图,我看到这张图真的是笑了,当时还给我朋友门转发了一下,不知道你笑了没有。

看完上面的图,应该猜到了,他们之间也确实没有什么实质性的联系,当然除了名字有点相同,文章后面有名称的由来可以参考阅读

Socket

英文socket的意思是插座,网络中的Socket是一个抽象的接口,可以理解为网络中连接的两端。通常被叫做套接字接口,其意义在对传输层进行封装屏蔽了传输层的复杂性。它并不是一个协议,是为了大家更方便的使用传输层协议产生的一个抽象层。大部分的主流编程语言都提供socket函数。我们拿php来举例说明

<?php
1. socket_create() - 创建一个套接字
2. socket_accept() - Accepts a connection on a socket (接收)
3. socket_bind() - 给套接字绑定IP和端口号
4. socket_connect() - 和一个套接字建立连接 
5. socket_listen() - Listens for a connection on a socket (监听)
6. socket_last_error() - Returns the last error on the socket 
7. socket_strerror() - Return a string describing a socket error 
?>

我们可以用这些函数来建立连接实现通信功能。关于Socket我们就说这些

WebSocket

说道WebSocket了解过一些的人可能会觉得有些高大上的感觉,它的诞生还有些故事可以讲,大概是在为w3c放弃了html后,还有那么一群人不服气(不想放弃),想要继续推动html发展,同时他们也发展了一些其他的网络标准,并且被官方接收。而WebSocket就是其中一种,是为了创建一种双向通信(全双工)的协议,来作为HTTP协议的一个替代者,以解决基于http上的长轮询等技术解决不了(或者解决的不那么优美)的问题。而且这厮一开始并不叫WebSocket,好像是叫webConnect之类的,最后是一位工程师提议说要么咱们叫WebSocket吧,然后。。。。。,好了故事就这样,他既然是HTTP的替代者,我们首先看一下它和HTTP(或者HTTP的长连接)的联系和区别。

WebSocket和HTTP 1.1的联系

首先两者都是应用层协议,而且 WebSocket 在建立连接时,需要借用 http 的 101 switch protocol 来达到协议转换,为了建立一个 WebSocket 连接,客户端浏览器首先要向服务器发起一个HTTP请求,这个请求和通常的HTTP请求不同,包含了一些附加头信息,其中附加头信息"Upgrade: WebSocket"和"Connection: Upgrade"表明这是一个申请协议升级的 HTTP 请求,服务器端解析这些附加的头信息然后产生应答信息返回给客户端,客户端和服务器端的 WebSocket 连接就建立起来了,在建立连接后,就和HTTP没有关系了,双方就可以通过这个连接通道自由的传递信息。
当然,也有可能服务器不支持WebSocket,那就老老实实的用http吧,目前大部分浏览器和服务器都已支持WebSocket。

贴一段简单WebSocket客户端的js代码

<script type="text/javascript">
    //语法 var Socket = new WebSocket(url, [protocol] );
    var ws = new WebSocket("ws://localhost:6688/send");
    //连接建立时触发
    ws.onopen = function(evt) {
        console.log("Connection open ...");
        ws.send("Hello WebSockets!");
    };
    //接收消息时触发
    ws.onmessage = function(evt) {
        console.log("Received Message: " + evt.data);
        ws.close();
    };
    //关闭连接触发
    ws.onclose = function(evt) {
        console.log("Connection closed.");
    };
    //通信发生错误时触发
    ws.onerror = function(evt) {
        console.log("Connection Error.");
    };
    //检查浏览器是否支持WebSocket
    if(typeof WebSocket != 'undefined'){
        alert("您的浏览器支持 WebSocket!");
    }else{
        // 浏览器不支持 WebSocket
        alert("您的浏览器不支持 WebSocket!");
    }
</script>

WebSocket和HTTP 1.1区别

我们来看一下他的格式:

//一个WebSocket连接始于握手(handshake)
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的Request的起始行一样,而真正在WS的握手过程中起到作用的是下面几个header域。

  • Upgrade:websocket
    upgrade是HTTP1.1中用于定义转换协议的header域。它表示要升级(转换)到某个协议(如果服务器支持的话)。

  • Connection:Upgrade 表示要进行升级协议

  • Sec-WebSocket-Key:用来发送给服务器过滤非预期的请求(比如手动填写header中的一些信息,但本身不想升级到WebSocket。这时候,由于Sec-WebSocket-Key和一些相关项被禁止手动设置,所以可以过滤掉出现非预期的情况)。

  • Origin:作安全使用,防止跨站攻击,浏览器一般会使用这个来标识原始域。

  • Sec-WebSocket-Protocol:客户端支持的子协议的列表。

  • Sec-WebSocket-Version:客户端支持的WS协议的版本。

//服务端应答handshake 101表示切换
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat

上面是报文区别,还有一些其他的特性

  • WebSocket的连接必须是一个直接连接(这个我还需要仔细研究研究,还不是很透彻,如果有懂的朋友可以帮我理解理解将不胜感谢。回头我仔细研究一下通信的方式和数据帧格式)。
  • WebSocket连接建立之后,通信双方都可以在任何时刻向另一方发送数据(即全双工,这是最主要的)。
  • WebSocket连接建立之后,数据的传输使用帧来传递,不同于Request。

由于展开来讲的话篇幅太长,大家也可以自行深入了解。

WebSocket的全双工和HTTP

在HTTP中,一个Request对应着一个Response,早期的HTTP1.0每次的HTTP连接都需要打开一个TCP连接,在一个Request后,服务器产生一个应答Request,这次HTTP连接就结束了,同时关闭了TCP连接,重复的建立TCP连接是一种资源浪费,主动关闭TCP连接后还会出现time_wait状态,继续占用资源 一段时间(可以看上一篇文章TCP连接和 time_wait、close_waite
这种情况在HTTP1.1中进行了一定的改进,使得有一个keep-alive,也就是说,在一个HTTP连接中,可以发送多个Request,接收多个Response,可以减少建立和拆除TCP连接的次数,因此同时减少了time_wait状态的连接,但是,如果设置了keep-alive的超时时间比如nginx中是keepalive_timeout,一段时间没有通信超时后服务器主动关闭连接也是可能造成服务器出现time_wait状态的,如果不设置超时时间也会造成一定的资源浪费(占用连接却不发送数据),所以怎么设置这个超时时间也很重要。

本质上HTTP1.1中虽然可以保持持久的连接,但是它依然不是全双工的,因为服务端是不可以主动给客户端发送消息的,ajax轮询的方式虽然可以达到WebSocket全双工的类似效果,但是会造成大量的资源浪费。

数据帧格式

 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 ...                |
 +---------------------------------------------------------------+
  1. 从左到右,单位是比特。比如FIN、RSV1、RSV2、RSV3各占据1比特,opcode占据4比特。
  2. 内容包括了标识、操作代码、掩码、数据、数据长度等。(下一小节会展开)

各个标志位

参考上面的数据帧格式来说一下各个字段的含义,重要字段加粗

  • FIN:(finish)1个比特。
    如果是1,表示这是消息的最后一个分片(fragment),如果是0,表示不是最后一个分片。

  • RSV1, RSV2, RSV3:各占1个比特。
    若不采用WebSocket扩展这里必须为0。当客户端、服务端协商采用WebSocket扩展时,这三个标志位可以非0,且值的含义由扩展进行定义。如果出现非零的值,且并没有采用WebSocket扩展,连接出错。

  • Opcode: 4个比特。

    操作代码,定义了对“负载数据”的解释,Opcode的值决定了应该如何解析后续的数据载荷(data payload)。如果收到一个未知的操作码,接收端点应该断开连接(fail the connection)。可选的操作代码如下:

    %x0:表示一个延续帧。当Opcode为0时,表示本次数据传输采用了数据分片,当前收到的数据帧为其中一个数据分片。
    %x1:表示这是一个文本帧(frame)
    %x2:表示这是一个二进制帧(frame)
    %x3-7:保留的操作代码,用于后续定义的非控制帧,(一般协议中都会预留出一些码用于扩展)。
    %x8:表示连接断开/关闭。
    %x9:表示这是一个ping操作。
    %xA:表示这是一个pong操作。
    %xB-F:保留的操作代码,用于后续定义的控制帧(一般协议中都会预留出一些码用于扩展)。

  • Mask: 1个比特。
    表示是否要对数据载荷进行掩码操作。从客户端向服务端发送数据时,需要对数据进行掩码操作,Mask需要为1,masking-key(掩码键)字段存在值;从服务端向客户端发送数据时,不需要对数据进行掩码操作,Mask需要为0。如果服务端接收到的数据没有进行过掩码操作,服务端需要断开连接。

  • Payload length:数据载荷的长度,单位是字节。为7位,或7+16位,或1+64位。

    假设Payload length的值为x,如果
    x为0~126:数据的长度为x字节。
    x为126:后续2个字节代表一个16位的无符号整数,该无符号整数的值为数据的长度。
    x为127:后续8个字节代表一个64位的无符号整数(最高位为0),该无符号整数的值为数据的长度。
    上面这种定义负载长度方式是一种网络协议中常用的方法,可以实现灵活扩展的数据长度定义。

  • Masking-key:0或4字节(32位)
    所有从客户端传送到服务端的数据帧,数据载荷都进行了掩码操作,Mask为1,且携带了4字节的Masking-key。如果Mask为0,则没有Masking-key。

  • Payload data:(载荷数据 x+y 字节)
    载荷数据:包括了扩展数据、应用数据。其中,扩展数据x字节,应用数据y字节。载荷数据的长度,不包括mask key的长度。

  • Extension data(扩展数据 x 字节):
    如果没有协商使用扩展的话,扩展数据数据为0字节。所有的扩展都必须声明扩展数据的长度,或者可以如何计算出扩展数据的长度。此外,扩展如何使用必须在握手阶段就协商好。如果扩展数据存在,那么载荷数据长度必须将扩展数据的长度包含在内。

  • Application data(应用数据 y 字节):
    任意的应用数据在扩展数据之后(如果存在扩展数据),占据了数据帧剩余的位置。载荷数据长度 减去 扩展数据长度,就得到应用数据的长度。

Ping Pang 心跳

对于长时间没有数据交互的连接,会浪费连接资源。但不排除有些场景,客户端、服务端虽然长时间没有数据往来,但仍需要保持连接。这个时候,可以采用心跳来实现。(不知道心跳为什么是ping、pang)

  1. ping
  • Ping帧操作码(opcode)0x9。可以包含“应用数据
  • 当收到一个Ping帧时,接收方必须在响应中发送一个Pong帧,除非它早已接收到一个关闭帧。它应该尽可能快地以Pong帧响应。(也用来验证远程端点是否可响应)
  1. Pong
  • Pong帧操作码(opcode)0x9。
  • 一个Pong帧必须携和被响应的Ping帧中相同的数据
  • 如果再一个ping到达服务端,服务端尚未响应前,由到达同源的ping帧,则可以只响应最新的ping帧,
  • 未收到ping也可以发送一个Pong帧。这个充当单向的心跳(heartbeat),另一方不需要响应。
posted @ 2018-05-22 13:45  vinter_he  阅读(1133)  评论(2编辑  收藏  举报