5

传统服务端推技术

  • 简单轮询
    这是最早的一种实现实时 Web 应用的方案。客户端以一定的时间间隔向服务端发出请求,以频繁请求的方式来保持客户端和服务器端的同步。这种同步方案的最大问题是,当客户端以固定频率向服务器发起请求的时候,服务器端的数据可能并没有更新,这样会带来很多无谓的网络传输,所以这是一种非常低效的实时方案。

  • 长轮询
    长轮询是对定时轮询的改进和提高,目地是为了降低无效的网络传输。当服务器端没有数据更新的时候,连接会保持一段时间周期直到数据或状态改变或者时间过期,通过这种机制来减少无效的客户端和服务器间的交互。当然,如果服务端的数据变更非常频繁的话,这种机制和定时轮询比较起来没有本质上的性能的提高。

  • 流:
    流技术方案通常就是在客户端的页面使用一个隐藏的窗口向服务端发出一个长连接的请求。服务器端接到这个请求后作出回应并不断更新连接状态以保证客户端和服务器端的连接不过期。通过这种机制可以将服务器端的信息源源不断地推向客户端。这种机制在用户体验上有一点问题,需要针对不同的浏览器设计不同的方案来改进用户体验,同时这种机制在并发比较大的情况下,对服务器端的资源是一个极大的考验。

综合这几种方案,您会发现这些目前我们所使用的所谓的实时技术并不是真正的实时技术,它们只是在用 Ajax 方式来模拟实时的效果,在每次客户端和服务器端交互的时候都是一次 HTTP 的请求和应答的过程,而每一次的 HTTP 请求和应答都带有完整的 HTTP 头信息,这就增加了每次传输的数据量,而且这些方案中客户端和服务器端的编程实现都比较复杂,在实际的应用中,为了模拟比较真实的实时效果,开发人员往往需要构造两个 HTTP 连接来模拟客户端和服务器之间的双向通讯,一个连接用来处理客户端到服务器端的数据传输,一个连接用来处理服务器端到客户端的数据传输,这不可避免地增加了编程实现的复杂度,也增加了服务器端的负载,制约了应用系统的扩展性。

WebSocket优势

WebSocket 设计出来的目的就是要取代轮询和 Comet 技术,使客户端浏览器具备像 C/S 架构下桌面系统的实时通讯能力。 浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。因为 WebSocket 连接本质上就是一个 TCP 连接,所以在数据传输的稳定性和数据传输量的大小方面,和轮询以及 Comet 技术比较,具有很大的性能优势。Websocket.org 网站对传统的轮询方式和 WebSocket 调用方式作了一个详细的测试和比较,将一个简单的 Web 应用分别用轮询方式和 WebSocket 方式来实现,在这里引用一下他们的测试结果图:
p1

通过这张图可以清楚的看出,在流量和负载增大的情况下,WebSocket 方案相比传统的 Ajax 轮询方案有极大的性能优势。这也是为什么我们认为 WebSocket 是未来实时 Web 应用的首选方案的原因。

WebSocket支持的版本

Spring - 4.0.1
Tomcat - 7.0.52
Web browser - chrome 和 firefox

实现

  • 增加依赖
 <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-websocket</artifactId>
   <version>4.0.1.RELEASE</version>
</dependency>
<dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-messaging</artifactId>
   <version>4.0.1.RELEASE</version>
</dependency>
  • 创建websocket处理类
    这个类处理来自浏览器(客户端)的websocket请求。在这个例子中,我们创建一个叫WebsocketEndPoint的类,并让它继承TextWebsocketHandler类。然后重写父类方法handlerTextMessage(),每当客户端发送消息过来,都会由这个函数接收并处理。
 public class WebsocketEndPoint extends TextWebSocketHandler {

    @Override
    protected void handleTextMessage(WebSocketSession session,TextMessage message) throws Exception {
        super.handleTextMessage(session, message);
        TextMessage returnMessage = new TextMessage(message.getPayload()+" received at server");
        session.sendMessage(returnMessage);
    }
}
  • 创建握手拦截器(optional)
    websocket握手拦截器用来拦截和处理客户端和服务端分别在握手前和握手后的的事件,我们在这里添加这个拦截器是为了能清晰知道什么时候以及是否成功握手
public class HandshakeInterceptor extends HttpSessionHandshakeInterceptor{
    @Override
    public boolean beforeHandshake(ServerHttpRequest request,
            ServerHttpResponse response, WebSocketHandler wsHandler,
            Map<String, Object> attributes) throws Exception {
        System.out.println("Before Handshake");
        return super.beforeHandshake(request, response, wsHandler, attributes);
    }

    @Override
    public void afterHandshake(ServerHttpRequest request,
            ServerHttpResponse response, WebSocketHandler wsHandler,
            Exception ex) {
        System.out.println("After Handshake");
        super.afterHandshake(request, response, wsHandler, ex);
    }

}
  • 配置handler和interceptor
    有两种方式:
    1)创建一个实现WebSocketConfigurer接口的类
@Configuration
@EnableWebSocket
public class WebsocketConfig implements WebSocketConfigurer{

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(myhandler(), "/websocket")
        .addInterceptors(myInterceptors());
    }

    @Bean
    public WebSocketHandler myhandler() {
        return new WebsocketEndPoint();
    }

    @Bean
    public HandshakeInterceptor myInterceptors() {
        return new HandshakeInterceptor();
    }
}

2)配置xml

<bean id="websocket" class="***.***.***.WebsocketEndPoint"/>
<websocket:handlers>
    <websocket:mapping path="/websocket" handler="websocket"/>
    <websocket:handshake-interceptors>
    <bean class="***.***.***.HandshakeInterceptor"/>
    </websocket:handshake-interceptors>
</websocket:handlers>

到这里,服务端的代码以及配置已经弄好

  • 浏览器端javascript


<script type="text/javascript"> function setConnected(connected) { document.getElementById('connect').disabled = connected; document.getElementById('disconnect').disabled = !connected; document.getElementById('conversationDiv').style.visibility = connected ? 'visible' : 'hidden'; document.getElementById('response').innerHTML = ''; } function connect() { if ('WebSocket' in window){ console.log('Websocket supported'); socket = new WebSocket('ws://localhost:8080//websocket'); console.log('Connection attempted'); socket.onopen = function(){ console.log('Connection open!'); setConnected(true); } socket.onclose = function(){ console.log('Disconnecting connection'); } socket.onmessage = function (evt) { var received_msg = evt.data; console.log(received_msg); console.log('message received!'); showMessage(received_msg); } } else { console.log('Websocket not supported'); } } function disconnect() { setConnected(false); console.log("Disconnected"); } function sendName() { var message = document.getElementById('message').value; socket.send(JSON.stringify({ 'message': message })); } function showMessage(message) { var response = document.getElementById('response'); var p = document.createElement('p'); p.style.wordWrap = 'break-word'; p.appendChild(document.createTextNode(message)); response.appendChild(p); } /* 1. new WebSocket('ws://localhost:8080//websocket')尝试与服务器建立连接; 2. 握手成功并建立连接后,socket.onopen被调用 3. 当接收来自服务器的消息,socket.onmessage被调用 4. socket.send()用来发送消息至服务端 */ </script>

完。

参考资料: 使用 HTML5 WebSocket 构建实时 Web 应用


zclau
359 声望12 粉丝

Java后端,爱好Java、C、Unix,喜欢钻研基础技术、基础架构