WebSocket新一代推送技术及Java Web实现
创始人
2024-04-27 18:19:23
0

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的兼容性:

在这里插入图片描述

由上图可知:目前主流的 Web 浏览器都支持 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 somethingbreak;case WebSocket.OPEN:// do somethingbreak;case WebSocket.CLOSING:// do somethingbreak;case WebSocket.CLOSED:// do somethingbreak;default:// this never happensbreak;
}//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非常类似,都是处理前端请求的容器,其本身不能单独存在需要借web服务器。servlet需要借助tomcat服务器,websocket容器也需要借助服务器。

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

JavaEE 7中出了JSR-356:Java API for WebSocket规范。不少Web容器,如Tomcat,Nginx,Jetty等都支持WebSocket。Tomcat从7.0.27开始支持 WebSocket,从7.0.47开始支持JSR-356,因此websocket容器需要部署在Tomcat7.0.47以上的版本才能运行。

Java实现websocket容器:

  1. 创建Maven项目:

在这里插入图片描述

  1. 导入websocket依赖
    javax.websocketjavax.websocket-api1.1provided
  1. 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;//连接时执行@OnOpenpublic void onOpen(@PathParam("userId") String userId,Session session) throws IOException{this.userId = userId;logger.info("有新的链接!");System.out.println("新连接:"+userId);}//关闭时执行@OnClosepublic void onClose(){logger.info("有链接关闭!");System.out.println("连接:"+this.userId);}//收到消息时执行@OnMessagepublic void onMessage(String message, Session session) throws IOException {System.out.println("收到用户"+this.userId+"的消息"+message);session.getBasicRemote().sendText("收到 "+this.userId+" 的消息 "); //回复用户}//连接错误时执行@OnErrorpublic 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 Endpoint组成。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应用启动时,通过WsSci.onStartup方法完成WebSocket的初始化:

  • 构造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。

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

将WsHttpUpgradeHandler传递给具体的Tomcat协议处理器(ProtocolHandler)进行Upgrade。接收到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. 为Upgrade Processor的输出流添加写监听器。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;//连接时执行@OnOpenpublic void onOpen(@PathParam("userId") String userId,Session session) throws IOException{this.userId = userId;logger.info("有新的链接!");System.out.println("新连接:"+userId);}//关闭时执行@OnClosepublic void onClose(){logger.info("有链接关闭!");System.out.println("连接:"+this.userId);}//收到消息时执行@OnMessagepublic void onMessage(String message, Session session) throws IOException {System.out.println("收到用户"+this.userId+"的消息"+message);session.getBasicRemote().sendText("收到 "+this.userId+" 的消息 "); //回复用户}//连接错误时执行@OnErrorpublic void onError(Session session, Throwable error){System.out.println("用户id为:"+this.userId+"的连接发送错误");error.printStackTrace();}}

注意后台不要忘了映入maven依赖。

前端链接与交互代码:






websocket Demo---- user000 

这个案例的基本实现是客户端向服务端发送任意消息,服务端能接受到该消息,然后返回一条固定信息。

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

在这里插入图片描述

参考文章有以下文章,感谢作者大大!

万字长文,一篇吃透WebSocket:概念、原理、易错常识、动手实践

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

websocket之三:Tomcat的WebSocket实现

java WebSocket开发入门WebSocket

相关内容

热门资讯

los系统和安卓系统的区别,两... 你有没有想过,为什么你的手机有时候运行得那么顺畅,有时候又卡得像蜗牛呢?这背后其实隐藏着两个大玩家—...
安卓系统可以安装cad软件,安... 你有没有想过,在安卓手机上也能轻松安装CAD软件呢?没错,就是那个专业的设计软件,以前只能在电脑上操...
车载ce系统与安卓系统的区别,... 你有没有想过,为什么你的车载系统有时候那么不智能,而安卓手机却总能给你带来惊喜?今天,就让我带你深入...
苹果6s系统换安卓系统,体验安... 你有没有想过,把你的苹果6s换成安卓系统呢?想象那流畅的触控体验,加上安卓那丰富的应用和可定制的界面...
安卓转移ios健康系统,探索健... 你有没有想过,从安卓手机转到iOS设备后,那些积累的健康数据怎么办呢?别急,今天就来给你详细解析如何...
安卓系统如何换微信号,教你如何... 你是不是也和我一样,对安卓系统换微信号这个话题感兴趣呢?毕竟,谁不想偶尔换个心情,换个昵称呢?好啦,...
安卓机清理系统内存,提升手机运... 手机用久了是不是感觉越来越卡?别急,今天就来教你怎么给安卓机清理系统内存,让你的手机焕发新生!一、内...
安卓子系统要求CPU,安卓子系... 你知道吗?最近在安卓系统圈子里,有个话题可是热得不得了,那就是安卓子系统对CPU的要求。这可不是小事...
安卓系统排名第几,引领智能时代... 你知道吗?在智能手机的世界里,有一个系统可是当之无愧的“王者”——那就是安卓系统!今天,就让我带你一...
阿里云是不是安卓系统,引领安卓... 最近是不是有很多小伙伴在问:“阿里云是不是安卓系统?”这个问题可真是让人好奇啊!咱们就来好好探讨揭开...
安卓系统音量调节的文件,安卓系... 你有没有遇到过这种情况:手机音量调得刚刚好,突然间就变得忽高忽低,让人听得心烦意乱?别急,今天就来跟...
平板刷安卓10原生系统,平板新... 你有没有想过,你的平板电脑也能拥有安卓10的原生系统呢?没错,就是那个流畅又强大的系统,现在它也能在...
安卓系统怎么设定位手机,安卓系... 你有没有想过,你的安卓手机是怎么知道你在哪儿的呢?没错,就是定位功能!这可是现代智能手机的一大亮点,...
升级的安卓系统怎样降级,安卓系... 你有没有遇到过这种情况?手机里的安卓系统突然升级了,结果发现新系统有点小bug,或者某些功能变得不那...
安卓刷机怎么升级系统,轻松实现... 你有没有发现,你的安卓手机最近有点儿慢吞吞的,是不是也想给它来个“大变身”,让它焕发新生呢?没错,刷...
安卓系统迷你小音响,便携式音乐... 你有没有想过,在忙碌的生活中,给自己一个小小的音乐角落,让心情随着音符跳动呢?今天,就让我带你走进一...
老安卓系统怎么删除页面,老安卓... 你有没有发现,手机里的安卓系统用久了,页面上的应用图标就像小山一样堆得高高的?有时候,看着这些图标,...
安卓手机死屏重置系统,轻松解决... 手机突然死屏了,是不是心里一紧?别慌,今天就来跟你聊聊安卓手机死屏后如何重置系统,让你轻松解决这个小...
安卓系统高怎么运行,解锁流畅体... 手机里的安卓系统突然变得卡顿起来,是不是让你感觉像是在迷宫里找出口?别急,今天就来给你支几招,让你的...
安卓系统新消息弹屏,体验升级 你知道吗?最近安卓系统又来了一大波新消息,这可真是让人兴奋不已!想象当你正在专心致志地刷着手机,突然...