SpringBoot整合WebSocket实战

WebSocket是一种在TCP连接上进行全双工通信的协议,建立客户端和服务器之间的通信渠道。浏览器和服务器仅需一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

SpringBoot整合WebSocket实战_第1张图片

WebSocket的实现方式有很多所以网上的文章都有些不一样,推荐使用第一种和第二种

方式一:原生jdk注解

 1.pom先导入依赖

        
            org.springframework.boot
            spring-boot-starter-websocket
        

2.创建WebSocket配置类,通过这个配置才能去扫描WebSocket的注解

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**
 * @Description WebSocket配置类
 */
@Configuration
public class WebSocketConfig {
    /**
     * 	注入ServerEndpointExporter,
     * 	这个bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

 3.创建WebSocket服务

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;

import org.springframework.stereotype.Component;

/**
 * @Description WebSocket服务
 */

@Component
@ServerEndpoint("/websocket/{userId}")  // 接口路径 ws://localhost:8080/websocket/1
public class WebSocket {

    private static final Logger log = LoggerFactory.getLogger(WebSocket.class);

    //与某个客户端的连接会话,需要通过它来给客户端发送数据
    private Session session;

    /**
     * 用户ID
     */
    private String userId;

    //concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
    //虽然@Component默认是单例模式的,但springboot还是会为每个websocket连接初始化一个bean,所以可以用一个静态set保存起来。
    //  注:底下WebSocket是当前类名
    private static final CopyOnWriteArraySet webSockets = new CopyOnWriteArraySet<>();
    // 用来存在线连接用户信息
    private static final ConcurrentHashMap sessionPool = new ConcurrentHashMap();

    /**
     * 链接成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam(value = "userId") String userId) throws InterruptedException {
        try {
            this.session = session;
            this.userId = userId;
            webSockets.add(this);
            sessionPool.put(userId, session);
            log.info(session.getId());
            log.info("【websocket消息】有新的连接,总数为:" + webSockets.size());
        } catch (Exception e) {
            e.printStackTrace();
        }
        //响应消息
        session.getAsyncRemote().sendText("连接成功");

        }

    }

    /**
     * 链接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        try {
            webSockets.remove(this);
            sessionPool.remove(this.userId);
            log.info("【websocket消息】连接断开,总数为:" + webSockets.size());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message
     */
    @OnMessage
    public void onMessage(String message) {
        log.info("【websocket消息】收到客户端消息:" + message);
    }

