参考资料来源:https://blog.csdn.net/elinespace/article/details/52879839

前言:

WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。在 WebSocket API 中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。

WebSocket优点:

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

前端与后端数据传输过程:

浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。当你获取 Web Socket 连接后,你可以通过 send() 方法来向服务器发送数据,并通过 onmessage 事件来接收服务器返回的数据。以下 API 用于创建 WebSocket 对象。

var Socket = new WebSocket(url, [protocol] );

WebSocket技术细节

  • 根据JSR356的规定,Java WebSocket应用由一系列的WebSocket Endpoint组成。Endpoint是一个Java对象,代表WebSocket链接的一端,对于服务端,我们可以视为处理具体WebSocket消息的接口,就像Servlet之于HTTP请求一样(不同之处在于Endpoint每个链接一个实例)。
  • Endpoint实例在WebSocket握手时创建,并在客户端与服务端链接过程中有效,最后在链接关闭时结束。Endpoint接口明确定义了与其生命周期相关的方法,规范实现者确保在生命周期的各个阶段调用实例的相关方法。
  • Endpoint的生命周期方法如下:
  1. onOpen:当开启一个新的会话时调用。这是客户端与服务器握手成功后调用的方法。等同于注解@OnOpen。
  2. onClose:当会话关闭时调用。等同于注解@OnClose。
  3. onError:当链接过程中异常时调用。等同于注解@OnError。
  • 当客户端链接到一个Endpoint时,服务器端会为其创建一个唯一的会话(javax.websocket.Session)。会话在WebSocket握手之后创建,并在链接关闭时结束。当生命周期中触发各个事件时,都会将当前会话传给Endpoint。
  • 我们通过为Session添加MessageHandler消息处理器来接收消息。当采用注解方式定义Endpoint时,我们还可以通过@OnMessage指定接收消息的方法。发送消息则由RemoteEndpoint完成,其实例由Session维护,根据使用情况,我们可以通过Session.getBasicRemote获取同步消息发送的实例或者通过Session.getAsyncRemote获取异步消息发送的实例。
  • WebSocket通过javax.websocket.WebSocketContainer接口维护应用中定义的所有Endpoint。它在每个Web应用中只有一个实例,类似于传统Web应用中的ServletContext。

WebSocket加载

  • 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实现。

websocket请求处理

  • 当服务器接收到来自客户端的请求时,首先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实现如下:
  1. org.apache.coyote.http11.upgrade.BioProcessor;
  2. org.apache.coyote.http11.upgrade.NioProcessor;
  3. org.apache.coyote.http11.upgrade.Nio2Processor;
  4. org.apache.coyote.http11.upgrade.AprProcessor;
  • 替换成功后,WsHttpUpgradeHandler会对Upgrade Processor进行初始化(按以下顺序):
  1. 创建WebSocket会话。
  2. 为Upgrade Processor的输出流添加写监听器。WebSocket向客户端推送消息具体由org.apache.tomcat.websocket.server.WsRemoteEndpointImplServer完成。
  3. 构造WebSocket会话,执行当前Endpoint的onOpen方法。
  4. 为Upgrade Processor的输入流添加读监听器,完成消息读取。WebSocket读取客户端消息具体由org.apache.tomcat.websocket.server.WsFrameServer完成。

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

1、编程实现——基于Endpoint子类+ServerApplicationConfig类

Endpoint子类

