服务器推送之WebSocket--原理及Tomcat的实现
Posted Tomcat那些事儿
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了服务器推送之WebSocket--原理及Tomcat的实现相关的知识,希望对你有一定的参考价值。
现如今,许多场景下需要实现从服务端到客户端的主动推送消息。而对于传统的HTTP,我们都了解,其必须是要通过主动的请求,每个Request对应一个Response,此时要实现服务端推,必须要有一个主动的请求。
为此,人们想出了ajax长轮询,长连接等一系列方式,但对比长轮询的不断无效的请求,都不如使用我们今天提到的更方便且不消耗资源实现。
对比HTTP请求,比较明显的你会感觉到,无论是异步还是同步请求,对于HTTP,在开发者工具中你都能观察到应用是新发了一个请求到服务器,之后根据返回的信息进行处理展示的。
而WebSocket,则在第一次握手建立连接之后,后续的收发消息,都不再重新建立连接,也就是你观察不到它后续的请求了。
这也是HTTP与WebSocket的区别。
而在Tomcat内部,我们来看Websocket是如何生效及工作的。
首先,来看WebSocket是如何初始化的。
无论哪种类型的请求,都会在ApplicationFilterChain中进行处理,根据是否配置Filter来决定整个处理的流向。(前面曾介绍过Filter的工作原理及请求流程:)。而无论哪个应用,其实Tomcat内部都会为其默认添加这样一个Filter:
WsFilter
这个Filter就是用来处理WebSocket的,但又不全是,因为它的filter-mapping是
/*
FilterRegistration.Dynamic fr = servletContext.addFilter(
"Tomcat WebSocket (JSR356) Filter", new WsFilter());
fr.setAsyncSupported(true);
EnumSet<DispatcherType> types = EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD);
fr.addMappingForUrlPatterns(types, true, "/*");
上面的代码即是使用Servlet3.0新增加的动态声明Filter的实现方式,把WsFilter这个动态增加到应用的filter链中。
而这个Filter中,也是在入口处判断,只有WebSocket的请求才处理,其它的就跳过了
// This filter only needs to handle WebSocket upgrade requests
if (!sc.areEndpointsRegistered() ||
!UpgradeUtil.isWebSocketUpgradeRequest(request, response)) {
chain.doFilter(request, response);
return;
}
添加Filter这一行为,是在应用启动的时候执行的,调用栈如下:
at org.apache.catalina.core.StandardContext.addFilterMap(StandardContext.java:2836)
at org.apache.catalina.core.ApplicationFilterRegistration.addMappingForUrlPatterns(ApplicationFilterRegistration.java:104)
at org.apache.tomcat.websocket.server.WsServerContainer.<init>(WsServerContainer.java:141)
at org.apache.tomcat.websocket.server.WsSci.init(WsSci.java:131)
at org.apache.tomcat.websocket.server.WsSci.onStartup(WsSci.java:47)
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5151)
请求连接建立
在数据发送之前,需要先建立连接。而WebSocket本质上仍然是TCP连接,虽然看起来是通过HTTP,只是因为其初次的握手是需要通过HTTP来建立。
我们以Tomcat自带的websocket样例中的echo例子来说明WebSocket的使用方式以及其在Tomcat内部的实现形式。
echo例子位于:
TOMCAT_HOME\webapps\examples\websocket
java代码位于:
TOMCAT_HOME\webapps\examples\WEB-INF\classes\websocket\echo
启动后,会看到如下图所示:
代码中,对于connect和echo message的实现如下:
function connect() {
var target = document.getElementById('target').value;
if (target == '') {
alert('Please select server side connection implementation.');
return;
}
if ('WebSocket' in window) {
ws = new WebSocket(target);
} else if ('MozWebSocket' in window) {
ws = new MozWebSocket(target);
} else {
alert('WebSocket is not supported by this browser.');
return;
}
ws.onopen = function () {
setConnected(true);
log('Info: WebSocket connection opened.');
};
ws.onmessage = function (event) {
log('Received: ' + event.data);
};
ws.onclose = function (event) {
setConnected(false);
log('Info: WebSocket connection closed, Code: ' + event.code + (event.reason == "" ? "" : ", Reason: " + event.reason));
};
}
我们看到,整个websocket对象会处理三个事件:
connect
message
close
而信息的发送,是直接使用websocket的send方法。
在初次连接握手时,通过开发者工具,我们可以观察到:
是通过Upgrade来进行协议的切换,同时连接到WebSocket 的Server上去的。
而此时的Upgrade就是通过我们前面提到的Filter来进行的。
在Filter中,通过UgradeUtil的doUpgrade方法进行后续关于WebSocket规范的调用实现。
而对于WebSocket Server 的支持,我们通过Echo的例子可以看到,可以直接使用Endpoint的类来进行,也可以通过注解的等式进行。
例如下面的代码就是通过注解的形式,声明了一个Websocket的Endpoint
@ServerEndpoint("/websocket/echoAnnotation")
public class EchoAnnotation {
@OnMessage
public void echoTextMessage(Session session, String msg, boolean last) {
try {
if (session.isOpen()) {
session.getBasicRemote().sendText(msg, last);
}
} catch (IOException e) {
}
}
根据注解,Tomcat内部会生成一个PojoServer,并使用反射调用当前标注有@OnMessage的方法。
而请求的分发,我们在前面介绍Connector的时候,曾简单说过是经过
Endpoint -> Handler -> Protocol
()
对于不同的请求,Protocol中进行不同的转发,
} else if (processor.isAsync() ||
state == SocketState.ASYNC_END) {
state = processor.asyncDispatch(status);
} else if (processor.isComet()) {
state = processor.event(status);
} else if (processor.isUpgrade()) {
state = processor.upgradeDispatch(status);
} else if (status == SocketStatus.OPEN_WRITE) {
// Extra write event likely after async, ignore
state = SocketState.LONG;
}
在建立连接之后,后续再进行的数据发送,通过开发者工具已经观察不到任何的请求了,这也是WebSocket之所以可以实现服务器推送的主要原因。其本质上在建立连接后,已经不再是一个HTTP请求了,而是一个TCP连接。
而且因于WebSocket在html5中的规范实现,各个主流浏览器的支持,现在多数的应用服务器也都已经根据规范进行了支持。许多要实现服务器推的场景也可以考虑使用WebSocket来实现。
例如下图,是知乎使用webSocket的请求:
打开Tomcat的webSocket样例,来体验一下吧!
以上是关于服务器推送之WebSocket--原理及Tomcat的实现的主要内容,如果未能解决你的问题,请参考以下文章