写点什么

Websocket 10 分钟快速入门

  • 2022 年 6 月 07 日
  • 本文字数:6736 字

    阅读完需:约 22 分钟

Websocket 10分钟快速入门

一、WebSocket 诞生背景

早期,很多网站为了实现推送技术,所用的技术都是轮询(也叫短轮询)。轮询是指由浏览器每隔一段时间向服务器发出 HTTP 请求,然后服务器返回最新的数据给客户端。

直观感受轮询与长轮询之间的区别,我们来看一下具体的代码


这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而 HTTP 请求与响应可能会包含较长的头部,其中真正有效的数据可能只是很小的一部分,所以这样会消耗很多带宽资源。


比较新的轮询技术是 Comet。这种技术虽然可以实现双向通信,但仍然需要反复发出请求。而且在 Comet 中普遍采用的 HTTP 长连接也会消耗服务器资源。


在这种情况下,HTML5 定义了 WebSocket 协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。

Websocket 使用 ws 或 wss 的统一资源标志符(URI)其中 wss 表示使用了 TLS 的 Websocket。

WebSocket 与 HTTP 和 HTTPS 使用相同的 TCP 端口,可以绕过大多数防火墙的限制。


默认情况下:

· 1)WebSocket 协议使用 80 端口;

· 2)若运行在 TLS 之上时,默认使用 443 端口。


二、WebSocket 简介

WebSocket 是一种网络传输协议,可在单个 TCP 连接上进行全双工通信,位于 OSI 模型的应用层。WebSocket 协议在 2011 年由 IETF 标准化为 RFC 6455,后由 RFC 7936 补充规范。


WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的连接,并进行双向数据传输。


介绍完轮询和 WebSocket 的相关内容之后,接下来用一张图看一下 XHR Polling(短轮询) 与 WebSocket 之间的区别。


XHR Polling 与 WebSocket 之间的区别如下图所示:

三、WebSocket 优点

普遍认为,WebSocket 的优点有如下几点:

· 1)较少的控制开销:在连接创建后,服务器和客户端之间交换数据时,用于协议控制的数据包头部相对较小;

· 2)更强的实时性:由于协议是全双工的,所以服务器可以随时主动给客户端下发数据。相对于 HTTP 请求需要等待客户端发起请求服务端才能响应,延迟明显更少;

· 3)保持连接状态:与 HTTP 不同的是,WebSocket 需要先创建连接,这就使得其成为一种有状态的协议,之后通信时可以省略部分状态信息;

· 4)更好的二进制支持:WebSocket 定义了二进制帧,相对 HTTP,可以更轻松地处理二进制内容;

· 5)可以支持扩展:WebSocket 定义了扩展,用户可以扩展协议、实现部分自定义的子协议。


由于 WebSocket 拥有上述的优点,所以它被广泛地应用在即时通讯/IM、实时音视频、在线教育和游戏等领域。

对于前端开发者来说,要想使用 WebSocket 提供的强大能力,就必须先掌握 WebSocket API,下面带大家一起来认识一下 WebSocket API。


四、基本介绍

在介绍 WebSocket API 之前,我们先来了解一下它的兼容性:


由上图可知:目前主流的 Web 浏览器都支持 WebSocket,所以我们可以在大多数项目中放心地使用它。

在浏览器中要使用 WebSocket 提供的能力,我们就必须先创建 WebSocket 对象,该对象提供了用于创建和管理 WebSocket 连接,以及可以通过该连接发送和接收数据的 API。

使用 WebSocket 构造函数,我们就能轻易地构造一个 WebSocket 对象。


接下来我们将从以下四个方面来介绍 WebSocket API:

· 1)WebSocket 构造函数;

· 2)WebSocket 对象的属性;

· 3)WebSocket 的方法;

· 4)WebSocket 事件。

接下来我们从 WebSocket 的构造函数入手开始学习。


4.1 构造函数

WebSocket 构造函数的语法为:

const myWebSocket = newWebSocket(url [, protocols]);


相关参数说明如下:

· 1)url:表示连接的 URL,这是 WebSocket 服务器将响应的 URL;

· 2)protocols(可选):一个协议字符串或者一个包含协议字符串的数组。


针对第 2 点:这些字符串用于指定子协议,这样单个服务器可以实现多个 WebSocket 子协议。


比如:你可能希望一台服务器能够根据指定的协议(protocol)处理不同类型的交互。如果不指定协议字符串,则假定为空字符串。


使用 WebSocket 构造函数时,当尝试连接的端口被阻止时,会抛出 SECURITY_ERR 异常。


