SpringBoot + WebSocket

1. Introduction

WebSocket protocol is a new network protocol based on TCP. It realizes the full duplex communication between the client and the server, and has learned the computer network. Since it is full duplex, it shows that the server can actively send information to the client. This is very similar to our push technology or multiplayer online chat function. HTTP is simplex communication and communication can only be initiated by the client.

HTTP simplex communication WebSocket full duplex communication

2. pom.xml

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

3. Config

@Configuration
@ConditionalOnWebApplication
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter(a) {
        return newServerEndpointExporter(); }}Copy the code

4. Server

Since WebSocket is a client-server like form (using THE WS protocol), the WebSocketServer here is actually a WS protocol Controller

  • The @Serverendpoint annotation is a class-level annotation that defines the current class as a WebSocket server. The value of the annotation will be used to listen for the URL of the terminal to which the user is connected. The client can connect to the WebSocket server through this URL
  • Create a ConcurrentHashMap webSocketMap to receive the WebSocket of the current token to push messages to users.
/**
 * websocket server
 *
 * @author CShip
 * @date2021/2/22 * /
@Slf4j
@ServerEndpoint(value = "/ws/{token}")
@Component
public class WebSocketServer {

    private static final AtomicInteger CONNECTED_COUNT = new AtomicInteger(0);

    /** * key userId * value session * */
    private static final ConcurrentHashMap<String, Session> sessionMap = new ConcurrentHashMap<>();

    private static LoginHelper loginHelper;

    @Autowired
    private LoginHelper loginHelperResource;

    @PostConstruct
    public void init(a) {
        WebSocketServer.loginHelper = loginHelperResource;
    }

    @OnOpen
    public void onOpen(@PathParam("token") String token, Session session) {
        // token+ three random digits
        // Use random numbers to distinguish the same user from multiple clients for operation
        Long userId = loginHelper.token2UserId(token.substring(0, token.length() - 4));
        String num = token.substring(token.length() - 4);
        sessionMap.put(userId.toString()  + num, session);
        int count = CONNECTED_COUNT.incrementAndGet();
        log.info("Connection successful: {}", userId);
        log.info("Current connection: {}", count);
        sendMessage(session, new WebSocketVO<>("connected".0));
    }

    @OnClose
    public void onClose(@PathParam("token") String token) {
        Long userId = loginHelper.getUserIdFromToken(token.substring(0, token.length() - 4));
        String num = token.substring(token.length() - 4);
        sessionMap.remove(userId.toString()  + num);
        int count = CONNECTED_COUNT.decrementAndGet();
        log.info("Connection closed: {}", userId);
        log.info("Current connection: {}", count);
    }


    @OnMessage
    public void onMessage(@PathParam("token") String token, String message, Session session) {
        WebSocketVO vo = new WebSocketVO<>();
        vo.setMessage(message);
        vo.setFlag(0);
        sendMessage(session, vo);
    }

    @OnError
    public void onError(@PathParam("token") String token, Session session, Throwable error) {
        log.error("Session {} error : {}", session.getId(), error.getMessage());
    }


    public static void sendMessage(Session session, WebSocketVO vo) {
        try {
            session.getBasicRemote().sendText(JSONObject.toJSONString(vo));
        } catch (IOException e) {
            log.error("Abnormal sending message: {}", e.getMessage()); }}/** * Sends a message to the specified user */
    public static void sendMessage(String userId, WebSocketVO vo) {
        for (Map.Entry<String, Session> entry : sessionMap.entrySet()) {
            String userIdWithSession = entry.getKey().split("\ \.") [0];
            Session session = entry.getValue();
            if (userIdWithSession.equals(userId)) {
                if(session ! =null && session.isOpen()) {
                    vo.setFlag(1); sendMessage(session, vo); }}}}/** * group message */
    public static void broadCastInfo(WebSocketVO vo) {
        if(! CollectionUtils.isEmpty(sessionMap)) { sessionMap.forEach((userId, session) -> {if (session.isOpen()) {
                    vo.setFlag(1); sendMessage(session, vo); }}); }}}Copy the code

Problem of 5.

A. Invoke the Service Service null pointer

When WebSocket is started, it takes precedence over the Spring container. As a result, null-pointer exceptions are reported when calling Service in WebSocketServer. The required service needs to be statically initialized in the WebSocketServer.

  1. Use static in WebSocketServer to decorate the business Service
  2. Use @Autowired in WebSocketConfig to decorate the set method of the business Service

B. Configure Nginx forwarding

The Upgrade and Connection in the header of the TCP/IP(socket) data should be carried over (or set to the same as the front-end).

# webSocket configuration
proxy_http_version 1.1;
# forward client Upgrade(as important websocket flag) request (must)
proxy_set_header Upgrade $http_upgrade;
# forward the client Connection(as an important websocket identifier)
proxy_set_header Connection "upgrade";
X-real-ip indicates the Real IP address of the client. There is no standard specification for this parameter. If it is a direct access request, it may be the Real IP address of the client, but if it has passed through layers of proxy, it is the IP address of the last layer of proxy.
proxy_set_header X-real-ip $remote_addr;
Each IP address is Forwarded to forwarded-For. The first IP address is the address of the client. Each IP address is separated by a comma (,) and a space (,).
$http_x_forwarded_for specifies the client's IP address
proxy_set_header X-Forwarded-For $remote_addr;
Copy the code