基于java servlet的http推送服务

前几天项目要求实现安卓手机端的推送功能,由于原来的工程与服务端通信用的是http的协议,又不想用极光推送,所以想自己做一个使用http的推送实现,最后研究了几天,今天把例子发出来:
http的原理就是。请求-响应过程,基本流程如下:

http

在一般响应的过程中,服务端一般不会阻塞,也就是说,服务端在收到客户端的请求后会立即进行处理,然后响应

数据给客户端,如果服务端响应时间过长,客户端就会出异常。但是,在http推送的过程中,客户端要和服务端保持一个连接,当服务端需要推送的时候,才把数据响应给客户端。这样就需要另外一个请求来通知服务端进行推送,基本流程如下:
http推送流程

基本流程详解:客户端给服务端发送一个请求,等待响应,服务端收到客户端的请求之后,并不会立即做出响应,而是会阻塞不动(因为http每一个请求都是一个线程)。在这个过程中,需要保证客户端不会出现time out异常(可以设置时间)。当有推送的客户端需要进行推送的时候,会向服务端发送一个推送的请求。服务端收到推送的请求后,解锁前面那个被阻塞的请求,并响应给客户端。客户端收到响应后要再次发送请求给服务端,依次循环。这样就达成了推送的效果。话不多说,直接上代码:

首先,新建一个web项目HttpPlusTest:
这里写图片描述

新建一个客户端servlet请求,用来处理来自客户端的请求,在这里,需要使用一个对象来使线程阻塞,为了让后面的请求可以解锁线程并设置推送内容,我们创造一个Map类型的对象,并将对象放入系统全局变量里面:

package com.sgcc.test;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class ClientPush extends HttpServlet {
    private static final long serialVersionUID = 1L;

    public ClientPush() {
        super();
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        //获取系统全局变量,在这里为了方便,暂时用Appliection代替。如果获取到的内容为空,则在下面new一个集合放进来,
        //这个集合里面存放了所有要被推送的内容的对象,暂时用Map代替,在实际使用的时候可以定义实体类。
        List<Map<String, String>> pushList = (List<Map<String, String>>) request.getSession().getServletContext()
                .getAttribute("PUSH");


        //使用这个对象的线程锁来让当前请求的线程暂停执行,并将这个对象放入全局变量,在需要推送的时候调用 这个对象的notify方法可以唤起线程
        Map<String, String> temp = new HashMap<String, String>();

        if (pushList == null) {
            List<Map<String, String>> list = new ArrayList<>();
            request.getSession().getServletContext().setAttribute("PUSH", list);
            list.add(temp);
        } else {
            pushList.add(temp);
        }

        //将对象和当前线程关联起来。在调用对象的wait()方法的时候,当前线程会阻塞
        synchronized (temp) {
            try {
                //使当前线程阻塞。等待其他线程唤醒它
                temp.wait();
                //被唤醒后,将内容发送给客户端,响应完成!
                response.getWriter().write(temp.get("pushContext"));
                response.getWriter().flush();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                if (pushList != null) {
                    pushList.remove(temp);
                }
            }

        }

    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }

}

当客户端请求到这个servlet的时候,会在44行代码的时候被阻塞,需要另外一个线程来唤醒这个线程,所以,我们还需要一个用来处理推送客户端的servlet 来遍历唤醒他:

package com.sgcc.test;

import java.io.IOException;
import java.util.Date;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class PutPush extends HttpServlet {
    private static final long serialVersionUID = 1L;

    public PutPush() {
        super();
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 获取当前系统所有待推送的请求
        List<Map<String, String>> pushList = (List<Map<String, String>>) request.getSession().getServletContext()
                .getAttribute("PUSH");
        if (pushList == null || pushList.size() == 0) {
            response.getWriter().write( "推送失败,");
            response.getWriter().flush();
            return;
        }

        // 遍历推送
        for (Map<String, String> map : pushList) {
            synchronized (map) {
                // 设置推送内容
                map.put("pushContext", new Date().toString() + "\n");
                map.notify();
            }
        }
        response.getWriter().write( "推送成功,共推送:" + pushList.size() + "个");
        response.getWriter().flush();
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }

}

最后还要在web.xml 中声明这两个servlet:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    id="WebApp_ID" version="2.5">
    <display-name>HttpPlusTest</display-name>
    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
        <welcome-file>index.htm</welcome-file>
        <welcome-file>index.jsp</welcome-file>
        <welcome-file>default.html</welcome-file>
        <welcome-file>default.htm</welcome-file>
        <welcome-file>default.jsp</welcome-file>
    </welcome-file-list>
    <servlet>
        <description></description>
        <display-name>ClientPush</display-name>
        <servlet-name>ClientPush</servlet-name>
        <servlet-class>com.sgcc.test.ClientPush</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>ClientPush</servlet-name>
        <url-pattern>/ClientPush</url-pattern>
    </servlet-mapping>
    <servlet>
        <description></description>
        <display-name>PutPush</display-name>
        <servlet-name>PutPush</servlet-name>
        <servlet-class>com.sgcc.test.PutPush</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>PutPush</servlet-name>
        <url-pattern>/PutPush</url-pattern>
    </servlet-mapping>
</web-app>

简易的客户端代码:

package com.brains.test;

import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;

public class TestHttp {

    public static void main(String[] args) throws Exception {
        URL url=new URL("http://127.0.0.1:7002/HttpPlusTest/ClientPush");
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.connect();
        connection.setConnectTimeout(0);
        InputStream in = connection.getInputStream();

        byte[] b=new byte[1024];

        int len=0;
        while((len=in.read(b))!=-1){
            System.out.println(new String(b));
        }

    }

}

执行这段客户端代码后,控制台暂时不会输出任何东西,然后在浏览器请求推送的时候,客户端会吧当前时间输出出来

ps:个人原创,转载请注明出处!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值