4.2 对象的属性

WebSocket 对象包含以下属性:


每个属性的具体含义如下:

· 1)binaryType:使用二进制的数据类型连接;

· 2)bufferedAmount(只读):未发送至服务器的字节数;

· 3)extensions(只读):服务器选择的扩展;

· 4)onclose:用于指定连接关闭后的回调函数;

· 5)onerror:用于指定连接失败后的回调函数;

· 6)onmessage:用于指定当从服务器接受到信息时的回调函数;

· 7)onopen:用于指定连接成功后的回调函数;

· 8)protocol(只读):用于返回服务器端选中的子协议的名字;

· 9)readyState(只读):返回当前 WebSocket 的连接状态,共有 4 种状态:

· - CONNECTING — 正在连接中,对应的值为 0;

· - OPEN — 已经连接并且可以通讯,对应的值为 1;

· - CLOSING — 连接正在关闭,对应的值为 2;

· - CLOSED — 连接已关闭或者没有连接成功,对应的值为 3

· 10)url(只读):返回值为当构造函数创建 WebSocket 实例对象时 URL 的绝对路径。


4.3 方法

WebSocket 主要方法有两个:

· 1)close([code[, reason]]):该方法用于关闭 WebSocket 连接,如果连接已经关闭,则此方法不执行任何操作;


· 2)send(data):该方法将需要通过 WebSocket 链接传输至服务器的数据排入队列,并根据所需要传输的数据的大小来增加 bufferedAmount 的值 。若数据无法传输(比如数据需要缓存而缓冲区已满)时,套接字会自行关闭。


4.4 事件

使用 addEventListener() 或将一个事件监听器赋值给 WebSocket 对象的 oneventname 属性,来监听下面的事件。


以下是几个事件:

· 1)close:当一个 WebSocket 连接被关闭时触发,也可以通过 onclose 属性来设置;

· 2)error:当一个 WebSocket 连接因错误而关闭时触发,也可以通过 onerror 属性来设置;

· 3)message:当通过 WebSocket 收到数据时触发,也可以通过 onmessage 属性来设置;

· 4)open:当一个 WebSocket 连接成功时触发,也可以通过 onopen 属性来设置。


介绍完 WebSocket API,我们来举一个使用 WebSocket 发送普通文本的示例。


五、WebSocket 与长轮询有什么区别?

长轮询就是:客户端发起一个请求,服务器收到客户端发来的请求后,服务器端不会直接进行响应,而是先将这个请求挂起,然后判断请求的数据是否有更新。如果有更新,则进行响应,如果一直没有数据,则等待一定的时间后才返回。

长轮询的本质还是基于 HTTP 协议,它仍然是一个一问一答(请求 — 响应)的模式。而 WebSocket 在握手成功后,就是全双工的 TCP 通道,数据可以主动从服务端发送到客户端。

5.1 什么是 WebSocket 心跳?

网络中的接收和发送数据都是使用 Socket 进行实现。但是如果此套接字已经断开,那发送数据和接收数据的时候就一定会有问题。


可是如何判断这个套接字是否还可以使用呢?

这个就需要在系统中创建心跳机制。所谓 “心跳” 就是定时发送一个自定义的结构体(心跳包或心跳帧),让对方知道自己 “在线”,以确保链接的有效性。


而所谓的心跳包就是客户端定时发送简单的信息给服务器端告诉它我还在而已。代码就是每隔几分钟发送一个固定信息给服务端,服务端收到后回复一个固定信息,如果服务端几分钟内没有收到客户端信息则视客户端断开。


在 WebSocket 协议中定义了 心跳 Ping 和 心跳 Pong 的控制帧:

· 1)心跳 Ping 帧包含的操作码是 0x9:如果收到了一个心跳 Ping 帧,那么终端必须发送一个心跳 Pong 帧作为回应,除非已经收到了一个关闭帧。否则终端应该尽快回复 Pong 帧;

· 2)心跳 Pong 帧包含的操作码是 0xA:作为回应发送的 Pong 帧必须完整携带 Ping 帧中传递过来的 “应用数据” 字段。


针对第 2 点:如果终端收到一个 Ping 帧但是没有发送 Pong 帧来回应之前的 Ping 帧,那么终端可以选择仅为最近处理的 Ping 帧发送 Pong 帧。此外,可以自动发送一个 Pong 帧,这用作单向心跳。


5.2 Socket 是什么

网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个 Socket(套接字),因此建立网络通信连接至少要一对端口号。