    /**
     * 发送错误时的处理
     *
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("用户错误,原因:" + error.getMessage());
        error.printStackTrace();
    }


    // 此为广播消息
    public void sendAllMessage(String message) {
        log.info("【websocket消息】广播消息:" + message);
        for (WebSocket webSocket : webSockets) {
            try {
                if (webSocket.session.isOpen()) {
                    webSocket.session.getAsyncRemote().sendText(message);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    // 此为单点消息
    public void sendOneMessage(String userId, String message) {
        Session session = sessionPool.get(userId);
        if (session != null && session.isOpen()) {
            try {
                log.info("【websocket消息】 单点消息:" + message);
                session.getAsyncRemote().sendText(message);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    // 此为单点消息(多人)
    public void sendMoreMessage(String[] userIds, String message) {
        for (String userId : userIds) {
            Session session = sessionPool.get(userId);
            if (session != null && session.isOpen()) {
                try {
                    log.info("【websocket消息】 单点消息:" + message);
                    session.getAsyncRemote().sendText(message);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

    }


    // 调用 :
    //@Resource
    //private WebSocket webSocket;
    public void sendTest() {
        //创建业务消息信息
        JSONObject obj = new JSONObject();
        obj.put("cmd", "topic");//业务类型
        obj.put("msgId", "messageid");//消息id
        obj.put("msgTxt", "内容");//消息内容
        //全体发送
        sendAllMessage(obj.toJSONString());
        //单个用户发送 (userId为用户id)
        sendOneMessage(userId, obj.toJSONString());
        //多个用户发送 (userIds为多个用户id,逗号‘,’分隔)
        sendMoreMessage(new String[]{"1"}, obj.toJSONString());

    }

}

 前端调用接口路径 ws://localhost:8080/websocket/1

 方式二:SpringBoot封装

1.在项目的pom.xml文件中,添加以下依赖:


    org.springframework.boot
    spring-boot-starter-websocket

2.创建一个WebSocket配置类,用于配置WebSocket相关的拦截路径参数和处理器。

@Configuration
@EnableWebSocket //启用WebSocket支持
public class WebSocketConfig implements WebSocketConfigurer {
 
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        //auth用于鉴权处理 openid用户的id
registry.addHandler(webSocketHandler(),"/netgate/{auth}/{openid}") //注册Handler
           .addInterceptors(new WebSocketHandshakeInterceptor())  //握手过滤器注册Interceptor
           .setAllowedOrigins("*"); //关闭跨域校验

        //注册SockJS,提供SockJS支持(主要是兼容ie8) 
        registry.addHandler(myHandler(), "/my-websocket-url") //注册Handler
        .withSockJS(); //支持sockjs协议
    }

    @Bean
    public ServletServerContainerFactoryBean createWebSocketContainer() {
        ServletServerContainerFactoryBean container = new                     
        ServletServerContainerFactoryBean();
        container.setMaxTextMessageBufferSize(2*1024*1024);//8192*1024 1024*1024*1024
        container.setMaxBinaryMessageBufferSize(2*1024*1024);
        container.setAsyncSendTimeout(55000l);
        container.setMaxSessionIdleTimeout(55000l);//心跳
        return container;
    }

    @Bean
    public TextWebSocketHandler webSocketHandler() {
        return new NetgateHandler();
    }
 
    @Bean
    public WebSocketHandler myHandler() {
        return new MyWebSocketHandler();
    }
 
}

 3.Websocket握手过滤器

public class WebSocketHandshakeInterceptor implements HandshakeInterceptor {
    private final static Logger LOGGER = LoggerFactory.getLogger(WebSocketHandshakeInterceptor.class);
 
    /**
     * 握手前
     */
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Map attributes) throws Exception {
        if (request instanceof ServletServerHttpRequest) {
        	String path = request.getURI().getPath();
        	if(requestIsValid(path)){
        		String[] params = getParams(path);
        		attributes.put("WEBSOCKET_AUTH", params[0]);
        		attributes.put("WEBSOCKET_OPENID", params[1]);
        		attributes.put("WEBSOCKET_FIRSTONE","yes");
        	}
        }
        System.out.println("================Before Handshake================");
        return true;
    }
 
    /**
     * 握手后
     */
    public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) {
    	System.out.println("================After Handshake================");
    	if(e!=null) e.printStackTrace();
    	System.out.println("================After Handshake================");
    }
    
    private boolean requestIsValid(String url){
        //在这里可以写上具体的鉴权逻辑
    	boolean isvalid = false;
    	if(StringUtils.isNotEmpty(url)
    			&& url.startsWith("/netgate/")
    			&& url.split("/").length==6){
    		isvalid = true;
    	}
    	return isvalid;
    }
    
    private String[] getParams(String url){
    	url = url.replace("/netgate/","");
    	return url.split("/");
    }
    
}

 4.创建一个WebSocket处理器类,用于处理WebSocket的连接、消息和事件,有两种接口实现方式。

TextWebSocketHandler:文本内容

BinaryWebSocketHandler:二进制内容

/**
 * Websocket处理器
 */
@Component
public class NetgateHandler extends TextWebSocketHandler {

