應用場景:設備爲安卓、PC以及服務器,要求PC端可以單向給移動端發送消息指令,安卓端解析消息,進行後續處理動做。其中安卓端爲基於Phonegap開發,說白了,就是HTML+JS。
規模:正常應用爲200移動端,PC端數量有限,不超過10臺,最多移動端爲不超過500臺。
能夠看出這是一個很小規模的應用,也正如此,才能夠給我像這樣大方的保有HTTP鏈接不釋放的機會。html
當前背景:目前關於推送的實現,無非就是谷歌,HTML5的websocket,韓國某牛寫的androidpn,以及第三方和僞推送方式。
谷歌的推送在中國大陸聽說不穩定,因此被中國人棄之不用,而後就是HTML5的websocket竟然在安卓4.0的機器上還不能被很好的支持,這些足以讓那位韓國人寫的androidpn在國內火了一陣子,不事後來由於國內的第三方推送開始發力,大部分應用開發者只要不是特別須要的話,就不會本身再作推送了。而僞推送方式,無外乎就是HTTP的長鏈接或者AJAX的長輪詢,以及iframe流的方式(或許還有其餘方式),這種技術就被稱爲comet。java
基本原理:安卓端頁面不間斷的發起輪詢請求,服務器接收請求後,若是沒有消息能夠返回,就先不釋放鏈接,即線程等待,等待超時或者中途被喚醒後,返回給頁面,釋放鏈接,安卓端的頁面再次發起輪詢請求。
服務器端接收到PC端指令後,喚醒等待線程,讓安卓端的下次輪詢能夠獲取消息。android
代碼實現:web
服務器端啓動時,像ServletContext內添加一個map用於存儲PC端像安卓端發送的消息。ajax
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<String,String>()); System.out.println("添加Map成功"); } }
View Codejson
接收安卓端長輪詢的servlet:windows
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"); HashMap<String,String>msg=new HashMap<String,String>(); Map<String,String> 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:websocket
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<String,String> map=((Map)application.getAttribute(Constant.IMMSG));synchronized(map){ map.put(req.getParameter("userID"), req.getParameter("msg")); map.notifyAll();//通知全部等待的線程,讓安卓端發起下次輪詢 } }
View Code網絡
所謂安卓端的頁面就是簡單的js,在一次請求結束後發起下一次請求而已。
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的最大鏈接數,以讓服務器可以處理這麼多的鏈接而不至於中止響應:
<Connector connectionTimeout="20000" port="8081" protocol="HTTP/1.1" redirectPort="8443" maxThreads="600" acceptCount="100"/>
針對個人這個應用,最大600個處理線程足以應付那500臺機器了。單純個人辦公電腦就能夠支持發起500個HTTP鏈接,並由本地的Tomcat處理,相信服務器更可以輕鬆應付。
另外,須要注意的是:聽說單機windows下只支持2000左右的HTTP鏈接,而Linux下約是1000個,因此各位若是使用這種方法的時候,要注意是否會超出這些限制。
爲何要服務器hold住鏈接一段時間後釋放呢?主要是由於長時間的靜態鏈接容易出問題,另外移動端的網絡複雜,因此纔會有釋放的必要。