mysql 长轮询_基于HTTP长轮询实现简单推送

应用场景:设备为安卓、PC以及服务器,要求PC端能够单向给移动端发送消息指令,安卓端解析消息,进行后续处理动作。其中安卓端为基于Phonegap开发,说白了,就是HTML+JS。

规模:正常应用为200移动端,PC端数量有限,不超过10台,最多移动端为不超过500台。

可以看出这是一个很小规模的应用,也正如此,才可以给我像这样大方的保有HTTP连接不释放的机会。

当前背景:目前关于推送的实现,无非就是谷歌,HTML5的websocket,韩国某牛写的androidpn,以及第三方和伪推送方式。

谷歌的推送在中国大陆据说不稳定,所以被中国人弃之不用,然后就是HTML5的websocket居然在安卓4.0的机器上还不能被很好的支持,这些足以让那位韩国人写的androidpn在国内火了一阵子,不过后来因为国内的第三方推送开始发力,大部分应用开发者只要不是特别需要的话,就不会自己再做推送了。而伪推送方式,无外乎就是HTTP的长连接或者AJAX的长轮询,以及iframe流的方式(或许还有其他方式),这种技术就被称为comet。

基本原理:安卓端页面不间断的发起轮询请求,服务器接收请求后,如果没有消息可以返回,就先不释放连接,即线程等待,等待超时或者中途被唤醒后,返回给页面,释放连接,安卓端的页面再次发起轮询请求。

服务器端接收到PC端指令后,唤醒等待线程,让安卓端的下次轮询可以获取消息。

代码实现:

服务器端启动时,像ServletContext内添加一个map用于存储PC端像安卓端发送的消息。

7d737c82e1cd0770ac6bbc4a8ec28c49.gif

72099b42d1f09e197654b59beb93f8bc.gif

public class AppListener implements ServletContextListener{//监听ServletContext的初始化@Overridepublic void contextDestroyed(ServletContextEvent arg0) {// TODO Auto-generated method stub

}

@Overridepublic void contextInitialized(ServletContextEvent event) {// TODO Auto-generated method stubevent.getServletContext().setAttribute(Constant.IMMSG, new HashMap());

System.out.println("添加Map成功");

}

}

View Code

接收安卓端长轮询的servlet:

7d737c82e1cd0770ac6bbc4a8ec28c49.gif

72099b42d1f09e197654b59beb93f8bc.gif

private static int num=0;public void service(HttpServletRequest req,HttpServletResponse res) throws UnsupportedEncodingException{

req.setCharacterEncoding("UTF-8");

res.setContentType("text/html,charset=UTF-8");

ServletContext application=req.getSession().getServletContext();

req.getParameter("userID");

HashMapmsg=new HashMap();

Map map=((Map)application.getAttribute(Constant.IMMSG));synchronized(map){

String temp=map.remove(req.getParameter("userID"));

if(temp==null||temp.trim().equals("")){

try {

System.out.println("休眠等待60秒"+(++num));

map.wait(60000);//服务器保留此连接60秒

msg.put("msg","nomsg");//没有消息时,返回nomsg

} catch (InterruptedException e) {

blockmsg.put("msg", "error");

e.printStackTrace();

}

}else{

msg.put("msg", temp);//如果有消息,则立刻返回

}

}

PrintWriter out;try {

out = res.getWriter();

out.print(JSONObject.fromObject(msg));

out.flush();

out.close();

System.out.println("----等待数"+(--num));

} catch (IOException e) {

e.printStackTrace();

}

}

View Code

接收PC端消息的Servlet:

7d737c82e1cd0770ac6bbc4a8ec28c49.gif

72099b42d1f09e197654b59beb93f8bc.gif

public class SendMsgService extends HttpServlet {public void service(HttpServletRequest req,HttpServletResponse res) throws UnsupportedEncodingException{

req.setCharacterEncoding("UTF-8");

res.setContentType("text/html,charset=UTF-8");

ServletContext application=req.getSession().getServletContext();

Map map=((Map)application.getAttribute(Constant.IMMSG));synchronized(map){

map.put(req.getParameter("userID"), req.getParameter("msg"));

map.notifyAll();//通知所有等待的线程,让安卓端发起下次轮询    }

}

View Code

所谓安卓端的页面就是简单的js,在一次请求结束后发起下一次请求而已。

7d737c82e1cd0770ac6bbc4a8ec28c49.gif

72099b42d1f09e197654b59beb93f8bc.gif

function longPolling(){

$.ajax({             //url:ip+'/haveMsg',

url:"http://192.168.1.109:8081/mobileinspect/haveMsg",

data:{'userID':111},

dataType:'json',

timeout:70000,

cache:false,

type:"post",

success:function(data){                 if(data.msg){                     if(data.msg=="nomsg"){

window.setTimeout(longPolling,1000)

}else{

navigator.notification.confirm(data.msg,onConfirm,"新的消息","接受,拒绝");

window.setTimeout(navigator.notification.beep(1),100);

window.setTimeout(longPolling,1000)

}

}

},

error:function(xhr,err){//如果出现错误,则在十秒钟之后,再进行长轮询

window.setTimeout(longPolling,10000)

}

})

}

View Code

然后就是修改Tomcat的最大连接数,以让服务器能够处理这么多的连接而不至于停止响应:

针对我的这个应用,最大600个处理线程足以应付那500台机器了。单纯我的办公电脑就可以支持发起500个HTTP连接,并由本地的Tomcat处理,相信服务器更能够轻松应付。

另外,需要注意的是:据说单机windows下只支持2000左右的HTTP连接,而Linux下约是1000个,所以各位如果使用这种方法的时候,要注意是否会超出这些限制。

为什么要服务器hold住连接一段时间后释放呢?主要是因为长时间的静态连接容易出问题,另外移动端的网络复杂,所以才会有释放的必要。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在 Java 中实现轮询机制可以使用 Servlet 或者基于 Servlet 的框架,如 Spring MVC。下面是一个使用 Servlet 实现轮询的示例代码: ```java @WebServlet("/api/data") public class LongPollingServlet extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 设置响应类型为 JSON response.setContentType("application/json"); // 模拟获取数据的过程 // 例如,可以查询数据库或者调用外部接口来获取数据 String data = getData(); if (data != null) { // 有新数据可用时,将数据作为响应发给客户端 response.getWriter().write(data); response.getWriter().flush(); } else { // 没有新数据可用时,将请求挂起,并保持连接打开 synchronized (this) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 接收到新数据时,唤醒挂起的请求 synchronized (this) { notifyAll(); } } private String getData() { // 返回获取到的数据,如果没有数据可用,返回 null // 例如,可以从数据库或其他资源中获取数据 return null; } } ``` 在上面的示例中,`/api/data` 路径对应的 Servlet 类 `LongPollingServlet` 用于接收轮询请求。在 `doGet` 方法中,首先设置响应类型为 JSON,然后模拟获取数据的过程。如果有新数据可用,将数据作为响应发给客户端;如果没有新数据可用,将请求挂起,并保持连接打开,直到有新数据到达或超时。在 `doPost` 方法中,接收到新数据时,唤醒挂起的请求。 这只是一个简单的示例,实际情况可能需要根据具体需求对代码进行更多的处理和优化。另外,轮询机制也可以通过使用 WebSocket 或其他技术来实现,这些方法可以实现实时的双向通信,而不需要轮询

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值