	@Autowired
	private MqttGateway mqttGateway;
	
    
    /*
	 * 网关连接集合
	 * 第一级:设备序列号 sn
	 * 第二级:用户唯一标识 openid
	 */
	private static ConcurrentHashMap> netgates = new ConcurrentHashMap>();

    
    /**
     * 处理前端发送的文本信息
     * js调用websocket.send时候,会调用该方法
     * WebSocketSession代表每个客户端会话
     */
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
    	if(!session.isOpen()) {
			System.out.println("连接已关闭,不再处理该连接的消息!");
			return;
		}
    	String mes = ObjectUtils.toString(message.getPayload(),"");
    	String pid = session.getAttributes().get("WEBSOCKET_PID").toString();
    	String sn = session.getAttributes().get("WEBSOCKET_SN").toString();
		if(message==null || "".equals(mes)){
			System.out.println(getSysDate()+"============接收到空消息,不予处理。");
		}else if(mes.length()==1){
			//心跳消息过滤掉
			return;
		}else {
			//转发成mqtt消息
			String topic = "pay/"+pid+"/server/"+sn;
			System.out.println(topic);
			System.out.println(getSysDate()+"============消息处理完成:"+mes);
			mqttGateway.sendToMqtt(topic,mes);
		}
    }
 
 
    /**
     * 当新连接建立的时候,被调用
     * 连接成功时候,会触发页面上onOpen方法
     *
     * @param session
     * @throws Exception
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
    	System.out.println(getSysDate()+"============正在初始化连接:"+session.getId());
        try {
            //初始化连接,把session存储起来
			this.initUsers(session);
		} catch (Exception e) {
			System.out.println(getSysDate()+"============初始化连接异常-开始:"+e.getMessage());
			e.printStackTrace();
			System.out.println(getSysDate()+"============初始化连接异常-结束:"+e.getMessage());
		}
        System.out.println(getSysDate()+"============初始化连接完成:"+session.getId());
    }
 
    /**
     * 当连接关闭时被调用
     *
     * @param session
     * @param status
     * @throws Exception
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
    	System.out.println(getSysDate()+"============正在关闭连接:"+session.getId()+",isOpen:"+session.isOpen()+";code:"+status.getCode());
    	try {
    		System.out.println("断开连接状态值"+status.getCode());
			this.removeSession(session);
		} catch (Exception e) {
			System.out.println(getSysDate()+"============关闭连接异常-开始:"+e.getMessage());
			e.printStackTrace();
			System.out.println(getSysDate()+"============关闭连接异常-结束:"+e.getMessage());
		}
    	System.out.println(getSysDate()+"============正在关闭完成:"+session.getId()+",isOpen:"+session.isOpen()+";code:"+status.getCode());
    }
 
    /**
     * 传输错误时调用
     *
     * @param session
     * @param exception
     * @throws Exception
     */
    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
    	System.out.println(getSysDate()+"============发生传输错误:"+session.getId()+";session.isOpen():"+session.isOpen()+";exception:"+exception.getMessage());
    	exception.printStackTrace();
    	if (session.isOpen()) {
        	//try { session.close(); } catch (Exception e) {e.printStackTrace();}
        }else {
        	try {
    			this.removeSession(session);
    		} catch (Exception e) {
    			System.out.println(getSysDate()+"============传输错误处理异常-开始:"+e.getMessage());
    			e.printStackTrace();
    			System.out.println(getSysDate()+"============传输错误处理异常-结束:"+e.getMessage());
    		}
        }
    	System.out.println(getSysDate()+"============错误处理结束:"+session.getId()+";session.isOpen():"+session.isOpen()+";exception:"+exception.getMessage());
    }


	public synchronized void sendMsgToNetgateSn(String sn, String msg)  {
		if(netgates.size()>0 && netgates.containsKey(sn) && !netgates.get(sn).isEmpty()){
			//获取EID对应的后台管理连接 多个
			for (WebSocketSession ws: netgates.get(sn).values()){
				System.out.println("对网关指令开始发送啦:sn="+sn+"消息内容"+msg);
				try {ws.sendMessage(new TextMessage(msg));} catch (IOException e) {System.out.println(getSysDate()+"发生了异常:"+e.getMessage());e.printStackTrace();continue;}
			}
		}
	}
    
	//连接接入的处理方法
	private synchronized void initUsers(WebSocketSession session){
		String pid = (String) session.getAttributes().get("WEBSOCKET_PID");
		String sn = (String) session.getAttributes().get("WEBSOCKET_SN");
		String openid = (String) session.getAttributes().get("WEBSOCKET_OPENID");
		if(StringUtils.isNotEmpty(pid) && StringUtils.isNotEmpty(sn) && StringUtils.isNotEmpty(openid)){
			ConcurrentHashMap netgate = netgates.get(sn);
			if(netgate == null){
				netgate = new ConcurrentHashMap();
			}
			WebSocketSession session_exist = netgate.get(sn);
			if(session_exist!=null) {
				System.out.println("检测到相同SN重复连接,SN:"+sn+",连接ID:"+session_exist.getId()+",准备清理失效的连接。。。");
				try {session_exist.close();} catch (IOException e) {e.printStackTrace();}
			}
			netgate.putIfAbsent(openid, session);
			netgates.put(sn,netgate);
		}
	}
    
	//连接被关闭时处理集合
	private synchronized void removeSession(WebSocketSession session){
		String sn = (String) session.getAttributes().get("WEBSOCKET_SN");
		String openid = (String) session.getAttributes().get("WEBSOCKET_OPENID");
		if(netgates.get(sn).containsKey(openid)) {
			WebSocketSession exist_session = netgates.get(sn).get(openid);
			//确保是同一个session 不是同一个session则不应该进行下一步的处理
			if(exist_session.getId()!=null && exist_session.getId().equals(session.getId())) {
				netgates.get(sn).remove(openid);
				System.out.println("有一网关连接关闭!SN:"+sn+",当前在线数量为"+netgates.get(sn).keySet().size());
			}else {
				System.out.println("检测到关闭session异常,程序中断处理,关闭sessionId:"+session.getId()+",当前实际sessionId:"+exist_session.getId());
			}
		}else {
			System.out.println("检测到关闭session异常,程序中断处理,系统中未找到对应的session,Sn="+sn+"openid="+openid);
		}
	}
	
	private String getSysDate() {
		 SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式
        return df.format(new Date());
	}
}

