Four solutions for integrating Websocket

1. Native annotations

pom.xml

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>Copy the code

WebSocketConfig

/* * * * * blog.coder4j.cn * * Copyright (C) 2016-2019 All Rights Reserved. * */ package cn.coder4j.study.example.websocket.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.config.annotation.EnableWebSocket; import org.springframework.web.socket.server.standard.ServerEndpointExporter; /** * @author buhao * @version WebSocketConfig.java, V 0.1 2019-10-18 15:45 buhao */ @configuration @enableWebSocket public class WebSocketConfig {@bean public ServerEndpointExporter serverEndpoint() { return new ServerEndpointExporter(); }}Copy the code
Description:

The configuration class is simple enough to enable Spring Boot to scan the webSocket annotations behind

WsServerEndpoint

/* * * * * blog.coder4j.cn * * Copyright (C) 2016-2019 All Rights Reserved. * */ package cn.coder4j.study.example.websocket.ws; import org.springframework.stereotype.Component; import javax.websocket.OnClose; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; import java.io.IOException; import java.util.HashMap; import java.util.Map; /** * @author buhao * @version WsServerEndpoint.java, V 0.1 2019-10-18 16:06 Component public class WsServerEndpoint {/** * Connect to ServerEndpoint ** @param session */ @onOpen public void OnOpen (session session) {system.out.println (" Connection succeeded "); } /** * connection closed ** @param session */ @onclose public void OnClose (session session) {system.out.println (" connection closed "); } /** * When a message is received ** @param text */ @onMessage public String onMsg(String text) throws IOException {return "Servet sends: " + text; }}Copy the code

instructions

There are a few notes to note here, the first being that their packages are all under Javax.webSocket. They are not provided by Spring, but come with the JDK. Here’s what they do.

  1.  @ServerEndpoint
    1. Spring Boot lets you know the path of your exposed WS application, similar to @requestMapping we often use. For example, if your startup port is 8080 and the annotation value is ws, we can connect to your application via ws://127.0.0.1:8080/ws
  2.  @OnOpen
    1. This annotated method is triggered when webSocket successfully establishes a connection. Note that it takes a Session parameter
  3. @OnClose
    1. This annotated method is triggered when a WebSocket connection is disconnected. Note that it takes a Session parameter
  4. @OnMessage
    1. This annotation modified method is triggered when the client sends a message to the server and has a String input parameter indicating the value passed in by the client
  5. @OnError
    1. This annotated method is triggered when an exception occurs while the WebSocket is establishing a connection. Note that it takes a Session parameter

The other thing is how the server sends the message to the client. The server must send the message through the Session class mentioned above, usually in the @onOpen method. When the connection is successful, it stores the Session into the Map value, the key is the user id corresponding to the Session. Get the session by key and send the message to the client via session.getBasicRemote().sendText().

2. Spring package

pom.xml

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>Copy the code

HttpAuthHandler

