基于servlet3实现长轮询

来源:互联网 发布:go语言编程 pdf 编辑:程序博客网 时间:2024/05/17 08:00

你可能有这样的需求:

客户端需要持续访问服务端获取变化的数据,但是拉取变化的数据这个动作可能比较费时,所以并不能立刻返回结果。如果使用普通的同步请求客户端的连接会一直阻塞在服务端,服务端持有的连接也会越来越多,性能极差。

短链接在请求耗时比较长的场景中效果很差,那用什么方式呢?长连接?长连接建立tcp三次握手后就会和服务器端一直保持连接直到服务端断开连接,但是连接状态会一直保存在web容器中。

使用长轮询更加合理,servlet3支持异步servlet,在服务器端阻塞请求,释放容器线程,在数据准备好之后再返回给客户端。这样客户端请求是同步的而服务端是异步的。

代码模拟示例:实现一个客户端不同用户去拉取数据设置数据,服务端接收设置数据请求并发送到各个客户端的场景。很像一个聊天室。

import javax.servlet.AsyncContext;import javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.io.PrintWriter;import java.text.DateFormat;import java.text.SimpleDateFormat;import java.util.*;import java.util.concurrent.ConcurrentHashMap;/** * @author <a href="mailto:liuchangqing@youku.com">liuchangqing</a> * @date 2017/8/2. */@WebServlet(name = "asyServlet",urlPatterns = "/asyServlet.do",asyncSupported = true)public class AsyServlet extends HttpServlet{    //存储请求的所有客户端    private static final Map<String,GetClient> clients = new ConcurrentHashMap<String,GetClient>();    static {        //启动一个线程,扫描,关闭所有超时的连接        new Timer().schedule(new TimerTask() {            @Override            public void run() {                long current = System.currentTimeMillis();                for (Map.Entry<String, GetClient> entry : clients.entrySet()) {                    GetClient client = entry.getValue();                    if (client == null)                        return;                    if (current - client.time > 20 * 1000) {                        client.complete();                        clients.remove(entry.getKey());                    }                }            }        }, 10, 5000);    }    @Override    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {        String action = req.getParameter("action");        if("get".equals(action)){//拉数据            final AsyncContext asyncContext = req.startAsync();            asyncContext.setTimeout(0L);            String uid = req.getParameter("uid");            clients.put(uid,new GetClient(asyncContext,uid));        }else if("set".equals(action)){//设置数据            String msg = req.getParameter("msg");            sendMsg(msg);            resp.getWriter().print("set success");        }    }    //消息发送给所有客户端    private void sendMsg(String msg) {        Iterator<String> users = clients.keySet().iterator();        while (users.hasNext()){            GetClient cl = clients.get(users.next());            if(cl != null) cl.response(msg);        }    }}class GetClient{    public AsyncContext asyncContext;    public String uid;    public long time;    public GetClient(AsyncContext asyncContext, String uid) {        this.asyncContext = asyncContext;        this.uid = uid;        this.time = System.currentTimeMillis();    }    //关闭连接    public void complete() {        response("connect end");        asyncContext.complete();    }    public static DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");    //响应    public void response(String message) {        Map<String, Object> content = new HashMap<String, Object>();        content.put("messages", message);        try {            HttpServletResponse response = (HttpServletResponse) asyncContext.getResponse();            PrintWriter writer = response.getWriter();            response.setHeader("Content-type", "text/html;charset=UTF-8");            writer.println(format.format(new Date()) + ",receive:" + content + "<br/>");            writer.flush();        } catch (Exception se) {        }    }}

发送数据


拉取数据



例子还是比较经典的。

好久没有更新博客了,在农药被人追着打的同时也要坚持写写博客,嘿嘿。







原创粉丝点击