public class MyWebSocketHandler extends TextWebSocketHandler {
 
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        // 在WebSocket连接建立时执行的逻辑
    }
 
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        // 处理接收到的文本消息
    }
 
    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        // 处理传输错误事件
    }
 
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        // 在WebSocket连接关闭时执行的逻辑
    }
}

5.Controller中可以通过@Autowired注入WebSocketHandler,并在方法中调用处理器的方法来与WebSocket进行交互。

 方式三:TIO

1.引入pom依赖配置application

 
     org.t-io
     tio-websocket-spring-boot-starter
     3.5.5.v20191010-RELEASE
tio:
  websocket:
    server:
      port: 8989

2.实现handler

@Component
public class MyHandler implements IWsMsgHandler {
    /**
     * 握手
     *
     * @param httpRequest
     * @param httpResponse
     * @param channelContext
     * @return
     * @throws Exception
     */
    @Override
    public HttpResponse handshake(HttpRequest httpRequest, HttpResponse httpResponse, ChannelContext channelContext) throws Exception {
        return httpResponse;
    }

    /**
     * 握手成功
     *
     * @param httpRequest
     * @param httpResponse
     * @param channelContext
     * @throws Exception
     */
    @Override
    public void onAfterHandshaked(HttpRequest httpRequest, HttpResponse httpResponse, ChannelContext channelContext) throws Exception {
        System.out.println("握手成功");
    }

    /**
     * 接收二进制文件
     *
     * @param wsRequest
     * @param bytes
     * @param channelContext
     * @return
     * @throws Exception
     */
    @Override
    public Object onBytes(WsRequest wsRequest, byte[] bytes, ChannelContext channelContext) throws Exception {
        return null;
    }

    /**
     * 断开连接
     *
     * @param wsRequest
     * @param bytes
     * @param channelContext
     * @return
     * @throws Exception
     */
    @Override
    public Object onClose(WsRequest wsRequest, byte[] bytes, ChannelContext channelContext) throws Exception {
        System.out.println("关闭连接");
        return null;
    }

