小工具      在线工具  汉语词典  dos游戏  css  js  c++  java

WebSocket新一代推送技术及Java Web实现

数据通信与计算机网络,websocket,java,前端 额外说明

收录于:17天前

WebSocket简介

很多网站为了实现推送技术,所用的技术都是轮询。轮询是在特定的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。
而比较新的技术去做轮询的效果是Comet。这种技术虽然可以双向通信,但依然需要反复发出请求。而且在Comet中,普遍采用的长链接,也会消耗服务器资源。

在这里插入图片描述

短轮询和长轮询的代码区别:

在这里插入图片描述

为了更好的节约资源,并且能够更实时地进行通讯。HTML5 定义了 WebSocket 协议,WebSocket是一种在单个TCP连接上进行全双工通信的协议。具有更强的实时性。由于协议是全双工的,所以服务器可以随时主动给客户端下发数据。相对于HTTP请求需要等待客户端发起请求服务端才能响应,延迟明显更少;即使是和Comet等类似的长轮询比较,其也能在短时间内更多次地传递数据。
保持连接状态。与HTTP不同的是,Websocket需要先创建连接,这就使得其成为一种有状态的协议,之后通信时可以省略部分状态信息。而HTTP请求可能需要在每个请求都携带状态信息(如身份认证等)。

Websocket 使用 ws 或 wss 的统一资源标识符 (URI),其中 wss 表示使用 TLS 的 Websocket。 (相当于http和https的区别)。

在这里插入图片描述

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

如图所示,XHR Polling(短轮询)和WebSocket的区别:

在这里插入图片描述

WebSocket 的优点

1)较少的控制开销:在连接创建后,服务器和客户端之间交换数据时,用于协议控制的数据包头部相对较小;
2)更强的实时性:由于协议是全双工的,所以服务器可以随时主动给客户端下发数据。相对于 HTTP 请求需要等待客户端发起请求服务端才能响应,延迟明显更少;
3)保持连接状态:与 HTTP 不同的是,WebSocket 需要先创建连接,这就使得其成为一种有状态的协议,之后通信时可以省略部分状态信息;
4)更好的二进制支持:WebSocket 定义了二进制帧,相对 HTTP,可以更轻松地处理二进制内容;
5)可以支持扩展:WebSocket 定义了扩展,用户可以扩展协议、实现部分自定义的子协议。
由于 WebSocket 拥有上述的优点,所以它被广泛地应用在即时通讯/IM、实时音视频、在线教育和游戏等领域

在这里插入图片描述

WebSocket API

网络套接字兼容性:

在这里插入图片描述

从上图可以看出:目前主流的网络浏览器都支持WebSocket

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

接下来我们将从以下四个方面来介绍 WebSocket API:
1)WebSocket 构造函数;
2)WebSocket 对象的属性;
3)WebSocket 的方法;
4)WebSocket 事件。

Websocket构造函数

const myWebSocket = new WebSocket(url [, protocols]);

在这里插入图片描述

相关参数说明如下:
1)url:表示连接的 URL,这是 WebSocket 服务器将响应的 URL;
2)protocols(可选):一个协议字符串或者一个包含协议字符串的数组。

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 的绝对路径。

WebSocket方法

WebSocket主要有两个方法:

  • close([code[, reason]]):该方法用于关闭 WebSocket 连接,如果连接已经关闭,则此方法不执行任何操作;
  • send(data):该方法将需要通过 WebSocket 链接传输至服务器的数据排入队列,并根据所需要传输的数据的大小来增加 bufferedAmount 的值 。若数据无法传输(比如数据需要缓存而缓冲区已满)时,套接字会自行关闭。

WebSocket事件

使用 addEventListener() 或将事件侦听器分配给 WebSocket 对象的 oneventname 属性来侦听以下事件。

以下是一些事件:

  • close:WebSocket 连接关闭时触发。也可以通过onclose属性来设置;
  • error:当 WebSocket 连接因错误而关闭时触发。也可以通过onerror属性来设置;
  • message:通过WebSocket接收数据时触发,也可以通过onmessage属性设置;
  • open:WebSocket连接成功时触发。也可以通过onopen属性来设置。

WebSocket实现

  • WebSocket实例对象:
var ws = new WebSocket("wss://echo.websocket.org");
 
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.");
};

//创建一个websocket实例对象
var ws = new WebSocket('ws://localhost:8080');

