长轮询:解决访问超时

 

​一、常用的web通信

1、轮询:客户端定时向服务器发送Ajax请求,服务器接到请求后马上返回响应信息并关闭连接。

优点:后端程序编写较为容易。

缺点:请求中有大半是无用,浪费带宽和服务器资源。

2、长轮询:客户端向服务器发送Ajax请求,服务器接到请求后hold住连接,直到有新消息才返回响应信息并关闭连接,客户端处理完响应信息后再向服务器发送新的请求。

优点:在无消息的情况下不会频繁的请求,耗费资源小。在即时通讯工具中,实时性高。

缺点:服务器hold连接会消耗资源,返回数据顺序无保证,难于管理维护。

3、长连接:在页面里嵌入一个隐蔵iframe,将这个隐蔵iframe的src属性设为对一个长连接的请求或是采用xhr请求,服务器端就能源源不断地往客户端输入数据。

优点:消息即时到达,不发无用请求;管理起来也相对方便。

缺点:服务器维护一个长连接会增加开销。

4、webSocket:前端和服务器只需要一次HTTP握手,整个通讯过程就是建立在一次连接状态中。

优点:一个Web客户端只建立一个TCP连接;Websocket服务端可以推送(push)数据到web客户端;有更加轻量级的头,减少数据传送量;通过协议promote的方式将http升级成websocket。

缺点:不兼容低版本的IE。

二、背景

即将上线的产品在测试时候,一切都挺好的,等着下班。

产品:查一年的数据出现了连接超时问题,赶紧处理下

开发:...可以,用webSocket这个就行了,再找一个封装好的框架(小意思啦)

产品:我希望今天就解决好问题

开发:...(不可描述的语言)

... ...

开发:还好我之前用过webSocket (https://mp.weixin.qq.com/s/JozHypUdCIwe2MsOhI3fTQ)

... ...

开发:what? 服务器不支持webSocket......(想掀桌的感觉)

开发:哎,还是改用长轮询吧

三、长轮询的操作流程

3.1  前端操作

前端不断请求后台,直到成功获取数据,轮询结束

var params = {
    key: '',     //作为通过redis中获取值的标识
    deptId: 't3o2o323o1dak21';
}
function demo(params){
    $.ajax({
        type: "post",  //AJAX请求类型  
        url: context.path + "web/myDemo",  //请求url  
        cache: false,  //无缓存  
        timeout: 1000 * 300,  //AJAX请求超时时间为30秒  
        data: params,
        success: function(result) {
            if(result.key != null){
                var param = {
                    key: result.key
                }
                demo(param)   //调用自身接口请求后台
           }
            if (result.code=0 && result.key==null) {  //成功条件 : result.code=0且            result.key为null
                layer.alert("获取数据成功");
                console.log(result);
            }
            if(result.code=-1){   //失败条件 : result.code=-1
                layer.alert("请求数据失败");
            }
        },
        error:function(result){
            console.log(result);
       }
    });
}

3.2 后台操作

(1)服务器接收到请求后,无论有没有数据,先返回响应信息给前端,前端界面显示“信息在刷新,请等待下“。

(2)后台的线程处理数据,并把返回的响应数据保存在redis中。

(3)浏览器受到http响应后,立刻发送一个同样的http请求,这次直接在redis中查询是否有数据,当获取数据成功则轮询结束。

@RequestMapping("web/myDemo")
public void myDemo(String key, String deptId, HttpSession session, HttpServletResponse response) throws Exception{
    Map<String, Object> result = new HashMap<String, Object>();
    if (StringUtil.isEmpty(key)) {
        key = ShortUUID.randomShortUUID();
        new Thread(new Runnable() {     //开启线程,把数据保存在redis中
            @Override
            public void run() {
                Map<String, Object> result = deptServer.findByDeptId(deptId);  
                redisService.set(key, 150, JSONObject.toJSONString(result));   //对象以json字符串的格式存放在redis中
            }
        }).start();
    }else{
        String value = redisService.get(key);    //直接在redis中获取数据,如果为空是线程还没运行完
        if(!StringUtil.isEmpty(value)) {
            return Response.ok(value).build();   //轮询结束
        }
    }  
    result.put("key", key);    
    result.put("code", 0);
    ResponseUtil.write(response, result);    //无论结果如何,先正常返回数据让前端等待结果(前端显示数据在刷新中)
}

                                                                                                                                                                             待优化......