服务器推送之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方法。



在初次连接握手时,通过开发者工具,我们可以观察到:

服务器推送之WebSocket--原理及Tomcat的实现

是通过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的请求:

服务器推送之WebSocket--原理及Tomcat的实现


打开Tomcat的webSocket样例,来体验一下吧!






Tomcat那些事儿

扫描或长按下方二维码,即可关注!




以上是关于服务器推送之WebSocket--原理及Tomcat的实现的主要内容,如果未能解决你的问题,请参考以下文章

分享im即时通讯开发之WebSocket:概念原理易错常识

WebSocket 实战

消息推送二 之webSocket

重学Springboot系列之服务器推送技术

轮询,长轮询,websocket原理

spring boot项目之webSocket消息推送