网络编程-轮询和长轮询


轮询(Polling):是指不管服务器端有没有更新,客户端(通常是指浏览器)都定时的发送请求进行查询,轮询的结果可能是服务器端有新的更新过来,也可能什么也没有,只是返回个空的信息。不管结果如何,客户端处理完后到下一个定时时间点将继续下一轮的轮询。

长轮询(Long Polling):长轮询的服务其客户端是不做轮询的,客户端在发起一次请求后立即挂起,一直到服务器端有更新的时候,服务器才会主动推送信息到客户端。 在服务器端有更新并推送信息过来之前这个周期内,客户端不会有新的多余的请求发生,服务器端对此客户端也啥都不用干,只保留最基本的连接信息,一旦服务器有更新将推送给客户端,客户端将相应的做出处理,处理完后再重新发起下一轮请求。

可见,长轮询的特点:

  • 服务器端会阻塞请求直到有数据传递或超时才返回.
  • 客户端响应处理函数会在处理完服务器返回的信息后,再次发出请求,重新建立连接.
  • 当客户端处理接收的数据、重新建立连接时,服务器端可能有新的数据到达;这些信息会被服务器端保存直到客户端重新建立连接,客户端会一次把当前服务器端所有的信息取回。

Java-长轮询(Long polling)实现

  • 服务端
package _20200418.example;

import com.sun.net.httpserver.HttpServer;

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.Random;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 长轮询服务端
 * 

* Created by zfh on 2020/04/18 */ public class server { private final AtomicLong value = new AtomicLong(); private void start() throws IOException { HttpServer httpServer = HttpServer.create(new InetSocketAddress(8080), 0); httpServer.setExecutor(Executors.newCachedThreadPool()); httpServer.createContext("/long-polling", httpExchange -> { byte[] data = fetchData(); httpExchange.sendResponseHeaders(200, data.length); OutputStream outputStream = httpExchange.getResponseBody(); outputStream.write(data); outputStream.close(); httpExchange.close(); }); httpServer.start(); } private byte[] fetchData() { try { // 由于客户端设置的超时时间是50s, // 为了更好的展示长轮询,这边random 100,模拟服务端hold住大于50和小于50的情况。 Random random = new Random(); TimeUnit.SECONDS.sleep(random.nextInt(100)); } catch (InterruptedException ignored) { } return Long.toString(value.getAndIncrement()).getBytes(StandardCharsets.UTF_8); } public static void main(String[] args) throws IOException { server server = new server(); server.start(); } }

  • 客户端
package _20200418.example;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 长轮询-客户端
 * 

* Created by zfh on 2020/04/18 */ public class client { private static final String SYNC_URL = "http://localhost:8080/long-polling"; private final AtomicLong sequence = new AtomicLong(); void poll() { // 循环执行,保证每次longpolling结束,再次发起longpolling // 结束条件,超时或者拿到数据 while (!Thread.interrupted()) { doPoll(); } } private void doPoll() { System.out.println("第" + (sequence.incrementAndGet()) + "次 longpolling"); long startMillis = System.currentTimeMillis(); HttpURLConnection connection = null; try { URL getUrl = new URL(SYNC_URL); connection = (HttpURLConnection) getUrl.openConnection(); // 50s作为长轮询超时时间 connection.setReadTimeout(50000); connection.setConnectTimeout(3000); connection.setRequestMethod("GET"); connection.setUseCaches(false); connection.setDoOutput(true); connection.setDoInput(true); connection.setRequestProperty("Content-Type", "application/json;charset=UTF-8"); connection.setRequestProperty("Accept-Charset", "application/json;charset=UTF-8"); connection.connect(); if (200 == connection.getResponseCode()) { BufferedReader reader = null; try { reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8)); StringBuilder result = new StringBuilder(256); String line; while ((line = reader.readLine()) != null) { result.append(line); } System.out.println("结果 " + result); } finally { if (reader != null) { reader.close(); } } } } catch (IOException e) { System.out.println("request failed"); } finally { long elapsed = (System.currentTimeMillis() - startMillis) / 1000; System.out.println("connection close" + " " + "elapse " + elapsed + "s"); if (connection != null) { connection.disconnect(); } System.out.println(); } } public static void main(String[] args) throws InterruptedException { client bootstrap = new client(); bootstrap.poll(); Thread.sleep(Integer.MAX_VALUE); } }