    /**
     * 接收消息
     *
     * @param wsRequest
     * @param s
     * @param channelContext
     * @return
     * @throws Exception
     */
    @Override
    public Object onText(WsRequest wsRequest, String s, ChannelContext channelContext) throws Exception {
        System.out.println("接收文本消息:" + s);
        return "success";
    }
}

3.主启动类加上开启注解

@EnableTioWebSocketServer

方式四:STOMP 

1.引入maven坐标


  org.springframework.boot
  spring-boot-starter-websocket

 2.加入配置类

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        // 配置客户端尝试连接地址
        registry.addEndpoint("/ws").setAllowedOrigins("*").withSockJS();
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        // 设置广播节点
        registry.enableSimpleBroker("/topic", "/user");
        // 客户端向服务端发送消息需有/app 前缀
        registry.setApplicationDestinationPrefixes("/app");
        // 指定用户发送(一对一)的前缀 /user/
        registry.setUserDestinationPrefix("/user/");
    }
}

3.添加处理器

@Controller
public class WSController {

    @Autowired
    private SimpMessagingTemplate simpMessagingTemplate;

    @MessageMapping("/hello")
    @SendTo("/topic/hello")
    public ResponseMessage hello(RequestMessage requestMessage) {
        System.out.println("接收消息:" + requestMessage);
        return new ResponseMessage("服务端接收到你发的:" + requestMessage);
    }

    @GetMapping("/sendMsgByUser")
    public @ResponseBody
    Object sendMsgByUser(String token, String msg) {
        simpMessagingTemplate.convertAndSendToUser(token, "/msg", msg);
        return "success";
    }

    @GetMapping("/sendMsgByAll")
    public @ResponseBody
    Object sendMsgByAll(String msg) {
        simpMessagingTemplate.convertAndSend("/topic", msg);
        return "success";
    }

    @GetMapping("/test")
    public String test() {
        return "test-stomp.html";
    }
}

在线接口测试:

websocket在线测试 (websocket-test.com)

在线websocket测试-在线工具-postjson (coolaf.com)

下面是两个前端的示例 



	
		
		
		
		本地websocket测试
		
		
		
		
	
	
		
		


    
    
    
    
    WebSocket Examples: Reverse
    
    
    
    
    


    
    
Echo

单工消息推送 SSE

SSE 是基于 HTTP 协议的;单向通信,只能由服务端向客户端单向通信;默认支持断线重连;只能传送文本消息;不支持IE;

SpringBoot整合WebSocket实战_第2张图片

sse 规范
在 html5 的定义中,服务端 sse,一般需要遵循以下要求

请求头
开启长连接 + 流方式传递
Content-Type: text/event-stream;charset=UTF-8
Cache-Control: no-cache
Connection: keep-alive

数据格式
服务端发送的消息,由 message 组成,其格式: field:value\n\n

其中 field 有五种可能

空: 即以:开头,表示注释,可以理解为服务端向客户端发送的心跳,确保连接不中断
data:数据。订阅后,服务端在消息可用时立即发送给客户端。事件是采用 UTF-8 编码的文本消息。事件之间由两个换行符分隔\n\n。每个事件由一个或多个名称:值字段组成,由单个换行符\n 分隔。 ​
event: 事件,默认值
id: 数据标识符 id 字段表示,相当于每一条数据的编号 。服务器可以发送唯一的事件标识符(id字段)。如果连接中断,客户端会自动重新连接并发送最后接收到的带有header的 Last-Event-ID 的事件 ID。 ​
retry: 重连时间 ,在重试字段中,服务器可以发送超时(以毫秒为单位),之后客户端应在连接中断时自动重新连接。如果未指定此字段,则标准应为 3000 毫秒。
 

 SSE工具类

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;

@Slf4j
public class SSEServer {

    /**
     * 当前连接数
     */
    private static AtomicInteger count = new AtomicInteger(0);

