SSE(server-sent event)结合AsyncContext实现点对点推送消息

本文详细介绍了在项目重构过程中采用Server-Sent Events (SSE)替代WebSocket进行计数统计的实践经验。通过区域匹配实现点对点通信,利用AsyncContext进行高效数据推送,附带具体代码示例及关键链接。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.为什么用SSE?

最近对原项目进行重构,在计数统计这块,原来项目用的是websocket,这个太复杂了,没有必要,就使用了SSE。

 

2.和其他网上文章的不同之处在于:

因为是用在项目,用区域对点对点进行了匹配,实际一点。用的是AsyncContext。

3.参考链接:

阮一峰的Server-Sent Events 教程:http://www.ruanyifeng.com/blog/2017/05/server-sent_events.html

一般实现:https://blog.youkuaiyun.com/ocean_fan/article/details/79225203

关于长连接、跳出循环:https://www.jianshu.com/p/100b82730e15

点对点原理:https://www.jianshu.com/p/573241b4dbd2

更多AsyncContext细节:https://book.2cto.com/201301/14823.html

 

4.代码

实现的原理:客户端直接在页面发起请求,在服务端接收后,存储到AsyncContext的list,在实际产生要推动的数据后,调用接口,判断标识(区域id,你也可以定用户id等)进行AsyncContext发送。

页面

<!DOCTYPE html>
<html>
  <head>
    <title>sse.html</title>
	
    <meta name="keywords" content="keyword1,keyword2,keyword3">
    <meta name="description" content="this is my page">
    <meta name="content-type" content="text/html; charset=UTF-8">
    
    <!--<link rel="stylesheet" type="text/css" href="./styles.css">-->

  </head>
  
  <body>
    This is my HTML page. <br>
    <input type="button" id="connect" value="Connect" /><br />
  <span id="status">Connection closed!</span><br />
  <span id="connecttime"></span><br />
  <span id="output"></span>
	<div id="msg_from_server">12222</div>
  </body>
  <script type="text/javascript">
 
	if (!!window.EventSource) {
	 	var output = document.getElementById("output");
		var source = new EventSource('http://localhost:8080/api/sse/push?areaId=2'); //为http://localhost:8080/testSpringMVC/push
		s = '';
		source.addEventListener('message', function(e) {
			s += e.data + "<br/>";
			document.getElementById("output").innerText=s;
		});
		source.addEventListener('open', function(e) {
			output.textContent = "连接打开.";
		}, false);
		source.addEventListener('error', function(e) {
			if (e.readyState == EventSource.CLOSED) {					
				output.textContent = "连接关闭";				
			} else {
				console.log(e.readyState);				
			}
		}, false);
	} else {
		alert(4);
		console.log("没有sse");
	}

  </script>
</html>

controller


import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

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

import javax.servlet.AsyncContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/**
 * @author: sw17028
 * @date:2018年12月20日 上午11:02:29
 * @version :
 * 
 */
@Controller
//@WebServlet(urlPatterns = {"/api/sse" }, asyncSupported = true)
@RequestMapping(value="/api/sse")
public class SSEController {
	public static List<AsyncContext> actxList =new ArrayList<AsyncContext>();
	//存储区域和通道信息
	public static Map<HttpSession, AsyncContext> sseMap = new HashMap<HttpSession,AsyncContext>();
	
	@RequestMapping(value = "/push", produces = "text/event-stream"
//			+ ";charset=UTF-8"
			+ "")	
	public @ResponseBody void push(HttpServletRequest request, HttpServletResponse response) {
		

		response.setContentType("text/event-stream");
		response.setHeader("Cache-Control", "no-cache");
		response.setHeader("Connection", "keep-alive");
		response.setCharacterEncoding("UTF-8");
		
		request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", true);
		AsyncContext actx = request.startAsync(request,response);
        actx.setTimeout(600000);
        actx.addListener(new AsyncListener() {
            @Override
            public void onComplete(AsyncEvent asyncEvent) throws IOException {
                // TODO Auto-generated method stub
//                System.out.println("zxczxc");
            }

            @Override
            public void onError(AsyncEvent arg0) throws IOException {
                // TODO Auto-generated method stub
//                System.out.println("[echo]event has error");
            }

            @Override
            public void onStartAsync(AsyncEvent arg0) throws IOException {
                // TODO Auto-generated method stub
//                System.out.println("[echo]event start:" + arg0.getSuppliedRequest().getRemoteAddr());
            }

            @Override
            public void onTimeout(AsyncEvent arg0) throws IOException {
                // TODO Auto-generated method stub
//                System.out.println(arg0);
            }
        });
        actxList.add(actx);
}

}

代码中保留了一些无用的注释,可以给大家做一些和其他文章的参考对照,不需要的可以直接删除。

调用接口:public void sendMessageByArea(Integer areaId, String message);

@Override
	public void sendMessageByArea(Integer areaId, String message) {
		// TODO Auto-generated method stub
		for(AsyncContext asyncContext : SSEController.actxList){
			//如果文中包含areaid,则发送
			if(asyncContext.getTimeout()<0){
					SSEController.actxList.remove(asyncContext);

					System.out.println("客户端断开连接");
				}
				else if(areaId.toString().equals(
					asyncContext.getRequest().getParameter("areaId"))
					){
				try {
					Thread.sleep(1000);
					PrintWriter pw = asyncContext.getResponse().getWriter();
					if(pw.checkError()){
							SSEController.actxList.remove(asyncContext);
							System.out.println("客户端断开连接");
							return;
						}
					pw.write("data:"+message + "\n\n");
					pw.flush();
					
				}
				catch (InterruptedException e) {
					e.printStackTrace();
				}
				catch (IOException e) {
					e.printStackTrace();
					return;
				}
			}
		}
	}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值