public class ChatEndpoint extends Endpoint {private static final Set<ChatEndpoint> connections = new CopyOnWriteArraySet<>();private Session session;private static class ChatMessageHandler implementsMessageHandler.Partial<String> {private Session session;private ChatMessageHandler(Session session){this.session = session;}@Overridepublic void onMessage(String message, boolean last) {String msg = String.format("%s %s %s", session.getId(), "said:" ,message);broadcast(msg);}};@Overridepublic void onOpen(Session session, EndpointConfig config) {this.session = session;connections.add(this);this.session.addMessageHandler(new ChatMessageHandler(session));String message = String.format("%s %s", session.getId(), "has joined.");broadcast(message);}@Overridepublic void onClose(Session session, CloseReason closeReason) {connections.remove(this);String message = String.format("%s %s", session.getId(),"has disconnected.");broadcast(message);}@Overridepublic void onError(Session session, Throwable throwable) {}private static void broadcast(String msg) {for (ChatEndpoint client : connections) {try {synchronized (client) {client.session.getBasicRemote().sendText(msg);}} catch (IOException e) {connections.remove(client);try {client.session.close();} catch (IOException e1) {}String message = String.format("%s %s",client.session.getId(), "has been disconnected.");broadcast(message);}}}
}

ServerApplicationConfig类

public class ChatServerApplicationConfig implements ServerApplicationConfig {
//负责处理注解类型    @Overridepublic Set<Class<?>> getAnnotatedEndpointClasses(Set<Class<?>> scanned) {return scanned;}
//负责处理Enpoint类型@Overridepublic Set<ServerEndpointConfig> getEndpointConfigs(Set<Class<? extends Endpoint>> scanned) {Set<ServerEndpointConfig> result = new HashSet<>();if (scanned.contains(ChatEndpoint.class)) {result.add(ServerEndpointConfig.Builder.create(ChatEndpoint.class,"/program/chat").build());}return result;}
}

2、编程实现——基于注解@ServerEndpoint(value = "访问路径")

@ServerEndpoint(value = "/anno/chat")
public class ChatAnnotation {private static final Set<ChatAnnotation> connections =new CopyOnWriteArraySet<>();private Session session;@OnOpenpublic void start(Session session) {this.session = session;connections.add(this);String message = String.format("%s %s", session.getId(), "has joined.");broadcast(message);}@OnClosepublic void end() {connections.remove(this);String message = String.format("%s %s", session.getId(), "has disconnected.");broadcast(message);}@OnMessage
public void incoming(String message) {String msg = String.format("%s %s %s", session.getId(), "said:" ,message);broadcast(msg);}@OnErrorpublic void onError(Throwable t) throws Throwable {}private static void broadcast(String msg) {for (ChatAnnotation client : connections) {try {synchronized (client) {client.session.getBasicRemote().sendText(msg);}} catch (IOException e) {connections.remove(client);try {client.session.close();} catch (IOException e1) {}String message = String.format("%s %s",client.session.getId(), "has been disconnected.");broadcast(message);}}}
}
  1. @ServerEndpoint注解声明该类是一个Endpoint,并指定了请求的地址。
  2. @OnOpen注解的方法在会话打开时调用,与ChatEndpoint类似,将当前实例添加到链接池。
  3. @OnClose注解的方法在会话关闭时调用。
  4. @OnError注解的方法在链接异常时调用。
  5. @OnMessage注解的方法用于接收消息。

使用注解方式定义Endpoint时,ServerApplicationConfig不是必须的,此时直接默认加载所有的@ServerEndpoin注解POJO。