    private static Map sseEmitterMap = new ConcurrentHashMap<>();

    public static SseEmitter connect(String userId){
        //设置超时时间,0表示不过期,默认是30秒,超过时间未完成会抛出异常
        SseEmitter sseEmitter = new SseEmitter(0L);
        //注册回调
        sseEmitter.onCompletion(completionCallBack(userId));
        sseEmitter.onError(errorCallBack(userId));
        sseEmitter.onTimeout(timeOutCallBack(userId));
        sseEmitterMap.put(userId,sseEmitter);
        //数量+1
        count.getAndIncrement();
        log.info("create new sse connect ,current user:{}",userId);
        return sseEmitter;
    }
    /**
     * 给指定用户发消息
     */
    public static void sendMessage(String userId, String message){
        if(sseEmitterMap.containsKey(userId)){
            try{
                sseEmitterMap.get(userId).send(message);
            }catch (IOException e){
                log.error("user id:{}, send message error:{}",userId,e.getMessage());
                e.printStackTrace();
            }
        }
    }

    /**
     * 想多人发送消息,组播
     */
    public static void groupSendMessage(String groupId, String message){
        if(sseEmitterMap!=null&&!sseEmitterMap.isEmpty()){
            sseEmitterMap.forEach((k,v) -> {
                try{
                    if(k.startsWith(groupId)){
                        v.send(message, MediaType.APPLICATION_JSON);
                    }
                }catch (IOException e){
                    log.error("user id:{}, send message error:{}",groupId,message);
                    removeUser(k);
                }
            });
        }
    }
    public static void batchSendMessage(String message) {
        sseEmitterMap.forEach((k,v)->{
            try{
                v.send(message,MediaType.APPLICATION_JSON);
            }catch (IOException e){
                log.error("user id:{}, send message error:{}",k,e.getMessage());
                removeUser(k);
            }
        });
    }
    /**
     * 群发消息
     */
    public static void batchSendMessage(String message, Set userIds){
        userIds.forEach(userId->sendMessage(userId,message));
    }
    public static void removeUser(String userId){
        sseEmitterMap.remove(userId);
        //数量-1
        count.getAndDecrement();
        log.info("remove user id:{}",userId);
    }
    public static List getIds(){
        return new ArrayList<>(sseEmitterMap.keySet());
    }
    public static int getUserCount(){
        return count.intValue();
    }
    private static Runnable completionCallBack(String userId) {
        return () -> {
            log.info("结束连接,{}",userId);
            removeUser(userId);
        };
    }
    private static Runnable timeOutCallBack(String userId){
        return ()->{
            log.info("连接超时,{}",userId);
            removeUser(userId);
        };
    }
    private static Consumer errorCallBack(String userId){
        return throwable -> {
            log.error("连接异常,{}",userId);
            removeUser(userId);
        };
    }
}

Controller层

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import vip.huhailong.catchat.sse.SSEServer;

@Slf4j
@RestController
@CrossOrigin
@RequestMapping("/sse")
public class SSEController {

    @GetMapping(value = "/connect/{userId}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public SseEmitter connect(@PathVariable String userId){
        return SSEServer.connect(userId);
    }

    @GetMapping("/process")
    public void sendMessage() throws InterruptedException {
        for(int i=0; i<=100; i++){
            if(i>50&&i<70){
                Thread.sleep(500L);
            }else{
                Thread.sleep(100L);
            }
            SSEServer.batchSendMessage(String.valueOf(i));
        }
    }
}

前端




    
    Home
    


    

nginx 配置 proxy_buffering off
不配置proxy_buffering off的话,会出现请求发出后,接口收到直接返回,无法保持长连接。
参考网上说明:proxy_buffering这个参数用来控制是否打开后端响应内容的缓冲区,如果这个设置为off,那么proxy_buffers和proxy_busy_buffers_size这两个指令将会失效。
 

你可能感兴趣的:(SpringBoot,websocket,网络协议,网络,java,spring,boot,http)