When you encounter the demand of "background push", you can't avoid websocket. This time, the demand is a little special. The ip of the client is fixed. You need to distinguish the specific client according to the ip of the client.
However, for future use, I also list another way to obtain the identity of the connected user - the url parameter.
It should be noted that if your project adds an interceptor or or uses a security framework such as security, you need to release the path of websocket, otherwise it will always report connection failure.
catalogue
2. Define WebSocketConfigurator
rely on
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> <version>2.4.5</version> </dependency>
configuration file
@Configuration public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }
Main class
The main class manages the connection of websocket. The following two schemes are given:
Scheme 1: URL authentication
The following userNo can be replaced with a token in the actual project and authenticated in onOpen.
@Slf4j @Component @ServerEndpoint(value = "/websocket/{userNo}") public class WebSocketServer { private static int onlineCount = 0; private static ConcurrentHashMap<String, WebSocketServer> serverMap = new ConcurrentHashMap<>(); private Session session; private String userNo; @OnOpen public void onOpen(Session session, @PathParam("userNo") String userNo) { this.session = session; this.userNo = userNo; if(serverMap.containsKey(userNo)) { serverMap.remove(userNo); serverMap.put(userNo, this); }else { serverMap.put(userNo, this); addOnlineCount(); log.info(userNo + ",Online!"); } } /** * The server receives messages from the client * @param message news * @param session Session session */ @OnMessage public void onMessage(String message, Session session) { log.info("The server received a user request" + userNo + "Message from:" + message); } /** * The server actively sends messages * @param message news */ public void sendMessage(String message){ try { this.session.getBasicRemote().sendText(message); } catch (IOException e) { log.error(e.getMessage()); } } /** * Get online people * @return Number of people online */ public static int getOnlineCount() { return onlineCount; } @OnClose public void onClose() { if(serverMap.containsKey(userNo)) { serverMap.remove(userNo); subOnlineCount(); log.info(userNo + ",Offline!"); } } @OnError public void onError(Session session, Throwable throwable) { log.error("user" + userNo + "An error occurred as follows:" + throwable.getMessage()); } private static synchronized void subOnlineCount() { onlineCount--; } public static synchronized void addOnlineCount() { onlineCount++; } public static WebSocketServer get(String userNo) { return serverMap.get(userNo); } public static ConcurrentHashMap<String, WebSocketServer> getMap() { return serverMap; } public static boolean isOnline(String userNo) { return serverMap.containsKey(userNo); } }
Scheme 2: ip authentication
It is suitable for Intranet environment where the number of hosts is fixed and the ip address is fixed.
WebSocket is just a protocol. There are many ways to implement this protocol.
The starter we use cannot directly obtain the client ip. Many people on the Internet use the netty websocket XX package, which provides an api to obtain the client ip.
Changing the package is too troublesome. We can solve this problem even without changing the package.
1. Define an interceptor
This interceptor is used to obtain the ip and put it into the session
@javax.servlet.annotation.WebFilter(filterName = "sessionFilter",urlPatterns = "/*") @Order(1) public class WebFilter implements Filter { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest req= (HttpServletRequest) servletRequest; req.getSession().setAttribute("ip",req.getRemoteHost()); filterChain.doFilter(servletRequest,servletResponse); } }
2. Define WebSocketConfigurator
It is used to pass the ip of the client to the session in websocket, which is equivalent to an intermediary
public class WebSocketConfigurator extends ServerEndpointConfig.Configurator { public static final String IP_ADDR = "IP.ADDR"; @Override public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) { Map<String, Object> attributes = sec.getUserProperties(); HttpSession session = (HttpSession) request.getHttpSession(); if (session != null) { attributes.put(IP_ADDR, session.getAttribute("ip")); Enumeration<String> names = session.getAttributeNames(); while (names.hasMoreElements()) { String name = names.nextElement(); attributes.put(name, session.getAttribute(name)); } } } }
3. Define principal class
The principal class is used to manage websocket connections and configure the configurator
@Slf4j @Component @ServerEndpoint(value = "/websocket",configurator = WebSocketConfigurator.class) public class WebSocketServer { private static int onlineCount = 0; private static ConcurrentHashMap<String, WebSocketServer> serverMap = new ConcurrentHashMap<>(); private Session session; private String ipAddr; @OnOpen public void onOpen(Session session) { this.session = session; Map<String, Object> userProperties = session.getUserProperties(); this.ipAddr = (String) userProperties.get(WebSocketConfigurator.IP_ADDR); if(serverMap.containsKey(this.ipAddr)) { serverMap.remove(this.ipAddr); serverMap.put(this.ipAddr, this); }else { serverMap.put(this.ipAddr, this); addOnlineCount(); log.info(this.ipAddr + ",Online!"); } } /** * The server receives messages from clients * @param message news * @param session Session session */ @OnMessage public void onMessage(String message, Session session) { log.info("The server received a user request" + ipAddr + "Message from:" + message); //Convenient front-end test sendMessage("The server received a user request" + ipAddr + "Message from:" + message); } /** * Send a message to the client with ip address ipAddr * @param ipAddr ip address * @param message news */ public static void sendMessage(String ipAddr, String message) { if(serverMap.containsKey(ipAddr)) { WebSocketServer webSocketServer = serverMap.get(ipAddr); webSocketServer.sendMessage(message); }else { log.error("Sending failed, client not connected: " + ipAddr); } } /** * The server actively sends messages * @param message news */ public void sendMessage(String message){ try { this.session.getBasicRemote().sendText(message); } catch (IOException e) { log.error(e.getMessage()); } } /** * Get online people * @return Number of people online */ public static int getOnlineCount() { return onlineCount; } @OnClose public void onClose() { if(serverMap.containsKey(ipAddr)) { serverMap.remove(ipAddr); subOnlineCount(); log.info(ipAddr + ",Offline!"); } } @OnError public void onError(Session session, Throwable throwable) { log.error("user" + ipAddr + "An error occurred as follows:" + throwable.getMessage()); } private static synchronized void subOnlineCount() { onlineCount--; } public static synchronized void addOnlineCount() { onlineCount++; } public static WebSocketServer get(String ipAddr) { return serverMap.get(ipAddr); } public static ConcurrentHashMap<String, WebSocketServer> getMap() { return serverMap; } public static boolean isOnline(String ipAddr) { return serverMap.containsKey(ipAddr); } }
4. Interceptor scan
If there is only the above code, the tomcat session cannot be obtained because the interceptor does not take effect.
Add an annotation @ ServletComponentScan on the SpringBoot startup class:
@EnableScheduling @SpringBootApplication @ServletComponentScan("The package name of the interceptor") public class WmsServerApplication { public static void main(String[] args) { SpringApplication.run(WmsServerApplication.class, args); } }