Long Polling长轮询实现进阶

简书 涤生。

转载请注明原创出处,谢谢!

如果读完觉得有收获的话,欢迎点赞加关注。

介绍

由于Long Polling长轮询详解 这篇文章中的code实现较为简单,尤其是服务端处理较为粗暴,有一些同学反馈希望服务端处理阻塞这块内容进行更深入讨论等等,所以这里专门补一篇实现进阶,让大家对长轮询有更加深刻的理解。

疑问

对上篇文章,同学反馈有两个疑问。

服务端实现使用的是同步servlet,性能比较差,能支撑的连接数比较少?

同步servlet来hold请求,确实会导致后续请求得不到及时处理,servlet3.0开始支持异步处理,可以更高效的处理请求。

服务端如何去hold住请求,sleep好吗?

同步servlet hold住请求的处理逻辑必须在servlet的doGet方法中,一般先fetch数据,准备好了,就返回,没准备好,就sleep片刻,再来重复。

异步servlet hold住请求比较简单,只要开启异步,执行完doGet方法后,不会自动返回此次请求,需要等到请求的context被complete,这样很巧妙的请求就自动hold住了。

实现

客户端实现

客户端实现基本和上篇差不多,没什么改变。

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;

import java.util.concurrent.atomic.AtomicLong;

/**

* Created by andy on 17/7/8.

*/