Socket 本质:是对 TCP/IP 协议栈的封装,它提供了一个针对 TCP 或者 UDP 编程的接口,并不是另一种协议。通过 Socket,你可以使用 TCP/IP 协议。


关于 Socket,可以总结以下几点:

· 1)它可以实现底层通信,几乎所有的应用层都是通过 socket 进行通信的;

· 2)对 TCP/IP 协议进行封装,便于应用层协议调用,属于二者之间的中间抽象层;

· 3)TCP/IP 协议族中,传输层存在两种通用协议: TCP、UDP,两种协议不同,因为不同参数的 socket 实现过程也不一样。


下图说明了面向连接的协议的套接字 API 的客户端/服务器关系:

六、WebSocket 与 HTTP 有什么关系?

WebSocket 是一种与 HTTP 不同的协议。两者都位于 OSI 模型的应用层,并且都依赖于传输层的 TCP 协议。

虽然它们不同,但是 RFC 6455 中规定:WebSocket 被设计为在 HTTP 80 和 443 端口上工作,并支持 HTTP 代理和中介,从而使其与 HTTP 协议兼容。为了实现兼容性,WebSocket 握手使用 HTTP Upgrade 头,从 HTTP 协议更改为 WebSocket 协议。

既然已经提到了 OSI(Open System Interconnection Model)模型,这里分享一张很生动、很形象描述 OSI 模型的示意图(如下图所示)。


七、手写 WebSocket 服务器

7.1 写在前面

在介绍如何手写 WebSocket 服务器前,我们需要了解一下 WebSocket 连接的生命周期。

握手是在通信电路创建之后,信息传输开始之前。

握手用于达成参数,如:

· 1)信息传输率

· 2)字母表

· 3)奇偶校验

· 4)中断过程;

· 5)其他协议特性。

握手有助于不同结构的系统或设备在通信信道中连接,而不需要人为设置参数。

既然握手是 WebSocket 连接生命周期的第一个环节,接下来我们就先来分析 WebSocket 的握手协议。


7.2 握手协议

· WebSocket 协议属于应用层协议,它依赖于传输层的 TCP 协议。WebSocket 通过 HTTP/1.1 协议的 101 状态码进行握手。为了创建 WebSocket 连接,需 4)Sec-WebSocket-Key:是随机的字符串,服务器端会用这些数据来构造出一个 SHA-1 的信息摘要;

· 5)Sec-WebSocket-Extensions:用于协商本次连接要使用的 WebSocket 扩展:客户端发送支持的扩展,服务器通过返回相同的首部确认自己支持一个或多个扩展;

· 6)Origin:字段是可选的,通常用来表示在浏览器中发起此 WebSocket 连接所在的页面,类似于 Referer。但是,与 Referer 不同的是,Origin 只包含了协议和主机名称。


针对上述第 4 点:把 “Sec-WebSocket-Key” 加上一个特殊字符串 “258EAFA5-E914-47DA-95CA-C5AB0DC85B11”,然后计算 SHA-1 摘要,之后进行 Base64 编码,将结果做为 “Sec-WebSocket-Accept” 头的值,返回给客户端。如此操作,可以尽量避免普通 HTTP 请求被误认为 WebSocket 协议。


7.3 搭建后段服务器 springboot 实例

1.引入 SpringBootWebSocket 依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency>
复制代码


2.配置 WebSocketConfig 启动类

import org.springframework.web.socket.config.annotation.*;@Configuration@EnableWebSocketMessageBrokerpublic class WebSocketConfig implements WebSocketMessageBrokerConfigurer {@Overridepublic void configureMessageBroker(MessageBrokerRegistry registry) {    registry.setApplicationDestinationPrefixes("/app");    registry.enableSimpleBroker("/queue", "/topic");}@Overridepublic void registerStompEndpoints(StompEndpointRegistry registry) {    registry.addEndpoint("/ws").setAllowedOrigins("*").withSockJS();    StompSubProtocolErrorHandler stompSubProtocolErrorHandler = new StompSubProtocolErrorHandler();    stompSubProtocolErrorHandler.handleClientMessageProcessingError(new ClientMessage(), new Throwable());    registry.setErrorHandler(new SimpleStompSubProtocolErrorHandler());    UrlPathHelper urlPathHelper = new UrlPathHelper();    urlPathHelper.setAlwaysUseFullPath(true);    urlPathHelper.setUrlDecode(true);        registry.setUrlPathHelper(urlPathHelper);    }}
复制代码


3.启动类添加 @EnableWebSocket 注解

