websocket 原理详解

一、概述

1、缘起

websocket协议诞生于2008年,在2011年成为国际标准,此时Http 1.1 已诞生了12年(1999年成为国标)。
WebSocket协议是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信,即允许服务器主动发送信息给客户端。因此,在WebSocket中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输,客户端和服务器之间的数据交换变得更加简单。

在此之前双向通信(客户端要向服务器传送数据,同时服务器也需要实时的向客户端传送信息,一个聊天系统就是典型的双向通信)时一般会使用这样几种解决方案:
1.轮询(polling):轮询就会造成对网络和通信双方的资源的浪费,且非实时。
2.长轮询:客户端发送一个超时时间很长的Request,服务器hold住这个连接,在有新数据到达时返回Response,相比#1,占用的网络带宽少了,其他类似。
3.长连接:Connection: keep-alive

2、解决了什么问题

1.websocket通过自己的 WS 协议(此处与HTTP协议有所区别)创建一个基于HTTP request请求并创建TCP链接之后,之后的数据交换都不需要再次去创建连接,实现真正的长连接。
2.websocket采用异步回调的方式接受消息,当建立通信连接,可以做到持久性的连接,并进行通信。而不像上面的几种方式一样需要定时进行发起请求到服务器获取最新更新信息,显得相当的被动)
3.实质的推送方式是服务器主动推送,只要有数据就推送到请求方。(变被动为主动)

3、怎么做到的

websocket协议本质上是一个基于TCP(长连接)的协议。建立连接需要握手,客户端首先向服务器发起一条特殊的http请求,带有websocket的升级标识,服务端对请求报文进行握手,生成客户端识别的响应报文,完成websocket连接的建立,直到某一方关闭连接才会结束。

二、连接过程

1.必不可少的TCP三次握手和四次挥手

在这里插入图片描述

2、客户端发起请求

在这里插入图片描述

可以看出跟http请求的一些不同之处

headersdesc
101首次发送请求进行握手,用的还是http1.1协议,规定服务端返回101 状态码,为握手成功
Connection: upgrade同意升级
Upgrade: websocket同意升级
Sec-WebSocket-Accept: vPTr9rrkhy8cA5jkXdEpDjFjqVU=服务器响应,包含Sec-WebSocket-Key的签名值,证明它支持请求的协议版本

用入参Sec-WebSocket-Key+“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”(这个值是固定的)

进行SHA-1算法加密再进行Base64编码返回 |
|Sec-WebSocket-Extensions: permessage-deflate;client_max_window_bits=15| deflate压缩算法,client_max_window_bits=15为算法的最大窗口长度,默认为15,范围必须在8-15之间 |

3.服务端返回报文
在这里插入图片描述

headersdesc
101首次发送请求进行握手,用的还是http1.1协议,规定服务端返回101 状态码,为握手成功
Connection: upgrade同意升级
Upgrade: websocket同意升级
Sec-WebSocket-Accept: vPTr9rrkhy8cA5jkXdEpDjFjqVU=服务器响应,包含Sec-WebSocket-Key的签名值,证明它支持请求的协议版本用入参Sec-WebSocket-Key+“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”(这个值是固定的)进行SHA-1算法加密再进行Base64编码返回
Sec-WebSocket-Extensions: permessage-deflate;client_max_window_bits=15deflate压缩算法,client_max_window_bits=15为算法的最大窗口长度,默认为15,范围必须在8-15之间

三、数据格式及解析

1.websocket数据格式—rfc6455.pdf(websocket基本协议内容)在这里插入图片描述

以二进制数据进行解析

namedesc
finFIN, 长度为 1 bits, 该标志位用于指示当前的 frame 是消息的最后一个分段。WebSocket 支持将长消息切分为若干个 frame 发送, 切分以后, 除了最后一个 frame, 前面的 frame 的 FIN 字段都为 0, 最后一个 frame 的 FIN 字段为 1, 当然, 若消息没有分段, 那么一个 frame 便包含了完成的消息, 此时其 FIN 字段值为 1。
RSV预留位,但必须是0
opcode占4bits ,操作描述符 16进制 0x0–0xf 我们只需要关心 1:文本数据 2:二进制数据 8:关闭连接
MASK长度为 1 bits, 该字段是一个标志位, 用于指示 frame 的数据 (Payload) 是否使用掩码掩盖。 服务端要求客户端请求的MASK必须为 1,反之则不需要。
Payload len数据长度 7 bits, 7+16 bits, or 7+64 bits 这个是可变的长度7位的长度 只能表达 128(0-127),这个字段有三个可能,0-125 时 数据长度就是此值;126时,往后取16比特 来表示数据长度;127时往后取64比特来表示长度。基本可以表达相当大的数据量,如果还不够就需要考虑数据分片。
Masking-key长度64 bits,当前面的MASK 为1 时才有此值,用于对真实数据进行掩码处理。掩码操作就是以 掩码数据对后续的业务数据进行遍历异或运算,客户端加掩码时也是这么操作的
Payload Data业务数据,长度为上文 Payload len所述

