zoukankan      html  css  js  c++  java
  • Long Polling长轮询详解

    众所周知,数据交互有两种模式:Push(推模式)、Pull(拉模式)。

    推模式指的是客户端与服务端建立好网络长连接,服务方有相关数据,直接通过长连接通道推送到客户端。其优点是及时,一旦有数据变更,客户端立马能感知到;另外对客户端来说逻辑简单,不需要关心有无数据这些逻辑处理。缺点是不知道客户端的数据消费能力,可能导致数据积压在客户端,来不及处理。

    拉模式指的是客户端主动向服务端发出请求,拉取相关数据。其优点是此过程由客户端发起请求,故不存在推模式中数据积压的问题。缺点是可能不够及时,对客户端来说需要考虑数据拉取相关逻辑,何时去拉,拉的频率怎么控制等等。

    详解

    说到Long Polling(长轮询),必然少不了提起Polling(轮询),这都是拉模式的两种方式。

    Polling是指不管服务端数据有无更新,客户端每隔定长时间请求拉取一次数据,可能有更新数据返回,也可能什么都没有。

    Long Polling原理也很简单,相比Polling,客户端发起Long Polling,此时如果服务端没有相关数据,会hold住请求,直到服务端有相关数据,或者等待一定时间超时才会返回。返回后,客户端又会立即再次发起下一次Long Polling。这种方式也是对拉模式的一个优化,解决了拉模式数据通知不及时,以及减少了大量的无效轮询次数。(所谓的hold住请求指的服务端暂时不回复结果,保存相关请求,不关闭请求连接,等相关数据准备好,写会客户端。)

    前面提到Long Polling如果当时服务端没有需要的相关数据,此时请求会hold住,直到服务端把相关数据准备好,或者等待一定时间直到此次请求超时,这里大家是否有疑问,为什么不是一直等待到服务端数据准备好再返回,这样也不需要再次发起下一次的Long Polling,节省资源?
    主要原因是网络传输层主要走的是tcp协议,tcp协议是可靠面向连接的协议,通过三次握手建立连接。但是所建立的连接是虚拟的,可能存在某段时间网络不通,或者服务端程序非正常关闭,亦或服务端机器非正常关机,面对这些情况客户端根本不知道服务端此时已经不能互通,还在傻傻的等服务端发数据过来,而这一等一般都是很长时间。当然tcp协议栈在实现上有保活计时器来保证的,但是等到保活计时器发现连接已经断开需要很长时间,如果没有专门配置过相关的tcp参数,一般需要2个小时,而且这些参数是机器操作系统层面,所以,以此方式来保活不太靠谱,故Long Polling的实现上一般是需要设置超时时间的。

    实现

    Long Polling的实现很简单,可分为四个过程:

    • 发起Polling
      发起Polling很简单,只需向服务器发起请求,此时服务端还未应答,所以客户端与服务端之间一直处于连接状态。

    • 数据推送
      如果服务器端有相关数据,此时服务端会将数据通过此前建立的通道发回客户端。

    • Polling终止
      Polling终止情况有三种:
      若服务端返回相关数据,此时客户端收到数据后,关闭请求连接,结束此次Polling过程。
      若客户端等待设定的超时时间后,服务端依然没有返回数据,此时客户端需要主动终止此次Polling请求。
      若客户端收到网络故障或异常,此时客户端自然也是需要主动终止此次Polling请求。

    • 重新Polling
      终止上次Polling后,客户端需要立即再次发起Polling请求。这样才能保证拉取数据的及时性。

    代码实现起来也很简单,Http Call按照上述过程就很方便实现LongPolling。下面Code只是简单展示过程,在具体场景下,根据具体的业务逻辑进行调整。

    客户端Code和服务端Code

    package com.andy.example.longpolling.client;
    
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.net.HttpURLConnection;
    import java.net.URL;
    
    /**
     * Created by zjm on 2021/3/6.
     */
    public class ClientBootstrap {
    
        public static final String URL = "http://localhost:8080/long-polling";
    
        public static void main(String[] args) {
            int i = 0;
            while (true) {
                System.out.println("" + (++i) + "次 longpolling");
                HttpURLConnection connection = null;
                try {
                    URL getUrl = new URL(URL);
                    connection = (HttpURLConnection) getUrl.openConnection();
                    connection.setReadTimeout(50000);//这就是等待时间,设置为50s
                    connection.setConnectTimeout(3000);
                    connection.setRequestMethod("GET");
                    connection.setRequestProperty("Accept-Charset", "utf-8");
                    connection.setRequestProperty("Content-Type", "application/json");
                    connection.setRequestProperty("Charset", "UTF-8");
    
                    if (200 == connection.getResponseCode()) {
                        BufferedReader reader = null;
                        try {
                            reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
                            StringBuilder result = new StringBuilder(256);
                            String line = null;
                            while ((line = reader.readLine()) != null) {
                                result.append(line);
                            }
    
                            System.out.println("结果 " + result);
    
                        } finally {
                            if (reader != null) {
                                reader.close();
                            }
                        }
                    }
                } catch (IOException e) {
    
                } finally {
                    if (connection != null) {
                        connection.disconnect();
                    }
                }
            }
        }
    
    }
    package com.andy.example.longpolling.server;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.io.PrintWriter;
    import java.util.Random;
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.atomic.AtomicLong;
    
    /**
     * Created by zjm on 2021/3/6.
     */
    public class LongPollingServlet extends HttpServlet {
    
        private Random random = new Random();
    
        private AtomicLong sequenceId = new AtomicLong();
    
        private AtomicLong count = new AtomicLong();
    
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
    
            System.out.println("" + (count.incrementAndGet()) + "次 longpolling");
    
            int sleepSecends = random.nextInt(100);
    //随机获取等待时间,来通过sleep模拟服务端是否准备好数据
    
            System.out.println("wait " + sleepSecends + " second");
    
            try {
                TimeUnit.SECONDS.sleep(sleepSecends);//sleep
            } catch (InterruptedException e) {
    
            }
    
            PrintWriter out = response.getWriter();
            long value = sequenceId.getAndIncrement();
            out.write(Long.toString(value));
        }
    
    }

    结果



    应用

    WebQQ、Comet都用到长轮询技术,另外一些使用Pull模式消费的消息系统,都会使用Long Polling技术进行优化。

    补充

    针对一些同学的反馈,补充一篇 https://www.jianshu.com/p/6e90c2f2e463,希望大家对长轮询理解更加深刻。
  • 相关阅读:
    TLS,SSL,HTTPS with Python(转)
    编码(转)
    python获取shell输出(转)
    python 线程安全
    Sublime Text3 运行python(转)
    KVM和QEMU的关系(转载)
    全虚拟化和半虚拟化(转)
    VMWare Workstation和VMWare vSphere(转)
    /etc/cron.d添加定时任务脚本后不生效
    Linux -- 部分命令
  • 原文地址:https://www.cnblogs.com/zjm-1/p/14486897.html
Copyright © 2011-2022 走看看