/* * * * * blog.coder4j.cn * * Copyright (C) 2016-2019 All Rights Reserved. * */ package cn.coder4j.study.example.websocket.handler; import cn.coder4j.study.example.websocket.config.WsSessionManager; import org.springframework.stereotype.Component; import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.handler.TextWebSocketHandler; import java.time.LocalDateTime; /** * @author buhao * @version MyWSHandler.java, V 0.1 2019-10-17 17:10 buhao */ @Component Public class HttpAuthHandler extends TextWebSocketHandler {/** * socket Establish a successful event * * @ param session * @ throws the Exception * / @ Override public void afterConnectionEstablished (WebSocketSession session) throws Exception { Object token = session.getAttributes().get("token"); if (token ! Wssessionmanager.add (token.tostring (), session); } else {throw new RuntimeException(" User login is invalid!" ); } /** * Receive message event ** @param session * @param message * @throws Exception */ @override protected void handleTextMessage(WebSocketSession session, TextMessage Message) throws Exception {// Get the message sent by the client. String payload = message.getPayload(); Object token = session.getAttributes().get("token"); System.out.println(" Server received "+ token +" sent "+ payload); Session. sendMessage(new TextMessage(" Server sent to "+ token +" message "+ payload +" "+ localDatetime.now ().toString())); } /** * when the socket is disconnected ** @param session * @param status * @throws Exception */ @override public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { Object token = session.getAttributes().get("token"); if (token ! Wssessionmanager.remove (token.tostring ()); }}}Copy the code

instructions

Websocket events can be handled by inheriting the TextWebSocketHandler class and overriding the corresponding methods, which can be seen in conjunction with the annotations of the native annotations

  1. AfterConnectionEstablished method is in after the success of the socket connection is triggered, with native @ OnOpen function annotation
  2. The afterConnectionClosed method is triggered when the socket connection is closed, as is the @onClose method in the native annotation
  3. The handleTextMessage method is triggered when the client sends a message, the same as the @onMessage function in the native annotation

WsSessionManager

/* * * * * blog.coder4j.cn * * Copyright (C) 2016-2019 All Rights Reserved. * */ package cn.coder4j.study.example.websocket.config; import lombok.extern.slf4j.Slf4j; import org.springframework.web.socket.WebSocketSession; import java.io.IOException; import java.util.concurrent.ConcurrentHashMap; /** * @author buhao * @version WsSessionManager.java, @slf4j public class WsSessionManager {/** * private static */ ConcurrentHashMap<String, WebSocketSession> SESSION_POOL = new ConcurrentHashMap<>(); /** * add session ** @param key */ public static void add(String key, WebSocketSession session) {// Add session session_pool. put(key, session); } /** * public static WebSocketSession remove(String key) {// Delete session session return SESSION_POOL.remove(key); } @param key */ public static void removeAndClose(String key) {WebSocketSession session = remove(key); if (session ! = null) {try {// close the connection session.close(); } catch (IOException e) {// todo: turn off the exception handler e.printStackTrace(); Public static WebSocketSession get(String key) {// Get session return  SESSION_POOL.get(key); }}Copy the code
instructions

ConcurrentHashMap is used to implement a session pool, which is used to store the session of the logged In Web socket. As mentioned earlier, the server must use this session to send messages to the client.

MyInterceptor

/* * * * * blog.coder4j.cn * * Copyright (C) 2016-2019 All Rights Reserved. * */ package cn.coder4j.study.example.websocket.interceptor; import cn.hutool.core.util.StrUtil; import cn.hutool.http.HttpUtil; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.stereotype.Component; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.server.HandshakeInterceptor; import java.util.HashMap; import java.util.Map; /** * @author buhao * @version MyInterceptor.java, V 0.1 2019-10-17 19:21 */ @component public class myShakeInterceptor implements HandshakeInterceptor {/** ** * @param request * @param response * @param wsHandler * @param attributes * @return * @throws Exception */ @Override public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> Attributes) throws Exception {system.out.println (" handshake begins "); // Get the request parameters HashMap<String, String> paramMap = httputil.decodeparammap (request.geturi ().getQuery(), "UTF-8 "); String uid = paramMap.get("token"); If (strutil. isNotBlank(uid)) {// Add attributes to the domain. System.out.println(" user token "+ uid +" handshake successful!" ); return true; } system.out. println(" user login invalid "); return false; } @param request @param response @param wsHandler @param exception */ @override public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception Exception) {system.out.println (" handshake done "); }}Copy the code
instructions

Define the HandshakeInterceptor by implementing the HandshakeInterceptor interface. Note that this is different from the above Handler events, which are pre-handshake events and post-handshake events. The Handler establishes a connection with the socket on the basis of a successful handshake. Therefore, if you put authentication in this step, it is relatively the most efficient server resources. It has two main methods beforeHandshake and afterHandshake, as the name implies one is triggered beforeHandshake and one is triggered afterHandshake.

WebSocketConfig

/* * * * * blog.coder4j.cn * * Copyright (C) 2016-2019 All Rights Reserved. * */ package cn.coder4j.study.example.websocket.config; import cn.coder4j.study.example.websocket.handler.HttpAuthHandler; import cn.coder4j.study.example.websocket.interceptor.MyInterceptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.config.annotation.EnableWebSocket; import org.springframework.web.socket.config.annotation.WebSocketConfigurer; import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; /** * @author buhao * @version WebSocketConfig.java, V 0.1 2019-10-17 15:43 buhao */ @configuration @enableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Autowired private HttpAuthHandler httpAuthHandler; @Autowired private MyInterceptor myInterceptor; @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry .addHandler(httpAuthHandler, "myWS") .addInterceptors(myInterceptor) .setAllowedOrigins("*"); }}Copy the code
instructions

The WebSocket is configured by implementing the WebSocketConfigurer class and overriding the corresponding methods. We mainly cover registerWebSocketHandlers this method. This is done by setting different parameters to the WebSocketHandlerRegistry. Where the addHandler method adds the WS handler class we wrote above, and the second argument is the WS path you exposed. AddInterceptors adds the handshake filter we wrote. SetAllowedOrigins (“*”) this is to turn off cross-domain validation, convenient for local debugging, online recommended to enable.

3. TIO

pom.xml

<dependency> <groupId>org.t-io</groupId> <artifactId>tio-websocket-spring-boot-starter</artifactId> < version > 2.6.2. V20191010 - RELEASE < / version > < / dependency >Copy the code

application.xml

tio:
  websocket:
    server:
      port: 8989Copy the code
instructions

Only the ws startup port is configured here, and there are many more configurations that can be found by following the link at the end

MyHandler

/* * * * * blog.coder4j.cn * * Copyright (C) 2016-2019 All Rights Reserved. * */ package cn.coder4j.study.example.websocket.handler; import org.springframework.stereotype.Component; import org.tio.core.ChannelContext; import org.tio.http.common.HttpRequest; import org.tio.http.common.HttpResponse; import org.tio.websocket.common.WsRequest; import org.tio.websocket.server.handler.IWsMsgHandler; /** * @author buhao * @version MyHandler.java, V 0.1 2019-10-21 14:39 */ @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; } /** * Handshake successful ** @param httpRequest * @param httpResponse * @param channelContext * @throws Exception */ @override public void onAfterHandshaked(HttpRequest httpRequest, HttpResponse httpResponse, ChannelContext ChannelContext) throws Exception {system.out. println(" Handshake successful "); } /** * Receiving binary files ** @param wsRequest * @Param bytes * @param channelContext * @return * @throws Exception */ @Override public Object onBytes(WsRequest wsRequest, byte[] bytes, ChannelContext channelContext) throws Exception { return null; } /** * Disconnect ** @param wsRequest * @Param bytes * @param channelContext * @return * @throws Exception */ @Override public Object onClose(WsRequest wsRequest, byte[] bytes, ChannelContext ChannelContext) throws Exception {system.out.println (" close the connection "); return null; } /** * Receive messages ** @param wsRequest * @param s * @param channelContext * @return * @throws Exception */ @override public Object onText(WsRequest wsRequest, String s, ChannelContext ChannelContext) throws Exception {system.out.println (" Receive text message :" + s); return "success"; }}Copy the code