//webSocket.readyState返回实例对象的当前状态
CONNECTING:值为0,表示正在连接。
OPEN:值为1,表示连接成功,可以通信了。
CLOSING:值为2,表示连接正在关闭。
CLOSED:值为3,表示连接已经关闭,或者打开连接失败。

//例子
switch (ws.readyState) {
    
  case WebSocket.CONNECTING:
    // do something
    break;
  case WebSocket.OPEN:
    // do something
    break;
  case WebSocket.CLOSING:
    // do something
    break;
  case WebSocket.CLOSED:
    // do something
    break;
  default:
    // this never happens
    break;
}



//webSocket.onopen连接成功的回调函数
ws.onopen = function () {
    
  ws.send('Hello Server!');
}


//如果要指定多个回调函数,可以使用addEventListener方法:
ws.addEventListener('open', function (event) {
    
  ws.send('Hello Server!');
});


//webSocket.onclose链接关闭的回调函数

ws.onclose = function(event) {
    
  var code = event.code;
  var reason = event.reason;
  var wasClean = event.wasClean;
  // handle close event
};
 
ws.addEventListener("close", function(event) {
    
  var code = event.code;
  var reason = event.reason;
  var wasClean = event.wasClean;
  // handle close event
});


//webSocket.onmessage用于指定收到服务器数据后的回调函数
ws.onmessage = function(event) {
    
  var data = event.data;
  // 处理数据
};
 
ws.addEventListener("message", function(event) {
    
  var data = event.data;
  // 处理数据
});


//服务器数据可能是文本,也可能是二进制数据(blob对象或Arraybuffer对象)
ws.onmessage = function(event){
    
  if(typeof event.data === String) {
    
    console.log("Received data string");
  }
 
  if(event.data instanceof ArrayBuffer){
    
    var buffer = event.data;
    console.log("Received arraybuffer");
  }
}


//除了动态判断收到的数据类型,也可以使用binaryType属性,显式指定收到的二进制数据类型

// 收到的是 blob 数据
ws.binaryType = "blob";
ws.onmessage = function(e) {
    
  console.log(e.data.size);
};
 
// 收到的是 ArrayBuffer 数据
ws.binaryType = "arraybuffer";
ws.onmessage = function(e) {
    
  console.log(e.data.byteLength);
};



//webSocket.send(),实例对象的send()方法用于向服务器发送数据。

//发送文本
ws.send('your message');

//发送Blob
var file = document
  .querySelector('input[type="file"]')
  .files[0];
ws.send(file);


//发送 ArrayBuffer 对象的例子
// Sending canvas ImageData as ArrayBuffer
var img = canvas_context.getImageData(0, 0, 400, 320);
var binary = new Uint8Array(img.data.length);
for (var ii = 0; ii < img.data.length; ii++) {
    
  binary[ii] = img.data[ii];
}
ws.send(binary.buffer);



//实例对象的bufferedAmount属性,表示还有多少字节的二进制数据没有发送出去。它可以用来判断发送是否结束。

var data = new ArrayBuffer(10000000);
socket.send(data);
 
if (socket.bufferedAmount === 0) {
    
  // 发送完毕
} else {
    
  // 发送还没结束
}



//webSocket.onerror,实例对象的onerror属性,用于指定报错时的回调函数
socket.onerror = function(event) {
    
  // handle error event
};
 
socket.addEventListener("error", function(event) {
    
  // handle error event
});

WebSocket案例

Websocket 和 servlet 非常相似。它们都是处理前端请求的容器。它们无法单独存在,需要借用网络服务器。 servlet需要使用tomcat服务器,websocket容器也需要使用该服务器。

随着HTML5中WebSocket的推出,真正实现了Web上的实时通信,使B/S模式具备了C/S模式的实时通信能力。 WebSocket的工作流程如下:浏览器通过JavaScript向服务器发送请求建立WebSocket连接。 WebSocket连接成功建立后,客户端和服务器端就可以通过TCP连接传输数据了。由于WebSocket连接本质上是TCP连接,每次传输不需要携带重复的头数据,因此其数据传输量比轮询和Comet技术小得多。

JSR-356:JavaEE 7 中发布了 Java API for WebSocket 规范。许多 Web 容器,例如 Tomcat、Nginx、Jetty 等都支持 WebSocket。 Tomcat 从 7.0.27 开始支持 WebSocket,从 7.0.47 开始支持 JSR-356。因此,websocket容器需要部署在Tomcat 7.0.47以上版本上才能运行。

Java实现websocket容器:

  1. 创建一个Maven项目:

