前几天项目要求实现安卓手机端的推送功能,由于原来的工程与服务端通信用的是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:个人原创,转载请注明出处!