public class AbstractBootstrap {

//同步URL

protected static final String URL = "http://localhost:8080/long-polling";

//异步URL

protected static final String ASYNC_URL = "http://localhost:8080/long-polling-async";

private final AtomicLong sequence = new AtomicLong();

protected void poll() {

//循环执行,保证每次longpolling结束,再次发起longpolling

while (!Thread.interrupted()) {

doPoll();

}

}

protected void doPoll() {

System.out.println("第" + (sequence.incrementAndGet()) + "次 longpolling");

long startMillis = System.currentTimeMillis();

HttpURLConnection connection = null;

try {

URL getUrl = new URL(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(), "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) {

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();

}

}

}

package com.andy.example.longpolling.client;

import java.io.IOException;

/**

* Created by andy on 17/7/6.

*/

public class ClientBootstrap extends AbstractBootstrap {

public static void main(String[] args) throws IOException {

ClientBootstrap bootstrap = new ClientBootstrap();

//发起longpolling

bootstrap.poll();

System.in.read();

}

}

服务端实现

长轮询服务端同步servlet处理

服务端同步servlet和上篇差不多,没什么改动,增加了相关注释。

package com.andy.example.longpolling.server;

import javax.servlet.ServletException;

import javax.servlet.annotation.WebServlet;

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 andy on 17/7/6.

*/

@WebServlet(urlPatterns = "/long-polling")

public class LongPollingServlet extends HttpServlet {

private final Random random = new Random();

private final AtomicLong sequence = new AtomicLong();

private final AtomicLong value = new AtomicLong();

@Override

protected void doGet(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException {

System.out.println();

final long currentSequence = sequence.incrementAndGet();

System.out.println("第" + (currentSequence) + "次 longpolling");

//由于客户端设置的超时时间是50s,

//为了更好的展示长轮询,这边random 100,模拟服务端hold住大于50和小于50的情况。

//再具体场景中,这块在具体实现上,

//对于同步servlet,首先这里必须阻塞,因为一旦doGet方法走完,容器就认为可以结束这次请求,返回结果给客户端。

//所以一般实现如下:

// while(结束){ //结束条件,超时或者拿到数据

// data = fetchData();

// if(data == null){

// sleep();

// }

// }

int sleepSecends = random.nextInt(100);

System.out.println(currentSequence + " wait " + sleepSecends + " second");

try {

TimeUnit.SECONDS.sleep(sleepSecends);

} catch (InterruptedException e) {

}

PrintWriter out = response.getWriter();

long result = value.getAndIncrement();

out.write(Long.toString(result));

out.flush();

}

}

长轮询服务端异步servlet处理

由于同步servlet,性能较差,所有的请求操作必须在doGet方法中完成,包括等待数据,占用了容器的处理线程,会导致后续的请求阻塞,来不及处理。servlet 3.0支持异步处理,使用异步处理doGet方法执行完成后,结果也不会返回到客户端,会等到请求的context被complete才会写回客户端,这样一来,容器的处理线程不会受阻,请求结果可由另外的业务线程进行写回,也就轻松实现了hold操作。

package com.andy.example.longpolling.server;

import javax.servlet.*;

import javax.servlet.annotation.WebServlet;

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.*;

import java.util.concurrent.atomic.AtomicLong;

/**

* Created by andy on 17/7/7.

*/

/**

* 开启异步servlet,asyncSupported = true

*/

@WebServlet(urlPatterns = "/long-polling-async", asyncSupported = true)

public class LongPollingAsyncServlet extends HttpServlet {

private Random random = new Random();

private final AtomicLong sequence = new AtomicLong();

private final AtomicLong value = new AtomicLong();

private static ThreadPoolExecutor executor = new ThreadPoolExecutor(100, 200, 50000L,

TimeUnit.MILLISECONDS, new ArrayBlockingQueue(100));

@Override

protected void doGet(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException {

System.out.println();

final long currentSequence = sequence.incrementAndGet();

System.out.println("第" + (currentSequence) + "次 longpolling async");

//设置request异步处理

AsyncContext asyncContext = request.startAsync();

//异步处理超时时间,这里需要注意,jetty容器默认的这个值设置的是30s,

//如果超时,异步处理没有完成(通过是否asyncContext.complete()来进行判断),将会重试(会再次调用doGet方法)。

//这里由于客户端long polling设置的是50s,所以这里如果小于50,会导致重试。

asyncContext.setTimeout(51000);

asyncContext.addListener(new AsyncListener() {

@Override

public void onComplete(AsyncEvent event) throws IOException {

}

//超时处理,注意asyncContext.complete();,表示请求处理完成

@Override

public void onTimeout(AsyncEvent event) throws IOException {

AsyncContext asyncContext = event.getAsyncContext();

asyncContext.complete();

}

@Override

public void onError(AsyncEvent event) throws IOException {

}

@Override

public void onStartAsync(AsyncEvent event) throws IOException {

}

});

//提交线程池异步写会结果

//具体场景中可以有具体的策略进行操作

executor.submit(new HandlePollingTask(currentSequence, asyncContext));

}

class HandlePollingTask implements Runnable {

private AsyncContext asyncContext;

private long sequense;

public HandlePollingTask(long sequense, AsyncContext asyncContext) {

this.sequense = sequense;

this.asyncContext = asyncContext;

}

@Override

public void run() {

try {

//通过asyncContext拿到response

PrintWriter out = asyncContext.getResponse().getWriter();

int sleepSecends = random.nextInt(100);

System.out.println(sequense + " wait " + sleepSecends + " second");

try {

TimeUnit.SECONDS.sleep(sleepSecends);

} catch (InterruptedException e) {

}

long result = value.getAndIncrement();

out.write(Long.toString(result));

} catch (Exception e) {

System.out.println(sequense + "handle polling failed");

} finally {

//数据写回客户端

asyncContext.complete();

}

}

}

}

结果

同步servlet实现结果

同步servlet客户端结果

同步servlet服务端结果

异步servlet实现结果

异步servlet客户端结果

异步servlet服务端结果

个人微信公共号,感兴趣的关注下,获取更多技术文章

涤生-微信公共号

java 实现http长轮询,Long Polling长轮询实现进阶相关推荐

  1. 易语言服务端与客户端怎么传送_配置中心是怎么推送的?动手实现一个 Long Polling 长轮询...

    介绍 众所周知,数据交互有两种模式:Push(推模式).Pull(拉模式). 推模式指的是客户端与服务端建立好网络长连接,服务方有相关数据,直接通过长连接通道推送到客户端.其优点是及时,一旦有数据变更 ...

  2. 从RocketMQ看长轮询(Long Polling)

    前言 消息队列一般在消费端都会提供push和pull两种模式,RocketMQ同样实现了这两种模式,分别提供了两个实现类:DefaultMQPushConsumer和DefaultMQPullCons ...