在这里插入图片描述

  1. 导入websocket依赖
    <dependency>
      <groupId>javax.websocket</groupId>
      <artifactId>javax.websocket-api</artifactId>
      <version>1.1</version>
      <scope>provided</scope>
    </dependency>
  1. 网络套接字容器
package com.example;

import java.io.IOException;
import java.util.logging.Logger;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;



/** * @Class: Test * @Description: 简单websocket demo */
@ServerEndpoint(value="/websocketTest/{userId}")
public class WsTest {
    

    private Logger logger = Logger.getLogger("WebSocket");

    private static String userId;

    //连接时执行
    @OnOpen
    public void onOpen(@PathParam("userId") String userId,Session session) throws IOException{
    
        this.userId = userId;
        logger.info("有新的链接!");
        System.out.println("新连接:"+userId);
    }

    //关闭时执行
    @OnClose
    public void onClose(){
    

        logger.info("有链接关闭!");

        System.out.println("连接:"+this.userId);
    }

    //收到消息时执行
    @OnMessage
    public void onMessage(String message, Session session) throws IOException {
    
        System.out.println("收到用户"+this.userId+"的消息"+message);
        session.getBasicRemote().sendText("收到 "+this.userId+" 的消息 "); //回复用户
    }

    //连接错误时执行
    @OnError
    public void onError(Session session, Throwable error){
    
        System.out.println("用户id为:"+this.userId+"的连接发送错误");
        error.printStackTrace();
    }


}

Endpoint的生命周期

Tomcat 从 7.0.5 版本开始支持 WebSocket,并实现了 Java WebSocket 规范(JSR356)。在7.0.5版本之前(7.0.2版本之后),使用的是自定义API,即WebSocketServlet。根据JSR356的规定,Java WebSocket应用程序由一系列WebSocket Endpoints组成。 Endpoint 是一个 Java 对象,代表 WebSocket 链接的一端。对于服务端来说,我们可以把它看成是一个处理特定WebSocket消息的接口,就像Servlet处理HTTP请求一样(区别在于每个链接都有一个Endpoint实例)。

我们可以通过两种方式定义Endpoint,第一种是编程式,即继承类javax.websocket.Endpoint并实现其方法。第二种是注解式,即定义一个POJO对象,为其添加Endpoint相关的注解。

Endpoint 实例在 WebSocket 握手期间创建,在客户端与服务器连接期间有效,在连接关闭时结束。 Endpoint接口明确定义了与其生命周期相关的方法,规范的实现者确保在生命周期的每个阶段都调用实例的相关方法。

Endpoint的生命周期方法如下

  • onOpen:打开新会话时调用。这是客户端和服务器成功握手后调用的方法。相当于注解@OnOpen。
  • onClose:会话关闭时调用。相当于注解@OnClose。
  • onError:链接过程中发生异常时调用。相当于@OnError注释。
  1. 当客户端链接到一个Endpoint时,服务器端会为其创建一个唯一的会话(javax.websocket.Session)。会话在WebSocket握手之后创建,并在链接关闭时结束。当生命周期中触发各个事件时,都会将当前会话传给Endpoint。
  2. 通过为Session添加MessageHandler消息处理器来接收消息。当采用注解方式定义Endpoint时,我们还可以通过@OnMessage指定接收消息的方法。发送消息则由RemoteEndpoint完成,其实例由Session维护,根据使用情况,我们可以通过Session.getBasicRemote获取同步消息发送的实例或者通过Session.getAsyncRemote获取异步消息发送的实例。
  3. WebSocket通过javax.websocket.WebSocketContainer接口维护应用中定义的所有Endpoint。它在每个Web应用中只有一个实例,类似于传统Web应用中的ServletContext。

WebSocket加载与处理

通过websocket案例可以获取到一个websocket容器,那么tomcat是如何加载这个容器的呢?

Tomcat提供了一个javax.servlet.ServletContainerInitializer的实现类org.apache.tomcat.websocket.server.WsSci。因此Tomcat的WebSocket加载是通过SCI机制完成的。WsSci可以处理的类型有三种:添加了注解@ServerEndpoint的类、Endpoint的子类以及ServerApplicationConfig的实现类。

