利用EventSource对象实现服务器推送(java servlet)

  • 普通轮询


poll_ajax_origin.png

客户端的每一个请求都是基于XmlHttpRequest异步请求对象。客户端每隔s秒之后,请求一次服务器端,然后客户端对服务器端的数据进行解析和渲染。客户端不断对服务器端进行请求(轮询),不管有没有数据,都必须进行返回。这样的方式固然有优势,但是缺点就是:造成服务器的压力非常大,而且由信息返回的时候还好,当服务器端没有数据的时候,依旧响应客户端(常理看来,很没有必要)。

普通轮询的前端代码实例:

<script type="text/javascript">
    var xmlHttpRequest = new XMLHttpRequest();
    function sendAsyncInfo(){
        
        xmlHttpRequest.onreadystatechange = function(){
            if(xmlHttpRequest.readyState == 4 && xmlHttpRequest.status == 200){
                var data = xmlHttpRequest.responseText;
                if(!(data == null || "" == data.trim())){
                    var ele = document.createElement("p");
                    ele.innerHTML = data;
                    document.getElementById("newInfo").appendChild(ele);
                }
            }
        }
        xmlHttpRequest.open("get","http://localhost:9090/lp",true);
        xmlHttpRequest.send();
    }
    //每隔1s,请求一次服务器端
    window.setInterval(sendAsyncInfo,1000);
</script>

普通轮询的后台服务器(servlet)代码实例:

protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        response.setCharacterEncoding("utf-8");
        PrintWriter out = response.getWriter();
        int number = new Random().nextInt(10);
        if(number > 2 && number < 5){
            //模拟有新的数据出现
            out.println(new Date() + ":您有新的信息出现");
        }
}
  • 长轮询


poll_ajax_long.png

客户端请求服务器端,客户端采用XmlHttpRequest对象进行请求。客户端每隔s秒钟进行请求服务器端一次(每请求一次,建立一个XmlHttpRequest对象)。请求服务器时,如果服务器端没有数据,则服务器端抓住这个Http连接,等服务器端有数据的时候再进行返回。而如果服务器端有数据,直接响应客户端,将数据返回,此时Http连接断开。

解释:每请求一次,建立一个XmlHttpRequest对象?

  • 如果每请求一次,都是基于同一个XmlHttpRequest对象。比如,a时刻,向服务器端发送请求s1,此时服务器端没有数据,那么客户端一直等待服务器端响应。b时刻,此时,s1请求没有得到响应(服务器端依旧没有新的数据),那么循环发起请求s2,因为请求都是建立在同一个XmlHttpRequest对象上,因此刚才的请求s1就会被停止掉。
  • 如果每请求一次,同时创建一个XmlHttpRequest对象。比如,a时刻,向服务器端发送请求s1,此时服务器端没有数据,那么客户端一直等待服务器端响应。b时刻,此时,s1请求没有得到响应(服务器端依旧没有新的数据),那么循环发起请求s2,s1和s2都是建立在不同的XmlHttpRequest对象上,因此s1和s2请求互不干扰,s1继续等待服务器端响应,s2开始请求服务器端数据。

长轮询的前端代码实例:

<script type="text/javascript">

    function sendAsyncInfo(){
        console.log("invoke");
        //每请求一次创建一个XmlHttpRequest对象
        var xmlHttpRequest = new XMLHttpRequest();
        xmlHttpRequest.onreadystatechange = function(){
            if(xmlHttpRequest.readyState == 4 && xmlHttpRequest.status == 200){
                var data = xmlHttpRequest.responseText;
                if(!(data == null || "" == data.trim())){
                    var ele = document.createElement("p");
                    ele.innerHTML = data;
                    document.getElementById("newInfo").appendChild(ele);
                }
            }
        }
        xmlHttpRequest.open("post","http://localhost:9090/lp",true);
        xmlHttpRequest.send();
    }
  
    //每隔1s,请求服务器端接口数据
    window.setInterval(sendAsyncInfo,1000);
</script>

长轮询的后台代码实例:

protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        System.out.println("开始新一轮的请求:");

        //设置响应内容的编码格式
        response.setCharacterEncoding("utf-8");
        PrintWriter out = response.getWriter();
        
        //模拟服务器一直在查找新的数据
        while(true){
            int number = new Random().nextInt(10);
            if(number == 6){
                //模拟有新的数据出现
                break;
            }
            else{
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        out.println(new Date() + ":您有新的信息出现");
}
  • EventSource 推送(ajax普通轮询)


eventSource_poll.png
处理过程:

客户端建立EventSource对象,对服务器通过http协议不断进行请求。服务器对客户端的响应数据格式有四部分构成,event,data,id,空格行。客户端接收到服务器端的响应数据之后,根据event事件值,找到EventSource对象对应的事件监听器。

  • 例如,event值为load,那么客户端收到响应数据之后,解析到event值为load。客户端为EventSource对象添加该事件的监听器,EventSource.onLoad = function(){ //处理服务器端的响应数据 }或者
    EventSource.addEventListener("load",function(){ //处理服务器端的响应数据 })。
  • EventSource有三个默认的监听器,分别监听open,message,error事件。客户端和服务器端进行连接时,将会触发open事件,执行EventSource.onOpen = function(){}或者 EventSource.addEventListener("open",function(){ })。对于message事件,当服务器端响应的数据没有指定事件类型时,将会默认触发客户端的message事件。
  • 服务器端响应的报文数据中,id 表示事件event的id,用户可以自定义。并且响应的类型为 text/event-stream 类型。

EventSource推送的前端代码实例:

<script type="text/javascript">

    if(window.EventSource){

        var eventSource = new EventSource("http://localhost:9090/sse");

        //只要和服务器连接,就会触发open事件
        eventSource.addEventListener("open",function(){
           console.log("和服务器建立连接");
        });

        //处理服务器响应报文中的load事件
        eventSource.addEventListener("load",function(e){
            console.log("服务器发送给客户端的数据为:" + e.data);
        });

        //如果服务器响应报文中没有指明事件,默认触发message事件
        eventSource.addEventListener("message",function(e){
            console.log("服务器发送给客户端的数据为:" + e.data);
        });

        //发生错误,则会触发error事件
        eventSource.addEventListener("error",function(e){
            console.log("服务器发送给客户端的数据为:" + e.data);
        });

    }
    else{
        console.log("服务器不支持EvenSource对象");
    }

</script>

EventSource推送的后台代码实例:

protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        //媒体类型为 text/event-stream
        response.setContentType("text/event-stream");
        response.setCharacterEncoding("utf-8");
        PrintWriter out = response.getWriter();

        //响应报文格式为:
        //data:Hello World
        //event:load
        //id:140312
        //换行符(/r/n)

        out.println("data:Hello World");
        out.println("event:load");
        out.println("id:140312");
        out.println();
        out.flush();
        out.close();
}

EventSource对象实现推送的请求和响应报文:
  • 请求报文
request.png
  • 响应报文头部
response_header.png
  • 响应报文数据部分
response_data.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335

推荐阅读更多精彩内容