  3. 长轮询java_网络编程-轮询和长轮询

    轮询(Polling):是指不管服务器端有没有更新,客户端(通常是指浏览器)都定时的发送请求进行查询,轮询的结果可能是服务器端有新的更新过来,也可能什么也没有,只是返回个空的信息.不管结果如何,客户端 ...

  4. 轮询没有收到的可能性_轮询(Polling)是什么?

    轮询(Polling)是一种CPU决策如何提供周边设备服务的方式,又称"程控输出入"(Programmed I/O).轮询法的概念是,由CPU定时发出询问,依序询问每一个周边设备是 ...

  5. php高效轮询,PHP实现长轮询

    今天帮人改了个在线聊天室的作业,用PHP+Ajax实现了一个长轮询(long polling). 服务端主要是两点,一个是用set_time_limit(0);去除页面执行时间的限制.再就是用一个wh ...

  6. ajax使用频率,AJAX轮询频率 - 要长期轮询还是不轮询长轮询?

    我正在构建一个需要相对不变的数据库轮询的网页组件.我可以看到两种不同的方法,我想知道他们中的一个是否比其他人好,或者如果我错过了第三个选择.AJAX轮询频率 - 要长期轮询还是不轮询长轮询? 1)发送 ...

  7. ajax长轮询tornado,Tornado长轮询和WebSocket

    Http协议是一种请求响应式协议, 不允许服务端主动向客户端发送信息. 短轮询是一种简单的实现服务端推送消息的解决方案, 客户端以一定间隔自动向服务端发送刷新请求, 服务端返回要推送的消息作为响应. ...

  8. Java中二维数组的用法(不定长二维数组)

    Java中二维数组的用法(不定长二维数组),即每个第二维的数组长度不一样. 1>代码如下: package com.demo.test;public class Test {public Tes ...

  9. LeetCode题库整理【Java】—— 3 无重复字符的最长子串

    LeetCode题库整理[Java] ## 3 无重复字符的最长子串 题目:给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度. 示例 1: 输入: "abcabcbb" ...

最新文章

  1. 要懂得利用和筛选友情链接
  2. python面试-python面试问题集锦
  3. 在RHEL-4下半小时搭建Sendmail邮件服务器(下)
  4. GNSS用户设备的组成及原理——以GPS用户设备为例
  5. BZOJ2216 [Poi2011]Lightning Conductor 【决策单调性dp】
  6. Dvbbs 7.1论坛鼠标指针修改方法
  7. DevOps组件高可用的思路
  8. java中this_JAVA中this用法小结
  9. 发送有序广播,只能运行在8.0之前的系统中
  10. 古文观止 —— 千古名篇
  11. 发电机机房设计规范_柴油发电机的容量怎么选择!发电机机房如何合理设计?来涨知识!...
  12. 华为盒子联网后显示无法连接服务器,【当贝市场】华为盒子连上无线后不能上网怎么办?...
  13. 如何对apk文件进行反编译
  14. Android开发中的常用库
  15. python excel筛选统计_如何用python对excel表格进行筛选
  16. selenium执行click报错的解决方案
  17. FOC电机控制,出售一份基于国产M0核MCU平台
  18. Potree使用指南
  19. 【每日新闻】阿里云回应大规模故障:运维操作失误 将认真改进 | 分析师:Azure已取代Windows成为微软新的增长点...
  20. 使用echarts实现雷达图

热门文章

  1. ApacheCN Python 译文集(二)20211110 更新
  2. excel基础-note-4.25
  3. java与python两个小人动图_CSS Sprite小图片自动合并工具(NodeJS,Python,Java,Ruby)
  4. 平面设计主要是学什么?平面设计主要有哪些内容?——黎乙丙
  5. Git runner安装
  6. 服务器系统raid设置,服务器RAID配置详解
  7. sql 查询及格率优秀率
  8. 每日新闻丨人工智能应用红利兑现期正在到来;三星向华为供应可折叠OLED面板...
  9. 【满分】【华为OD机试真题2023 JAVAJS】查找充电设备组合
  10. veracrypt取消加密卷_VeraCrypt 加密个人隐私(便携式 )