当Web应用程序启动时,WebSocket的初始化是通过WsSci.onStartup方法完成的:

  • 构造WebSocketContainer实例,Tomcat提供的实现类为WsServerContainer。在WsServerContainer构造方法中,Tomcat除了初始化配置外,还会为ServletContext添加一个过滤器org.apache.tomcat.websocket.server.WsFilter,它用于判断当前请求是否为WebSocket请求,以便完成握手。
  • 对于扫描到的Endpoint子类和添加了注解@ServerEndpoint的类,如果当前应用存在ServerApplicationConfig实现,则通过ServerApplicationConfig获取Endpoint子类的配置(ServerEndpointConfig实例,包含了请求路径等信息)和符合条件的注解类,将结果注册到WebSocketContainer上,用于处理WebSocket请求。
  • 通过ServerApplicationConfig接口我们以编程的方式确定只有符合一定规则的Endpoint可以注册到WebSocketContainer,而非所有。规范通过这种方式为我们提供了一种定制化机制。
  • 如果当前应用程序没有定义ServerApplicationConfig的实现类,那么WsSci默认只会将所有扫描到的带注释的Endpoint注册到WebSocketContainer中。因此,如果您以编程方式定义 Endpoint,则必须添加 ServerApplicationConfig 实现。

在这里插入图片描述

如上图所示,当服务器收到客户端的请求时,WsFilter首先判断该请求是否为WebSocket Upgrade请求(即包含Upgrade: websocket头信息)。如果有,则根据请求路径查找对应的Endpoint处理类,并进行协议升级。

在协议Upgrade过程中,除了检测WebSocket扩展、添加相关的转换外,最主要的是添加WebSocket相关的响应头信息、构造Endpoint实例、构造HTTP Upgrade处理类WsHttpUpgradeHandler

将 WsHttpUpgradeHandler 传递给特定的 Tomcat 协议处理程序(ProtocolHandler)进行升级。收到Upgrade动作后,Tomcat的协议处理器(HTTP协议)不再使用原来的Processor来处理请求,而是换成专用的Upgrade Processor。

根据I/O的不同,Tomcat提供的Upgrade Processor实现如下:

  • org.apache.coyote.http11.upgrade.BioProcessor;
  • org.apache.coyote.http11.upgrade.NioProcessor;
  • org.apache.coyote.http11.upgrade.Nio2Processor;
  • org.apache.coyote.http11.upgrade.AprProcessor;

替换成功后,WsHttpUpgradeHandler会初始化Upgrade Processor(顺序如下):

  1. 创建 WebSocket 会话。
  2. 为升级处理器的输出流添加写入侦听器。 WebSocket专门通过org.apache.tomcat.websocket.server.WsRemoteEndpointImplServer将消息推送到客户端。
  • 构造一个WebSocket会话并执行当前Endpoint的onOpen方法。
  • 在Upgrade Processor的输入流中添加一个读监听器来完成消息的读取。 WebSocket对客户端消息的读取是由org.apache.tomcat.websocket.server.WsFrameServer具体完成的。

这样,Tomcat就实现了WebSocket请求处理与具体I/O方式的解耦。

整个websocket处理案例如下:

websocket后台容器:

package com.example;



import java.io.IOException;
import java.util.logging.Logger;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;



/** * @Class: Test * @Description: 简单websocket demo */

//必须要添加该注解定义实现类
@ServerEndpoint(value="/websocketTest/{userId}")
public class WsTest {
    

    private Logger logger = Logger.getLogger("WebSocket");

    private static String userId;

    //连接时执行
    @OnOpen
    public void onOpen(@PathParam("userId") String userId,Session session) throws IOException{
    
        this.userId = userId;
        logger.info("有新的链接!");
        System.out.println("新连接:"+userId);
    }

    //关闭时执行
    @OnClose
    public void onClose(){
    

        logger.info("有链接关闭!");

        System.out.println("连接:"+this.userId);
    }

    //收到消息时执行
    @OnMessage
    public void onMessage(String message, Session session) throws IOException {
    
        System.out.println("收到用户"+this.userId+"的消息"+message);
        session.getBasicRemote().sendText("收到 "+this.userId+" 的消息 "); //回复用户
    }

    //连接错误时执行
    @OnError
    public void onError(Session session, Throwable error){
    
        System.out.println("用户id为:"+this.userId+"的连接发送错误");
        error.printStackTrace();
    }


}

注意背景,不要忘记反映maven依赖关系。

前端链接及交互代码:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
websocket Demo---- user000 <br />
<input id="text" type="text" />
<button onclick="send()"> Send </button>
<button onclick="closeWebSocket()"> Close </button>
<div id="message">   </div>

