- 使用 Spring Initializr 创建 Spring Boot 应用程序
- 在Spring Boot中配置Cassandra
- 在 Spring Boot 上配置 Tomcat 连接池
- 将Camel消息路由到嵌入WildFly的Artemis上
本文分享自华为云社区《Tomcat支持WebSocket吗?》,作者: JavaEdge 。
HTTP协议是“请求-响应”模式,浏览器必须先发请求给服务器,服务器才会响应该请求。即服务器不会主动发送数据给浏览器。
实时性要求高的应用,如在线游戏、股票实时报价和在线协同编辑等,浏览器需实时显示服务器的最新数据,因此出现Ajax和Comet技术:
但它们实时性不高,频繁请求也会给服务器巨大压力,也浪费网络流量和带宽。于是HTML5推出WebSocket标准,使得浏览器和服务器之间任一方都可主动发消息给对方,这样服务器有新数据时可主动推给浏览器。
网络上的两个程序通过一个双向链路进行通信,这个双向链路的一端称为一个Socket。一个Socket对应一个IP地址和端口号,应用程序通常通过Socket向网络发出或应答网络请求。
Socket不是协议,是对TCP/IP协议层抽象出来的API。
WebSocket跟HTTP协议一样,也是应用层协议。为兼容HTTP协议,它通过HTTP协议进行一次握手,握手后数据就直接从TCP层的Socket传输,与HTTP协议再无关。
这里的握手指应用协议层,不是TCP层,握手时,TCP连接已建立。
即HTTP请求里带有websocket的请求头,服务端回复也带有websocket的响应头。
浏览器发给服务端的请求会带上跟WebSocket有关的请求头,比如Connection: Upgrade和Upgrade: websocket
若服务器支持WebSocket,同样会在HTTP响应加上WebSocket相关的HTTP头部:
这样WebSocket连接就建立好了。
WebSocket的数据传输以frame形式传输,将一条消息分为几个frame,按先后顺序传输出去。为何这样设计?
浏览器端核心代码:
var Chat = {};
Chat.socket = null;
Chat.connect = (function(host) {
//判断当前浏览器是否支持WebSocket
if ('WebSocket' in window) {
// 若支持,则创建WebSocket JS类
Chat.socket = new WebSocket(host);
} else if ('MozWebSocket' in window) {
Chat.socket = new MozWebSocket(host);
} else {
Console.log('WebSocket is not supported by this browser.');
return;
}
// 再实现几个回调方法
// 回调函数,当和服务器的WebSocket连接建立起来后,浏览器会回调这个方法
Chat.socket.onopen = function () {
Console.log('Info: WebSocket connection opened.');
document.getElementById('chat').onkeydown = function(event) {
if (event.keyCode == 13) {
Chat.sendMessage();
}
};
};
// 回调函数,当和服务器的WebSocket连接关闭后,浏览器会回调这个方法
Chat.socket.onclose = function () {
document.getElementById('chat').onkeydown = null;
Console.log('Info: WebSocket closed.');
};
// 回调函数,当服务器有新消息发送到浏览器,浏览器会回调这个方法
Chat.socket.onmessage = function (message) {
Console.log(message.data);
};
});
服务器端Tomcat实现代码:
Tomcat端的实现类加上**@ServerEndpoint**注解,value是URL路径
@ServerEndpoint(value = "/websocket/chat")
public class ChatEndpoint {
private static final String GUEST_PREFIX = "Guest";
// 记录当前有多少个用户加入到了聊天室,它是static全局变量。为了多线程安全使用原子变量AtomicInteger
private static final AtomicInteger connectionIds = new AtomicInteger(0);
//每个用户用一个CharAnnotation实例来维护,请你注意它是一个全局的static变量,所以用到了线程安全的CopyOnWriteArraySet
private static final Set<ChatEndpoint> connections =
new CopyOnWriteArraySet<>();
private final String nickname;
private Session session;
public ChatEndpoint() {
nickname = GUEST_PREFIX + connectionIds.getAndIncrement();
}
//新连接到达时,Tomcat会创建一个Session,并回调这个函数
@OnOpen
public void start(Session session) {
this.session = session;
connections.add(this);
String message = String.format("* %s %s", nickname, "has joined.");
broadcast(message);
}
//浏览器关闭连接时,Tomcat会回调这个函数
@OnClose
public void end() {
connections.remove(this);
String message = String.format("* %s %s",
nickname, "has disconnected.");
broadcast(message);
}
//浏览器发送消息到服务器时,Tomcat会回调这个函数
@OnMessage
public void incoming(String message) {
// Never trust the client
String filteredMessage = String.format("%s: %s",
nickname, HTMLFilter.filter(message.toString()));
broadcast(filteredMessage);
}
// WebSocket连接出错时,Tomcat会回调这个函数
@OnError
public void onError(Throwable t) throws Throwable {
log.error("Chat Error: " + t.toString(), t);
}
// 向聊天室中的每个用户广播消息
private static void broadcast(String msg) {
for (ChatAnnotation client : connections) {
try {
synchronized (client) {
client.session.getBasicRemote().sendText(msg);
}
} catch (IOException e) {
...
}
}
}
}
根据Java WebSocket规范的规定,Java WebSocket应用程序由一系列的WebSocket Endpoint组成。Endpoint是一个Java对象,代表WebSocket连接的一端,就好像处理HTTP请求的Servlet一样,你可以把它看作是处理WebSocket消息的接口。
跟Servlet不同的地方在于,Tomcat会给每一个WebSocket连接创建一个Endpoint实例。
可以通过两种方式
编写一个Java类继承javax.websocket.Endpoint,并实现它的onOpen、onClose和onError方法。这些方法跟Endpoint的生命周期有关,Tomcat负责管理Endpoint的生命周期并调用这些方法。并且当浏览器连接到一个Endpoint时,Tomcat会给这个连接创建一个唯一的Session(javax.websocket.Session)。Session在WebSocket连接握手成功之后创建,并在连接关闭时销毁。当触发Endpoint各个生命周期事件时,Tomcat会将当前Session作为参数传给Endpoint的回调方法,因此一个Endpoint实例对应一个Session,我们通过在Session中添加MessageHandler消息处理器来接收消息,MessageHandler中定义了onMessage方法。在这里Session的本质是对Socket的封装,Endpoint通过它与浏览器通信。
实现一个业务类并给它添加WebSocket相关的注解。
@ServerEndpoint(value = "/websocket/chat")
注解,它表明当前业务类ChatEndpoint是个实现了WebSocket规范的Endpoint,并且注解的value值表明ChatEndpoint映射的URL是/websocket/chat
。
ChatEndpoint类中有@OnOpen、@OnClose、@OnError和在@OnMessage注解的方法,见名知义。
我们只需关心具体的Endpoint实现,比如聊天室,为向所有人群发消息,ChatEndpoint在内部使用了一个全局静态的集合CopyOnWriteArraySet维护所有ChatEndpoint实例,因为每一个ChatEndpoint实例对应一个WebSocket连接,即代表了一个加入聊天室的用户。
当某个ChatEndpoint实例收到来自浏览器的消息时,这个ChatEndpoint会向集合中其他ChatEndpoint实例背后的WebSocket连接推送消息。
Tomcat的WebSocket加载是通过SCI,ServletContainerInitializer,是Servlet 3.0规范中定义的用来接收Web应用启动事件的接口。
为什么要监听Servlet容器的启动事件呢?
这样就有机会在Web应用启动时做一些初始化工作,比如WebSocket需要扫描和加载Endpoint类。
将实现ServletContainerInitializer接口的类增加HandlesTypes注解,并且在注解内指定的一系列类和接口集合。比如Tomcat为了扫描和加载Endpoint而定义的SCI类如下:
定义好SCI,Tomcat在启动阶段扫描类时,会将HandlesTypes注解指定的类都扫描出来,作为SCI的onStartup参数,并调用SCI#onStartup。
WsSci#HandlesTypes注解定义了ServerEndpoint.class、ServerApplicationConfig.class和Endpoint.class,因此在Tomcat的启动阶段会将这些类的类实例(不是对象实例)传递给WsSci#onStartup。
因为Tomcat是将HTTP协议升级成WebSocket协议的,因为WebSocket是通过HTTP协议握手的,当WebSocket握手请求到来时,HttpProtocolHandler首先接收到这个请求,在处理这个HTTP请求时,Tomcat通过一个特殊的Filter判断该当前HTTP请求是否是一个WebSocket Upgrade请求(即包含Upgrade: websocket的HTTP头信息),如果是,则在HTTP响应里添加WebSocket相关的响应头信息,并进行协议升级。
就是用UpgradeProtocolHandler替换当前的HttpProtocolHandler,相应的,把当前Socket的Processor替换成UpgradeProcessor,同时Tomcat会创建WebSocket Session实例和Endpoint实例,并跟当前的WebSocket连接一一对应起来。这个WebSocket连接不会立即关闭,并且在请求处理中,不再使用原有的HttpProcessor,而是用专门的UpgradeProcessor,UpgradeProcessor最终会调用相应的Endpoint实例来处理请求。
Tomcat对WebSocket请求的处理没有经过Servlet容器,而是通过UpgradeProcessor组件直接把请求发到ServerEndpoint实例,并且Tomcat的WebSocket实现不需要关注具体I/O模型的细节,从而实现了与具体I/O方式的解耦。
WebSocket技术实现了Tomcat与浏览器的双向通信,Tomcat可以主动向浏览器推送数据,可以用来实现对数据实时性要求比较高的应用。这需要浏览器和Web服务器同时支持WebSocket标准,Tomcat启动时通过SCI技术来扫描和加载WebSocket的处理类ServerEndpoint,并且建立起了URL到ServerEndpoint的映射关系。
当第一个WebSocket请求到达时,Tomcat将HTTP协议升级成WebSocket协议,并将该Socket连接的Processor替换成UpgradeProcessor。这个Socket不会立即关闭,对接下来的请求,Tomcat通过UpgradeProcessor直接调用相应的ServerEndpoint来处理。
还可以通过Spring来实现WebSocket应用。
我希望在某些环境中使用用户名和密码保护某个角色,但在其他环境中甚至不需要提示。如果我在 tomcat web.xml 中有一个 auth-constraint,我可以创建一个具有“匿名”访问权限的角色
我正在使用 Tomcat jmxproxy 和状态来监视 Web 应用程序,但是 jmxproxy 页面中有很多无用的信息,并且其中没有任何信息,例如事件连接数。有谁知道如何过滤 jmxproxy 页
是否可以通过执行 JSP 来重启 Tomcat6? 这是因为我想通过使用网络服务器远程部署应用程序的更改。 部署脚本是用 bash 编写的,它从 svn 中 check out 最新版本,然后将其打包
我有一个包含 2 个子项目(后端和 ui)的 gradle 项目。 Ui由gradle tomcatRunWar完美启动.后端有我们在生产地点的配置描述符/conf/Catalina/localhos
发现 XAMPP 控件认为 tomcat 正在运行但无法停止它的问题。 在catalina下的tomcat logs目录下可以找到如下错误 “严重:无法联系 localhost:8005。Tomcat
PuppetLabs 在 PuppetForge 上有一个模块,用于部署 Tomcat 及其配置。 https://forge.puppet.com/puppetlabs/tomcat Tomcat
我有一个部署到 Tomcat 实例中的 Web 应用程序。我希望能够将 tomcat 配置为在 Tomcat 本身启动时不自动启动该应用程序。但是,我确实希望启动 Tomcat 管理器,以便我可以根据
操作系统:windows XP。 我已经安装了 Tomcat 7.0.25,文件夹“manager”位于 webapps 文件夹中。 我已阅读此处的文档:http://tomcat.apache.or
我们在 server.xml 文件中启用了以下访问日志模式 pattern="%h %H %l %u %t "%r" %s %b location: %{location}o"。 有人可以帮助理解模式
我最近开始使用 tomcat,我有一个关于 Tomcat 请求路由/映射的查询。 假设我在 tomcat 服务器中部署了四个应用程序 A、B、C 和 D,当有请求到来时,tomcat 如何知道要调用哪
我在我的计算机上使用 Tomcat,它可以通过端口 8080 访问。我想要的是我应该能够使用我的计算机的 IP 地址访问我的 Tomcat 服务器页面。我以前读过很多主题,但找不到一个可以帮助我的主题
我有一个 tomcat 7 服务器和一个 postgreSQL 9.0 数据库。我用它来为地理网络元数据编辑器设置开发环境。一切都是根据 geonetwork 网站教程设置的。我在将服务器与数据库连接
我的服务器有 物理 ip 和 虚拟 ip 由网络管理员设置。在我安装的服务器内部 Apache tomcat 7.0.29并创建一个网络应用程序。当我运行 wget http://:8080/xxx或
我有多个应用程序在不同的端口上运行(tomcat 实例) 都有相同的CATALINA_HOME 目前我必须在所有实例中部署和安装psi 探针,并在不同的窗口中分别监控每个端口。 我如何在一个单一的探测
based on this question 我尝试将 Tomcat 控制台输出重定向到一个文本文件,它对我的 Web 应用程序工作正常,但问题是,每次 Tomcat 启动时它都会被覆盖。我需要创
我需要阐明我的问题。问题是:有什么方法可以影响 Tomcat 为特定部署的应用程序分配多少堆内存?更多详细信息 - 我如何为已部署的应用程序设置特定的 Java 选项(考虑我想为每个应用程序设置特殊的
应用服务器内部的类加载机制是开发人员常见的困惑来源;这就是为什么我想问一个关于 tomcat 7 服务器中这个机制的问题:我有一个网络应用程序 sample.war,它依赖于 jgroups 库, 放
尝试将嵌入式 Tomcat 5 迁移到嵌入式 Tomcat 7。在启动过程中获取 NPE。 我扩展了 Embedded 类并按照正确的顺序执行所有建议的初始化。 NPE 发生在这里: Caused b
我可以使用随附的 start.sh 文件启动 tomcat,但是是否有任何参数可以用来执行以下操作: 在指定的根目录启动 Tomcat 服务 强制 Tomcat 在浏览器中拉出指定的主页 谢谢 最佳答
我在两台 diff 机器 tomcat 服务器上做一个简单的集群配置。每次我启动 tomcat 时,我都会收到一个错误,就像集群组中没有事件成员一样。我正在附加集群配置
我是一名优秀的程序员,十分优秀!