instructions

IWsMsgHandler (IWsMsgHandler, IWsMsgHandler, IWsMsgHandler

  1. handshake
    1. Triggered during a handshake
  2. onAfterHandshaked
    1. Triggered after a successful handshake
  3. onBytes
    1. Triggered when the client sends a binary message
  4. onClose
    1. Triggered when the client closes the connection
  5. onText
    1. Trigger when the client sends a text message

StudyWebsocketExampleApplication

/* * * * * blog.coder4j.cn * * Copyright (C) 2016-2019 All Rights Reserved. * */ package cn.coder4j.study.example.websocket; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.tio.websocket.starter.EnableTioWebSocketServer; @SpringBootApplication @EnableTioWebSocketServer public class StudyWebsocketExampleApplication { public static void main(String[] args) { SpringApplication.run(StudyWebsocketExampleApplication.class, args); }}Copy the code

instructions

The name of this class doesn’t matter, it’s actually your Spring Boot startup class, just remember to annotate @enabletioWebSocketServer

STOMP

pom.xml

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>Copy the code

WebSocketConfig

/* * * * * blog.coder4j.cn * * Copyright (C) 2016-2019 All Rights Reserved. * */ package cn.coder4j.study.example.websocket.config; import org.springframework.context.annotation.Configuration; import org.springframework.messaging.simp.config.MessageBrokerRegistry; import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; import org.springframework.web.socket.config.annotation.StompEndpointRegistry; import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; /** * @author buhao * @version WebSocketConfig.java, V buhao roar, 2019-10-21 * 0.1 / @ Configuration @ EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry) { // Configure the client to attempt to connect to the address registry. AddEndpoint ("/ws").setallowedOrigins ("*").withsockjs (); } @override public void configureMessageBroker(MessageBrokerRegistry Registry) {// Set the broadcast node registry.enableSimpleBroker("/topic", "/user"); / / the client to the server sends the message should be/app prefix registry. SetApplicationDestinationPrefixes ("/app "); / / specified users to send (one-on-one) prefix/user/registry. SetUserDestinationPrefix ("/user/"); }}Copy the code
instructions

  1. By implementing WebSocketMessageBrokerConfigurer interface and add @ EnableWebSocketMessageBroker to stomp configuration and annotation scanning.
  2. Override the registerStompEndpoints method to set the path of exposed STOMp and other cross-domain, client-side, and other Settings.
  3. coverconfigureMessageBroker Method to configure a node.
    1. The broadcast node configured by enableSimpleBroker, that is, the node where the server sends messages and the client subscribes to receive them.
    2. Cover setApplicationDestinationPrefixes method, set up the client to the server sends the message of the node.
    3. Override the setUserDestinationPrefix method to set the nodes for one-to-one communication.

WSController

/* * * * * blog.coder4j.cn * * Copyright (C) 2016-2019 All Rights Reserved. * */ package cn.coder4j.study.example.websocket.controller; import cn.coder4j.study.example.websocket.model.RequestMessage; import cn.coder4j.study.example.websocket.model.ResponseMessage; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.handler.annotation.SendTo; import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ResponseBody; /** * @author buhao * @version WSController.java, V 0.1 2019-10-21 17:22 / @controller public class WSController {@autoWired private SimpMessagingTemplate simpMessagingTemplate; @MessageMapping("/hello") @SendTo("/topic/hello") public ResponseMessage hello(RequestMessage requestMessage) { System.out.println(" Receive message: "+ requestMessage); Return 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"; }}Copy the code
instructions

  1. The node path is exposed via @messagemapping, similar to @requestMapping. Note that although hello is written here, the real address of our client call is /app/hello. Because we are in the above config configuration in the registry. SetApplicationDestinationPrefixes (“/app “).
  2. The @sendto annotation will send the contents of the returned value to clients subscribed to /topic/ Hello, and the @sendtouser annotation is similar except that it is sent to clients for one-to-one communication. These annotations are typically response-on-reply, and if a server actively sends a message it can do so through the convertAndSend method of the simpMessagingTemplate class. Note simpMessagingTemplate. ConvertAndSendToUser (token, “/ MSG, MSG). To contact our registry configuration above. SetUserDestinationPrefix (“/user/”), client subscription here is/user / {token} / MSG, don’t make a mistake.

Session sharing problem

One of the recurring issues mentioned above is that a server must use a session in order to actively send messages to a client. And you all know that the session thing is not cross-JVM. If there are multiple servers, in the case of HTTP requests, we can solve this problem by sharing sessions in the caching middleware, with a few configurations of Spring Session. But web sockets do not. Its sessions are not serialized, not to embarrass you, but because of the difference between HTTP and Web socket requests. At present, the simplest solution found online is to subscribe to broadcast via Redis. The main code is similar to the second method. You need to place a map locally to save the requested session. That is to say, each server saves the session connected to it locally. Then the message location needs to be changed, not sent directly as now, but via redis subscription mechanism. When the server wants to send a message, you broadcast the message through Redis, and all subscribed servers receive the message and try to send it locally. I’m sure that only the one with the corresponding user session will be able to send it.

How to choose

  1. If you are using TIO, it is recommended to use tio integration. Because it already does a lot of things, including session sharing through Redis, just add a few configurations. But TIO is semi-open source, and documents are paid for. If not, forget it.
  2. If your business needs are more flexible, the first two are recommended, especially the second form of Spring encapsulation.
  3. If it is simple server two-way communication, the STOMP form is recommended because it is easier to normalize.

other

  1. Websocket online authentication

After writing the server code to debug, but not front-end code how to do, click here, this is an online Websocket client, the function is fully enough for us to debug.

  1. Stomp validation

There is no online version of this, but there are many online demos that can be downloaded locally for debugging, and can also be found through the link below.

  1. In addition, due to limited space, we cannot put all the codes, but all the test codes are uploaded to GitLab to ensure normal operation, which can be found here

Refer to the link

  1. SpringBoot system – Integrated WebSocket real-time communication
  2. WebSocket Story ii – How to use STOMP to quickly build WebSocket broadcast message patterns in Spring
  3. [SpringBoot integration WebSocket for point-to-point based on pure H5 】 【 [one] and radio [a one-to-many] live post] (https://blog.csdn.net/Ouyzc/article/details/79994401)
  4. Spring Framework Reference Documentation (WebSocket STOMP)
  5. Use WebSocket in Spring Boot summary (a) : several implementation methods in detail
  6. Spring Boot series – WebSocket easy to use
  7. tio-websocket-spring-boot-starter