基于注解方式实现在Tomcat8中实现WebSocket服务器接口的编写相关推荐

  1. Elasticsearch-mapper 基于注解方式生成mapping(2.0以上)

    Elasticsearch生成mapping的方式上有多种方式,我们可以把mapping做成配置文件,也可以用spring-data-elasticsearch基于注解生成. 在基于注解生成这种方式上 ...

  2. spring IOC容器 Bean 管理——基于注解方式

    IOC 操作 Bean 管理(基于注解方式) 1.什么是注解 ​ (1)注解是代码特殊标记,格式:@注解名称(属性名称=属性值, 属性名称=属性值-) ​ (2)使用注解,注解作用在类上面,方法上面, ...

  3. java datasource 配置_Spring boot 基于注解方式配置datasource

    Spring boot 基于注解方式配置datasource Xml配置 我们先来回顾下,使用xml配置数据源. 步骤: 先加载数据库相关配置文件; 配置数据源; 配置sqlSessionFactor ...

  4. spring的依赖注入 -------基于注解方式

    前言: 做了2年的软件,刚开始入行的时候,没有个目标基本上都是在摸索,技术看的我眼花缭乱,这个想学,那个也想学结果是对很多技术一知半解的,工作中才发现,我们要掌握的一门可以搞定快速开发搞定所有业务需求 ...

  5. XShell+Xmanager实现在XShell中显示远程服务器的图形界面

    最近开始学习机器学习的知识,迫切的需要用到Linux环境,但是因为虚拟机用着电脑会变卡,而且自己有台式+笔记本,所以自己想要创建一个随时随地多设备可以用的实验环境.因此想到了搭建一个Linux系统的远 ...

  6. 基于注解方式@AspectJ的AOP

    启用对@AspectJ的支持 Spring默认不支持@AspectJ风格的切面声明,为了支持需要使用如下配置: <aop:aspectj-autoproxy/> 这样Spring就能发现@ ...

  7. Spring Boot基于注解方式处理接口数据脱敏

    1.定义注解 创建Spring Boot项目添加以下依赖 <dependencies><dependency><groupId>org.springframewor ...

  8. SpringMVC通过注解方式读取properties文件中的值

    为什么80%的码农都做不了架构师?>>>    本方法是结合Java配置及XML配置来完成. 首先定义XML配置文件 app.xml: <?xml version=" ...

  9. 【基于注解方式】Spring整合Kafka

    文章目录 1. 添加Maven依赖 2. 配置与参数分离 3. 工具类度内容 4. Producer 消息生产者配置 5. Consumer 消息消费者配置 6. 使用注解监听消息 7. 请求测试 8 ...

最新文章

  1. CE5.0 - eboot汇编Startup.s中MMU设置流程详细分析
  2. python字符串无效的原因_python字符串问题
  3. PHP使用GD库封装验证码类
  4. ASP.NET 使用 X509Certificate2 系统找不到指定的文件
  5. cacti不能实时刷新流量图_介绍一种编码帧内刷新算法
  6. 多少秒算长镜头_下中国象棋,能算多少步才算高手?
  7. 动手学servlet(四) cookie和session
  8. C#遍历DataSet数据的几种方法总结
  9. Linux Mysql5.6安装
  10. vue.js开发微信公众号加载缓慢出现的白页问题-随笔
  11. 用VMware安装Windows 8.x虚拟机镜像系统详细流程
  12. 使用MS Word设计和打印自己的圣诞贺卡,第1部分
  13. DataFun: 微信NLP算法微服务治理
  14. 太赞了!2021最新Android开发者学习路线,offer拿到手软
  15. 微博html5版打不开,PC端网页版微博就是打不开是什么问题啊!缓 – 手机爱问
  16. php pdf数字签名,用PHP从PDF中检索数字签名信息
  17. Linux学习:Linux启动管理器GRUB2
  18. 科学计算机 百分号,普通计算器上百分号(%)有什么用?
  19. 小强怎样练成——读《现代软件工程——构建之法》第三章有感
  20. Ubuntu下C语言程序编写与运行

热门文章

  1. 计算机网络在音乐软件的应用,软网推荐:简单音乐软件 不凡播放管理
  2. 8 个适合程序员学习新技能的网站
  3. 基于H5和C3的静态电商网站
  4. 满屏飞舞的心HTML动画,使用snowfall.jquery.js实现爱心满屏飞的效果
  5. 光芯片-汽车-自动驾驶-新能源分析
  6. 实在智能RPA机器人:你相信光吗
  7. 产品分享:Qt+Arm基于RV1126平台的内窥镜软硬整套解决方案(实时影像、冻结、拍照、录像、背光调整、硬件光源调整,其他产品也可使用该平台,如视频监控,物联网产品等等)
  8. 抚顺C语言培训,c语言程序设计
  9. 嵌入式单片机高级篇(三)Stm32F103+OV2640摄像头
  10. tpcc-mysql 下载_tpcc-mysql 实践