- A+
WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议;使用 ws://
(非加密)和 wss://
(加密)作为协议前缀。该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信。本文主要介绍使用 WebSocket 来实现跨域请求,文中所使用到的软件版本:Chrome 90.0.4430.212、Spring Boot 2.4.4、jdk1.8.0_181。
1、WebSocket 简介
WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。在 WebSocket API 中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。
目前,很多网站都使用 Ajax 轮询来实现推送。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。
HTML5 定义的 WebSocket 协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。
浏览器通过 WebSocket 对象向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。当获取 WebSocket 连接后,就可以通过 send() 方法来向服务器发送数据,并通过 onmessage 事件来接收服务器返回的数据。
1.1、WebSocket 语法
let Socket = new WebSocket(url, [protocol]);
1.2、WebSocket 属性
属性 | 描述 |
WebSocket.readyState | 只读属性 readyState 表示连接状态,可以是以下值: 0 - 表示连接尚未建立。 1 - 表示连接已建立,可以进行通信。 2 - 表示连接正在进行关闭。 3 - 表示连接已经关闭或者连接不能打开。 |
WebSocket.bufferedAmount | 只读属性 bufferedAmount 表示已被 send() 放入队列中正在等待传输,但是还没有发出的 UTF-8 文本字节数。 |
1.3、WebSocket 事件
事件 | 事件处理程序 | 描述 |
open | WebSocket.onopen | 连接建立时触发 |
message | WebSocket.onmessage | 客户端接收服务端数据时触发 |
error | WebSocket.onerror | 通信发生错误时触发 |
close | WebSocket.onclose | 连接关闭时触发 |
1.4、WebSocket 实例
WebSocket 协议本质上是一个基于 TCP 的协议。为了建立 WebSocket 连接,客户端浏览器首先要向服务器发起一个 HTTP 请求,这个请求和通常的 HTTP 请求不同,包含了一些附加头信息,其中附加头信息"Upgrade: WebSocket"表明这是一个申请协议升级的 HTTP 请求,服务器端解析这些附加的头信息然后产生应答信息返回给客户端,客户端和服务器端的 WebSocket 连接就建立起来了,双方就可以通过这个连接通道自由的传递信息,并且这个连接会持续存在直到客户端或者服务器端的某一方主动的关闭连接。
2、WebSocket 实战
2.1、服务端实现(SpringBoot 版)
2.1.1、引入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>
2.1.2、注入 ServerEndpointExporter
package com.abc.demo.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.server.standard.ServerEndpointExporter; @Configuration public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }
2.1.3、WebSocket 实现类
package com.abc.demo.websocket; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import javax.websocket.*; import javax.websocket.server.ServerEndpoint; @ServerEndpoint(value = "/websocket") @Component public class WebSocketTest { private static Logger logger = LoggerFactory.getLogger(WebSocketTest.class); @OnOpen public void onOpen(Session session) { logger.info("有新连接加入:{}", session.getId()); sendMessage(session); } @OnClose public void onClose(Session session) { logger.info("有一连接关闭:{}", session.getId()); } @OnMessage public void onMessage(String message, Session session) { logger.info("服务端收到客户端[{}]的消息:{}", session.getId(), message); } @OnError public void onError(Session session, Throwable error) { logger.error("发生错误"); error.printStackTrace(); } private void sendMessage(Session session) { new Thread(() -> { try { for (int i = 0; i <= 10 && session.isOpen(); i++) { session.getBasicRemote().sendText("服务端消息" + i); Thread.sleep(1000 * 5); } if (session.isOpen()) { session.getBasicRemote().sendText("服务端消息发送完毕。"); } session.close(); } catch (Exception e) { e.printStackTrace(); } }).start(); } }
2.2、客户端实现
websocket.html
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>websocket测试</title> <script type="text/javascript"> let websocket = null; function initSocket() { if (window.WebSocket) { websocket = new WebSocket("ws://localhost:8081/websocket"); } else { alert('你的浏览器不支持WebSocket'); return; } websocket.onerror = function() { console.log('发生错误'); }; websocket.onopen = function(event) { console.log("建立连接"); } websocket.onmessage = function(event) { document.getElementById('message').innerHTML += event.data + '<br/>'; } websocket.onclose = function() { console.log("连接关闭"); } //窗口关闭事件,关闭websocket连接。 window.onbeforeunload = function() { websocket.close(); } } initSocket(); function closeWebSocket() { websocket.close(); } function send() { var message = document.getElementById('text').value; websocket.send(message); } </script> </head> <body> <input id="text" type="text" /> <button onclick="send()">Send</button> <button onclick="closeWebSocket()">Close</button> <div id="message"></div> </body> </html>
2.3、测试
把 websocket.html 放到 tomcat(端口:8080) 的 webappsROOT 下,并启动 SpringBoot 应用(端口:8081)。
参考:https://www.runoob.com/html/html5-websocket.html