基於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端像安卓端發送的消息。
public class AppListener implements ServletContextListener{//監聽ServletContext的初始化

    @Override
    public void contextDestroyed(ServletContextEvent arg0) {
        // TODO Auto-generated method stub
        
    }

    @Override
    public void contextInitialized(ServletContextEvent event) {
        // TODO Auto-generated method stub
        event.getServletContext().setAttribute(Constant.IMMSG, new HashMap<String,String>());
System.out.println("添加Map成功");
    }

}
View Code
  • 接收安卓端長輪詢的servlet:
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) {
                // TODO Auto-generated catch block
                msg.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) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
     }
View Code
  • 接收PC端消息的Servlet:
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住連接一段時間后釋放呢?主要是因為長時間的靜態連接容易出問題,另外移動端的網絡復雜,所以才會有釋放的必要。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM