背景:在上一篇文章中介绍了扫码登录的原理,其中涉及到了服务端和客户端的通信,客户端如何能获取最新的状态?服务端又怎样把最新的消息推送给客户端?本文将围绕服务端和客户端的通信展开叙述。

以HTTP为基础的请求中,服务端“推送”方案有三种,这里其实的推送并不是严格意义上的推送,这里是以获取服务端最新及时的消息为重点,只有第三种方案是服务端推送。

1  基于轮询

2  长训轮(long-polling)

3 长链接 

接下来分别讲述下这三种方案的优缺点和使用场景。

 

1 基于轮询

优点:开发简单,客户端实现即可,不需要服务端配合。

缺点:大多数情况下是无用的请求,占用服务端资源。

实现方式:客户端每隔一段时间调用接口,无论有没有数据,接口立即返回.

使用场景:不想折腾的开发者,消息及时性要求没那么高,服务器资源资源足。

$(function() {
	get();
        setInterval("getMsg();",5000);
});
 
function getMsg(){
	$.ajax({
		url : "${APP_PATH}/getMsg",
		data : {},
		datatype: "json",
		type : "post",	
		success : function(result) {	
			//console.log(result);
						
		},
		error:function (){  
		 
		} 
	});

2 基于长轮询(long-polling)

优点:消息及时,命中率高,消耗服务端资源少

缺点:服务端和客户端需要同时改造,消息会有部分延迟(发生在请求交替之时)

实现方式:客户端在上次请求返回后,在发送下次请求,服务端当有数据或者超时后返回,没有数据时hang住链接(超时时间需要综合考虑服务器性能和及时性做出平衡,有代理的话需要考虑代理对于链接的超时机制)。

使用场景:扫码登录,微信网页端获取消息等。

客户端代码:

function getMsg(){
	$.ajax({
		url : "${APP_PATH}/getMsg",
		data : {},
		datatype: "json",
		type : "post",	
		success : function(result) {	
			//处理逻辑
            getMsg();		
		},
		error:function (){  
		 getMsg();
		} 
	});

服务端代码:

@RequestMapping("/getMsg")
public void getMsg(long timed, HttpServletResponse response) throws Exception {
     PrintWriter writer = response.getWriter();
    //模拟redis有消息
    long start=  System.currentTimeMillis();
     while (true) {
        String msg=redisGetMsg(key);
        if(StringUtils.isNotBlank(msg){
                writer.write(msg);
                break;
          }else{
             if(System.currentTimeMillis()-start)>20000){    //约定hold住20s,会有部分偏差
                  writer.write(“”);
                  break;
                }
           } 
     }

}

2 长链接 

优点:通信及时,通信模式采用双工,类似于打电话

缺点:服务端和客户端需要同时改造,当链接过多时,消耗服务端资源比较大。

实现方式:客户端和服务端建立长链接,基于http1.1 ,keepalive ,websocket,comet,iframe等,基于socket的需要维持心跳

使用场景:实时性要求很高,银行系统,股票系统等

基于websocket案例代码前端:

<script type="text/javascript">
         function WebSocketTest()
         {
            if ("WebSocket" in window)
            {
               alert("您的浏览器支持 WebSocket!");
               
               // 打开一个 web socket
               var ws = new WebSocket("ws://localhost:9998/echo");
                
               ws.onopen = function()
               {
                  // Web Socket 已连接上,使用 send() 方法发送数据
                  ws.send("发送数据");
                  alert("数据发送中...");
               };
                
               ws.onmessage = function (evt) 
               { 
                  var received_msg = evt.data;
                  alert("数据已接收...");
               };
                
               ws.onclose = function()
               { 
                  // 关闭 websocket
                  alert("连接已关闭..."); 
               };
            }
            
            else
            {
               // 浏览器不支持 WebSocket
               alert("您的浏览器不支持 WebSocket!");
            }
         }
      </script>

 

后端websocket代码:

备注:链接需要维护连接池,对于java有两种方式:1 使用tomcat容器支持实现   2 spring 4.x实现websocket,前端需要socketjs

ublic class WsServer extends WebSocketServer {
    public WsServer(int port) {
        super(new InetSocketAddress(port));
    }

    public WsServer(InetSocketAddress address) {
        super(address);
    }

    @Override
    public void onOpen(WebSocket conn, ClientHandshake handshake) {
        // ws连接的时候触发的代码,onOpen中我们不做任何操作

    }

    @Override
    public void onClose(WebSocket conn, int code, String reason, boolean remote) {
        //断开连接时候触发代码
        userLeave(conn);
        System.out.println(reason);
    }

    @Override
    public void onMessage(WebSocket conn, String message) {
        System.out.println(message);
        if(null != message &&message.startsWith("online")){
            String userName=message.replaceFirst("online", message);//用户名
            userJoin(conn,userName);//用户加入
        }else if(null != message && message.startsWith("offline")){
            userLeave(conn);
        }

    }

    @Override
    public void onError(WebSocket conn, Exception ex) {
        //错误时候触发的代码
        System.out.println("on error");
        ex.printStackTrace();
    }
    /**
     * 去除掉失效的websocket链接
     * @param conn
     */
    private void userLeave(WebSocket conn){
        WsPool.removeUser(conn);
    }
    /**
     * 将websocket加入用户池
     * @param conn
     * @param userName
     */
    private void userJoin(WebSocket conn,String userName){
        WsPool.addUser(userName, conn);
    }

}