java 实现http长轮询,Long Polling长轮询实现进阶「建议收藏」

java 实现http长轮询,Long Polling长轮询实现进阶「建议收藏」LongPolling长轮询实现进阶简书涤生。转载请注明原创出处,谢谢!如果读完觉得有收获的话,欢迎点赞加关注。介绍由于LongPolling长轮询详解这篇文章中的code实现较为简单,尤其是服务端处理较为粗暴,有一些同学反馈希望服务端处理阻塞这块内容进行更深入讨论等等,所以这里专门补一篇实现进阶,让大家对长轮询有更加深刻的理解。疑问对上篇文章,同学反馈有两个疑问。服务端实现使用的是同…


Jetbrains全系列IDE稳定放心使用

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实现结果

6e90c2f2e463

同步servlet客户端结果

6e90c2f2e463

同步servlet服务端结果

异步servlet实现结果

6e90c2f2e463

异步servlet客户端结果

6e90c2f2e463

异步servlet服务端结果

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

6e90c2f2e463

涤生-微信公共号

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/183558.html原文链接:https://javaforall.net

赞 (0)
全栈程序员-站长的头像全栈程序员-站长

相关推荐

  • php设计模式总结-工厂模式

    php设计模式总结-工厂模式

    2022年3月12日
  • savefiledialog是什么对话框_saveshowfile什么意思

    savefiledialog是什么对话框_saveshowfile什么意思c#获取要保存文件的对话框,用SaveFileDialog类。具体用法很简单分享一下吧,对于初学者可能有用//可能要获取的路径名stringlocalFilePath=””,fileNameExt=””,newFileName=””,FilePath =””;SaveFileDialogsaveFileDialog=newSaveFileDialog();

    2022年10月8日
  • symbian软件下载_手机系统更新软件

    symbian软件下载_手机系统更新软件编辑器加载中…常见软件类型  .sis和.sisx  分别的是第一、二版和第三版、第五版标准的SymbianOS操作系统唯一的可执行安装的安装文件,直接传入手机安装即可。  .app  一般来说*.app文件是某个软件的激活成功教程补丁文件,就是可以将未注册的软件变成已注册软件的文件。一般使用app激活成功教程的方法:利用文件管理程序(如文件动力)将某个软件一同带的*.app文件通过…

    2022年9月24日
  • 在Centos中yum安装和卸载软件的使用方法

    在Centos中yum安装和卸载软件的使用方法yum-yinstall包名(支持*):自动选择y,全自动yuminstall包名(支持*):手动选择yornyumremove包名(不支持*)rpm-ivh包名(支持*):安装rpm包rpm-e包名(不支持*):卸载rpm包安装一个软件时yum-yinstallhttpd安装多个相类似的软件时yum-y

    2022年5月27日
  • [医疗信息化][DICOM教程]DICOM标准简介[通俗易懂]

    [医疗信息化][DICOM教程]DICOM标准简介[通俗易懂][医疗信息化][DICOM教程]DICOM标准简介使用OsiriX的DICOM标准简介内容介绍什么是DICOM医院系统内的图像传输了解DICOM服务OsiriX提供的DICOM服务其他DICOM服务DICOM文件格式DICOM结构化报告符合DICOMDICOM与其他标准的互操作性结论介绍这是我有关DICOM标准的系列文章的一部分,并快速概述了DICOM标…

    2022年5月25日
  • Mysql 日期类型比较 TIMESTAMPDIFF

    Mysql 日期类型比较 TIMESTAMPDIFF  在数据库查询中,经常遇到计算2个日期相差值,SQL提供一个非常有用的函数:TIMESTAMPDIFFT。  基本语法:TIMESTAMPDIFF(interval,datetime_expr1,datetime_expr2)    其中,interval的取值可以为:SECOND,MINUTE,HOUR,DAY,WEEK,MONTH,QUARTERorYEAR   …

    2022年5月18日

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号