<script type="text/javascript"> //判断当前浏览器是否支持WebSocket if('WebSocket' in window){
       websocket = new WebSocket("ws://localhost:8080/demo/websocketTest/user00"); console.log("link success") }else{
       alert('Not support websocket') } //连接发生错误的回调方法 websocket.onerror = function(){
       setMessageInnerHTML("error"); }; //连接成功建立的回调方法 websocket.onopen = function(event){
       setMessageInnerHTML("open"); } console.log("-----") //接收到消息的回调方法 websocket.onmessage = function(event){
       setMessageInnerHTML(event.data); } //连接关闭的回调方法 websocket.onclose = function(){
       setMessageInnerHTML("close"); } //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。 window.onbeforeunload = function(){
       websocket.close(); } //将消息显示在网页上 function setMessageInnerHTML(innerHTML){
       document.getElementById('message').innerHTML += innerHTML + '<br/>'; } //关闭连接 function closeWebSocket(){
       websocket.close(); } //发送消息 function send(){
       var message = document.getElementById('text').value; websocket.send(message); } </script>

</body>
</html>

这种情况的基本实现是,客户端向服务器发送任意消息,服务器可以接收到该消息,然后返回固定的消息。

在这里插入图片描述
也可以验证发送的请求为websocket:

在这里插入图片描述

参考文章包括以下文章,非常感谢作者!

万字长文,彻底理解WebSocket:概念、原理、易错常识、上手实践

新手快速入门:WebSocket 简要教程

websocket三:Tomcat的WebSocket实现

java WebSocket开发简介WebSocket

. . .

相关推荐

额外说明

RocketMQ与Kafka的区别对比

Broker差异 主从差异: kafka的master/slave是基于partition维度的,而rocketmq是基于broker维度的;kafka的master/slave是可以切换的,而rocketmq不行,当rocketmq的master宕机时

额外说明

使用cpolar配置内网访问(内网穿透)教程(超详细简单)

目录 一、什么是cpolar? 二、它能用在哪些场景?   三、一些很棒的功能 四、如何使用? 1、下载cpolar软件安装包  2、注册cpolar账号 3、 登录账号,并拿到隧道 Authtoken 4、启动 cpolar 5、配置 Authtoke

额外说明

Photoshop2021安装教程

Photoshop主要处理以像素所构成的数字图像。使用其众多的编修与绘图工具,可以有效地进行图片编辑工作。ps有很多功能,在图像、图形、文字、视频、出版等各方面都有涉及。 目录 一、下载地址 二、安装步骤  一、下载地址 下载地址:Photoshop 软

额外说明

Apollo 配置中心:分布式部署

Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性,适用于微服务配置管理场景。 服务端架构       上图简要描述了 Apollo 的

额外说明

TIDB与MYSQL兼容性测试

1.建表插入导入兼容性 测试场景: 将项目的生产数据库备份导入到TIDB测试环境。 测试结果: 有少量建表语句不兼容,建表语句需要调整,对于某些时间字段,例如 updateTime timestamp NOT NULL ON UPDATE CURRENT

额外说明

强化学习中Ornstein-Uhlenbeck噪声什么使用最合理以及效果如何

读过 DDPG 论文 [1] 的同学几乎都会有一个问题,论文中使用 Ornstein-Uhlenbeck 噪声用于探索,比较难理解,参数又多,真的会比简单的高斯噪声等更有效吗? 由于大部分回答都没有说清楚甚至**完全相反地解释**该部分,本文会尝试从噪声

额外说明

Spring Boot中的常用注解

Spring Boot中的常用注解 深入探讨Spring Boot中的常用注解 摘要 引言 一、认识Spring Boot Spring Boot简介和核心概念 核心概念详解 1. 自动配置 2. 起步依赖 3. 嵌入式Web服务器 二、Spring中常

额外说明

hadoop 自定义分区

分区概念 分区这个词对很多同学来说并不陌生,比如Java很多中间件中,像kafka的分区,mysql的分区表等,分区存在的意义在于将数据按照业务规则进行合理的划分,方便后续对各个分区数据高效处理 Hadoop分区 hadoop中的分区,是把不同数据输出到

额外说明

第2章[2.1] 开发模式及快速测试方式

Ext JS 开发模式的分类及演进 Ext JS开发模式的演进历程基本就代表了Web开发模式演进的大致方向,即: 早期的Ext JS类似于JQuery, 是一个前端的组件库, 开发方式是导入一个JS的文件和一个CSS的样式文件。随着前端框架的发展,Ext

额外说明

IDEA初始化常用配置(持续更新)

激活 setting->Plugins->设置轮子->Manage Plugins Repositories->添加仓库 https://plugins.zhile.io ->搜索插件 IDE Eval Reset 代码提示快捷键设置为 alt+, se

ads via 小工具