概述
本文介绍了如何根据Tomcat给出的websocket实例,通过对实例的学习,定制自己基于websocket的应用。
环境及版本:
- Ubuntu 22.04.4 LTS
- Apache Tomcat/10.1.20
- openjdk 11.0.23 2024-04-16
- 浏览器:Chrome
相关资源及链接
Class Loader How-To:
Apache Tomcat 11 (11.0.0-M20) - Class Loader How-To
推荐几篇本站内介绍Websocket原理及tomcat附带的实例的文章,可作为参考:
Websocket原理-CSDN博客
看完让你彻底理解 WebSocket 原理_websocket原理-CSDN博客
Tomcat实现Web Socket_tomcat 9 wss服务配置-CSDN博客
Tomcat提供的websocket实例
Tomcat安装完成后给出的Examples中,包括了有关websocket的例子。
如上图,点击‘Examples’,进入如下界面:
继续点击‘WebSocket Examples’,进入如下界面:
点击‘Echo example’,进入如下界面:
从界面可以看出,Tomcat提供以下三种方式与服务器建立websocket双向通信:
- programmatic API
- annotation API (basic)
- annotation API (stream)
programmatic:编程式,即编写一个Java类继承javax.websocket.Endpoint(根据tomcat及openjdk的版本不同,或继承jakarta.websocket.Endpoint,本文中为jakarta),并实现它的onOpen、onClose和onError等方法。
annotation:注解式,实现一个业务类并给其添加websocket相关的注解(通过@ServerEndpoint(...)),注解表明当前业务类是已经实现了WebSocket规范的Endpoint。根据上面tomcat给出的实例界面显示,注解式又分为basic和stream两种模式。
本文不对上述三种方式展开详细讨论。
点击上面(tomcat)界面的三种websocket的实现方式,下方的编辑框中会同步显示将实际在代码中用到的websocket URL,例如点击‘annotation API (basic)’,下面编辑框的内容同步更新为‘ws://host/examples/websocket/echoAnnotation’,其中host为服务器的URL(含端口),以下均使用‘127.0.0.1:8080’作为默认值,例如:
ws://127.0.0.1:8080/examples/websocket/echoAnnotation
客户端浏览器将使用该URL串作为目标websocket服务器地址。
点击‘Connect’按钮,再点击‘Echo message’按钮,界面如下:
依葫芦画瓢
now,我们照着tomcat给出的实例依葫芦画瓢建立自己的websocket应用,并试图在这一过程中逐步理解tomcat的websocket实现原理以及相关的配置。
新建一个自己的webapp,例如命名为myws:
- 在目录‘opt/tomcat/webapps’新建目录‘myws’;
- 将examples实例下‘websocket’目录及其文件拷贝到‘myws’目录下;
- 在‘myws’目录下新建目录‘WEB-INF’;进入新建的‘WEB-INF’目录,继续创建目录‘classes’,此目录为本文涉及的tomcat 11加载Java类的默认目录!
- 将examples实例下的‘WEB-INF/classes/websocket’目录及其文件拷贝到myws应用下新建的‘classes’目录。
依葫芦画瓢(文件拷贝)暂时到此。
在新建的myws应用根目录下新建一个index.html文件,内容如下:
<html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>项目测试首页列表</title> <style> body { background-color: lightblue; } h1 { text-align: center; } div.exam_list { font-family: verdana; font-size: 18px; margin-left: 100px; margin-top:5px; } </style> </head> <body> <h1>tomcat websocket应用学习</h1> <p> <div class="exam_list"><li><a href="/myws/websocket/index.xhtml">examples实例学习</a></li></div> </p> </body> </html> |
启动浏览器,输入URL,例如:http://hostname/myws,出现如下界面:
点击页面链接,进入如下界面(此界面与之前的完全相同):
点击‘Echo example’,进入如下界面(此界面与之前的完全相同):
分别点击tomcat下websocket的三种通信实现方式,下方编辑框的链接URI为:
- ws://127.0.0.1:8080/examples/websocket/echoProgrammatic
- ws://127.0.0.1:8080/examples/websocket/echoAnnotation
- ws://127.0.0.1:8080/examples/websocket/echoStreamAnnotation
点击页面其他按钮,并操作,一切正常,注意观察三个URL,其仍然连接的是安装包默认提供的examples项目上了,故一切正常。
打开并编辑文件‘echo.xhtml’,注意如下代码行:
<div> <div id="connect-container"> <div> <span>Connect to service implemented using:</span> <br/> <!-- echo example using new programmatic API on the server side --> <input id="radio1" type="radio" name="group1" value="/examples/websocket/echoProgrammatic" onclick="updateTarget(this.value);"/> <label for="radio1">programmatic API</label> <br/> <!-- echo example using new annotation API on the server side --> <input id="radio2" type="radio" name="group1" value="/examples/websocket/echoAnnotation" onclick="updateTarget(this.value);"/> <label for="radio2">annotation API (basic)</label> <br/> <!-- echo example using new annotation API on the server side --> <input id="radio3" type="radio" name="group1" value="/examples/websocket/echoStreamAnnotation" onclick="updateTarget(this.value);"/> <label for="radio3">annotation API (stream)</label> <br/> <!-- echo example using new annotation API on the server side --> <!-- Disabled by default --> <!-- <input id="radio4" type="radio" name="group1" value="/examples/websocket/echoAsyncAnnotation" οnclick="updateTarget(this.value);"/> <label for="radio4">annotation API (async)</label> --> </div> |
根据代码,如前所述,每当用户点击了不同的通信方式,页面会自动更新websocket连接,其中实例代码还注释掉了第四种方式‘AsynAnntation’。
将上述代码中高亮的‘examples’替换为本项目名称‘myws’。刷新页面,点击选择不同的通信方式,确认编辑框中websocket连接URL更新。
回到页面进行操作,OK!一切正常!!!
温馨提示(重要的问题说三遍),在测试页面之前务必通过tomcat的管理页面重新启动web应用,界面如下:
点击‘停止’按钮,再点击‘启动’按钮。
不知道如何配置管理页面的,可直接重启tomcat服务。
重要的事情说三遍!!!一定记得重启应用!
tomcat三种websocket通信方式测试
一个小测试:
查看目录‘myws/WEB-INF/classes/websocket’,除了四个子目录,注意该目录下有两个文件,一个是‘ExamplesConfig.java’,另一个是对应的class文件。
从项目目录中删除该两个文件,重新启动web应用,再次进入Echo example界面,同样进行三种方式的通信测试,其中后两种(基础注解式/annotation API (basic)和流式注解式/annotation API (stream))正常,第一种‘编程式’连接失败,连接失败界面如下。
查看目录‘myws/WEB-INF/classes/websocket/echo’,该目录下文件列表如下图。
再次回顾前文提到的三种通信方式的URL,如下:
- ws://127.0.0.1:8080/examples/websocket/echoProgrammatic
- ws://127.0.0.1:8080/examples/websocket/echoAnnotation
- ws://127.0.0.1:8080/examples/websocket/echoStreamAnnotation
查看文件EchoAnnotation.java,在类定义之前有一处申明,代码如下:
注解式下,通过‘@ServerEndpoint’添加注解后,在项目(网站)启动时tomcat服务会自动扫描(WEB-INF/calsses目录下)java类,并将注解类与ws服务关联。
查看文件EchoStreamAnnotation.java,同样有一处类似的申明,如下:
在EchoEndpoint.java文件中,没有发现类似的注解。
所以,在删除了文件ExampleConfig(并重新启动应用)后,注解式的方式依然有效,编程式的方式连接失败。
重新拷贝ExampleConfig.java/.class到应用目录,查看ExampleConfig.java,内容如下:
package websocket; import java.util.HashSet; import java.util.Set; import jakarta.websocket.Endpoint; import jakarta.websocket.server.ServerApplicationConfig; import jakarta.websocket.server.ServerEndpointConfig; import websocket.drawboard.DrawboardEndpoint; import websocket.echo.EchoEndpoint; public class ExamplesConfig implements ServerApplicationConfig { @Override public Set<ServerEndpointConfig> getEndpointConfigs( Set<Class<? extends Endpoint>> scanned) { Set<ServerEndpointConfig> result = new HashSet<>(); if (scanned.contains(EchoEndpoint.class)) { result.add(ServerEndpointConfig.Builder.create( EchoEndpoint.class, "/websocket/echoProgrammatic").build()); } if (scanned.contains(DrawboardEndpoint.class)) { result.add(ServerEndpointConfig.Builder.create( DrawboardEndpoint.class, "/websocket/drawboard").build()); } return result; } @Override public Set<Class<?>> getAnnotatedEndpointClasses(Set<Class<?>> scanned) { // Deploy all WebSocket endpoints defined by annotations in the examples // web application. Filter out all others to avoid issues when running // tests on Gump Set<Class<?>> results = new HashSet<>(); for (Class<?> clazz : scanned) { if (clazz.getPackage().getName().startsWith("websocket.")) { results.add(clazz); } } return results; } } |
首先类ExampleConfig继承自ServerApplicationConfig,该类会执行目录自动扫描,对于目录(及子目录)下所有继承自‘Endpoint’的类进行处理,并分别映射了两个ws服务:
- "/websocket/echoProgrammatic"
- "/websocket/drawboard"
其中第一个正是echo测试中的第一种基于编程式的通信方式所对应的服务,第二个为多人协同画板应用的实例服务(对应的注册名称)。
对于所有注解式实现的websocket服务(类),示例代码中进行了过滤操作,即任何不是以‘websocket.’开头的服务,都将被屏蔽。上面的实际测试中(删除ExamplesConfig),注解式的不需要代码中的add(clazz)操作也可以正常工作。
另外,在自己的应用中,可将配置文件/类(ExamplesConfig)更改为项目对应的名称,例如本例中更改为MywsConfig.java/class,记得类名与文件一致,重新编译.java,并重启web应用。
记录一下ubuntu下成功编译MywsConfig.java的命令(好记性不如烂笔头),主要是指定import的相关库/类的路径,如下:
javac -cp /opt/tomcat/webapps/myws/WEB-INF/classes:/opt/tomcat/lib/* MywsConfig.java
其他
Tomcat有关websocket实现的包在目录$CATALINA_HOME/lib($CATALINA_HOME的默认安装目录为‘/opt/tomcat’)下,包含三个文件,如下图:
本文未涉及注解式以及编程式websocket通信的各接口的分析,相关文章可在站内搜索。