简易解码逻辑–如何取到业务数据

public static  byte[] dataToMsg(IoBuffer in) {
    if (in.remaining() < 2)
        return null;
    //一次get取一个字节,占8个bit位
    byte fstByte = in.get();
    if ((fstByte & 0x8) != 0) {
        //fin位必须为1 后三位为0
        return null;
    }
    int rsv=(fstByte & 0x70) >>> 4;
    int rsvNext = rsv;
    if ((rsv & RSV_BITMASK) != 0) {
        rsvNext = rsv ^ RSV_BITMASK;
    }
    if(rsvNext != 0){
        //验证RSV和操作码组合
        return null;
    }
    int opCode = fstByte & 0x0f;
    switch (opCode) {
        case 0x0:
            return null;
        case 0x1:
            byte secByte = in.get();
            //第一位判断
            boolean isMasking = ((secByte & 0x80) != 0);
            int dataLength;
            //取后7位
            byte payload = (byte) (secByte & 0x7F);
            logger.info("数据长度:{}",payload);
            if (payload == 126)
                //如果是126 再取 无符合的short类型 相当于两个字节类型  16位
                dataLength = in.getUnsignedShort();
            else if (payload == 127)
                //如果是127 再取 Long类型 相当于8个字节类型  64位
                dataLength = (int) in.getLong();
            else
                //真实长度
                dataLength = payload;

            byte[] mask = new byte[4];
            byte[] data = new byte[dataLength];
            if (isMasking)
                in.get(mask);

            in.get(data);
            // 用掩码处理数据。
            for (int i = 0, maskLength = mask.length, looplimit = data.length; i < looplimit; i++)
                data[i] = (byte) (data[i] ^ mask[i % maskLength] & 0xFF);
            return data;
        default:
            return null;
    }
}

2.deflate压缩算法–2015年引入的rfc7692 协议

一种利用数据的重复结构进行压缩的算法

public static void testInflater(String message){
      try {
         System.out.println("Original Message: " + message);
         System.out.println("Original Message length: " + message.getBytes(StandardCharsets.UTF_8).length);
         byte[] input = message.getBytes("UTF-8");
         // Compress the bytes
         byte[] output = new byte[1024];
         Deflater deflater = new Deflater();
         deflater.setInput(input);
         deflater.finish();
         //返回压缩后长度
         int compressedDataLength = deflater.deflate(output);
         deflater.end();
//         System.out.println("Compressed Message: " + output.length);
         System.out.println("Compressed Message length: " + compressedDataLength);
         // Decompress the bytes
         Inflater inflater = new Inflater();
         inflater.setInput(output);
         byte[] result = new byte[1024*1024];
         int resultLength = inflater.inflate(result);
         inflater.end();
         // Decode the bytes into a String
         message = new String(result, 0, resultLength, "UTF-8");
      } catch (UnsupportedEncodingException e) {
         e.printStackTrace();
      } catch (DataFormatException e) {
         e.printStackTrace();
      }
      System.out.println("UnCompressed Message: " + message);
   }

如果数据过短可能不会起到好的效果

3.sockJs&StompJs

1.sockJs —维护了请求降级逻辑,即浏览器不支持时,降级为轮询

链接: https://github.com/sockjs/sockjs-client

在这里插入图片描述
在这里插入图片描述在这里插入图片描述

2.Stomp.Js–更为便捷的简单流文本传输协议

链接: https://github.com/stomp-js/stompjs

在这里插入图片描述

简单来说就是定义了websocket-message的内容格式来优雅的实现C/S架构的消息通讯,并维护了服务端->客户端的心跳。

四、开发一个websocket服务器

代码演示–待上传

五、运用场景

代码包:
简易聊天室演示:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值