import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.web.socket.config.annotation.EnableWebSocket;@SpringBootApplication@EnableWebSocketpublic class WebsocketDemoApplication {public static void main(String[] args) {      SpringApplication.run(WebsocketDemoApplication.class, args);   }}
复制代码


4.添加 Controller

@Controllerpublic class ChatController {@MessageMapping("/chat.sendMessage")@SendTo("/topic/public")public ChatMessage sendMessage(@Payload ChatMessage chatMessage) {    log.info("subscribeMapping-chart-sendMessage: " + chatMessage.toString());return chatMessage;}}
复制代码

5.添加消息对象

public class ChatMessage {private MessageType type;private String      content;private String      sender;public  enum  MessageType {        CHAT,        JOIN,        LEAVE    }public  MessageType getType() {return type;    }public  void     setType(MessageType type) {this.type = type;    }public  String getContent() {return content;    }public  void setContent(String content) {this.content = content;    }public  String  getSender() {return sender;    }public  void    setSender(String sender) {this.sender = sender;    }}
复制代码


7.4 搭建前段服务器 vue.js 实例

引入模块

import SockJS from 'sockjs-client'import Stomp from 'stompjs'
复制代码

1.初始化模块函数 配置 error 断线重连

initwebsocket () {      this.connect()var chatMessage = {sender: username,content: messageInput.value,type: 'CHAT'}stompClient.send("/app/chat.sendMessage", {}, JSON.stringify(chatMessage))  }
复制代码

2.创建连接函数 heartbeat 配置默认心跳

onconnect (event) {      let socket = new SockJS('http://127.0.0.1:3991/ws')      this.stompClient = Stomp.over(socket)      this.stompClient.connect({}, this.onConnected, this.onError)    }
复制代码

3.监听接口

4.连接时配置 error 错误函数

onError (error) {      console.log(error)      this.initwebsocket()    }
复制代码

5.建立 websocket 连接

onConnected () {      this.stompClient.subscribe('/topic/public', this.onMessageReceived)      this.displayConnect = false    }
复制代码

6.配置监听函数

onMessageReceived (payload) {      let message = JSON.parse(payload.body)      console.log(message)    }
复制代码


7.5 搭建 Android 客户端实例

引入依赖

api 'org.java-websocket:Java-WebSocket:1.3.6'

api 'com.squareup.okhttp3:okhttp:3.12.1'

 

Android Github 例子

https://github.com/NaikSoftware/StompProtocolAndroid


客户端连接实例

Vue.js 与 Android Java-WebSocket 连接

Vue.js 基于 rabbitmq stomp 集群实例

Vue.js 与 Android WebView 通讯实例


关于领创集团(Advance Intelligence Group)

领创集团成立于 2016 年,致力于通过科技创新的本地化应用,改造和重塑金融和零售行业,以多元化的业务布局打造一个服务于消费者、企业和商户的生态圈。集团旗下包含企业业务和消费者业务两大板块,企业业务包含 ADVANCE.AI 和 Ginee,分别为银行、金融、金融科技、零售和电商行业客户提供基于 AI 技术的数字身份验证、风险管理产品和全渠道电商服务解决方案;消费者业务 Atome Financial 包括亚洲领先的先享后付平台 Atome 和数字金融服务。2021 年 9 月,领创集团宣布完成超 4 亿美元 D 轮融资,融资完成后领创集团估值已超 20 亿美元,成为新加坡最大的独立科技创业公司之一。


往期回顾 BREAK AWAY

Spring data JPA 实践和原理浅析

如何解决海量数据更新场景下的 Mysql 死锁问题

企业级 APIs 安全实践指南 (建议初中级工程师收藏)

Cypress UI 自动化测试框架

serverless让我们的运维更轻松


▼ 如果觉得这篇内容对你有所帮助,有所启发,欢迎点赞收藏:

1、点赞、关注领创集团,获取最新技术分享和公司动态。

2、关注我们的公众号 & 知乎号「领创集团 Advance Group」或访问官方网站,了解更多企业动态。


发布于: 刚刚阅读数: 4
用户头像

智慧领创美好生活 2021.08.12 加入

AI技术驱动的科技集团,致力于以技术赋能为核心,通过科技创新的本地化应用,改造和重塑金融和零售行业,以多元化的业务布局打造一个服务于消费者、企业和商户的生态圈,带来个性化、陪伴式的产品服务和优质体验。

评论

发布
暂无评论
Websocket 10分钟快速入门_websocket_领创集团Advance Intelligence Group_InfoQ写作社区