Protocol: WebSocket full-duplex communication network protocol

Project scenario:

WebSocket is a network protocol for full-duplex communication between client and server. It allows real-time, bidirectional data transfer over a single TCP connection.

In actual projects, specific requirements were encountered and the WebSocket network protocol was used. My requirement is that I need to continuously call the third-party interface, and the frequency and speed of the calls are controlled by the front end. For example, if called once every 50ms, the 50 speed parameter is controlled by the front end. It can be understood as the effect of acceleration and deceleration.

Problem description:

If the front-end uses timers and other things to frequently send requests to the back-end, this is obviously unreasonable. In all this scenario, WebSocket can be used for full-duplex communication between the client and the server. To put it bluntly, the front-end and back-end establish a connection, and requests are no longer frequently initiated by the front-end. Instead, the backend controls the request. A simple understanding is that the backend defines a url path of WebSocket’s ws, and the frontend initiates a connection to the backend based on this exclusive url path. After the connection is successful, the front end sends a message to the back end, and the back end responds to the front end after receiving it.

Front-end code:

Tip: The following is a simple dome case with detailed comments. Through these buttons, you can open the console to see the specific effect, which can be used as a reference.

<template>
  <div class="diagram">
    <button @click="webSocket()">Establish socket connection</button>
    <button @click="websocketclose()">Connection closed</button><br>
    <button @click="websocketsend(num2)">Data sending change 150</button>
    <button @click="websocketsend(num1)">Data sending change 100</button>
    <button @click="websocketsend(num)">Data sent 50</button><br>
  </div>
</template>

<script>
export default {
  name: "test",
  data() {
    return {
      num: 50,
      num1 : "updateVelocityValue:100",
      num2 : "updateVelocityValue:150",
    };
  },
  created() {
  //this.webSocket(); // Connect to WebSocket
},
destroyed: function () {
  this. websocket close();
},
methods: {
    say() {
      if (this.websock == null) {
        alert("Connection exception: null");
        return 'null'
      }
      if(this.userInput===""){
        this.tips('Please enter the chat content');
      }else{
        this.tips('');
      }
      this.websocketsend(this.userInput);
      this. userInput = '';
    },
    //WebSocket connection
    webSocket: function () {
      // establish socket connection
      if ('WebSocket' in window) {//Determine whether the current browser supports webSocket
          this.websock = new WebSocket("ws://localhost:8010/websocket/343369");//Establish a connection with the back-end service
      } else {
        alert('Your browser does not support websocket yet:(');
      }
      console.log(this.websock);
      this.websock.onopen = this.websocketonopen;
      this.websock.onerror = this.websocketonerror;
      this.websock.onmessage = this.websocketonmessage;
      this.websock.onclose = this.websocketclose;
    },
    // send data
    websocketsend :function (data) {
      this. websock. send(data)
    },
    // data reception
    websocketonmessage :function(e) {
      console.log(e.data);
    },
    //WebSocket connection closed
    websocketclose :function(e) {
      this. websock. close();
      console.log("Connection closed");
    },
    //WebSocket connection error occurred
    websocketonerror :function(e) {
      console.log("WebSocket connection error");
    },
  }
};
</script>

<style lang="less" scoped>

</style>

Backend code (three steps):

Tip: SpringBoot used in the backend is divided into 3 steps. As follows:

First step: Maven dependencies

 <!--websocket-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
            <version>1.3.5.RELEASE</version>
        </dependency>

Step 2: Configuration Class

package com.it.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
 
@Configuration
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

The third step: specific implementation (you can read the comments, it is very clear and brainless)

Code description: When the front end clicks the socket connection button, it will establish a connection with the back end and enter the back end onOpen() method. The ws path needs to be noted and the comments are described. The front end clicks the data again to send 50, and sends a message to the back end. The back end enters the onMessage() method, receives the message and processes the business logic. I will judge whether it is the default 50 parameter or a modified parameter. If the parameter is 50, open a thread. to execute business logic. After processing things, the front end clicks on the connection to close. This channel will be closed.

package com.it.websocket;

import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.CopyOnWriteArraySet;
import com.alibaba.fastjson.JSON;
import com.it.domain.User;
import com.it.dto.MyResponseDto;
import com.it.service.impl.EmulationOutcomeServiceImpl;
import com.it.service.impl.UserServiceImpl;
import com.it.util.DateUtil;
import com.it.util.SpringContextUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;

@Slf4j
@ServerEndpoint("/websocket/{userName}") //It is the url when you connect. If the backend is 127.0.0.1:8080/websocket/Zhang San, then write the front-end websocket connection url
@Component // Don’t forget this annotation. Its main function is to incorporate this listener into the Spring container for management.
public class WebSocketServer {

    //Used to record the current number of online connections. It should be designed to be thread-safe
    private static int onlineCount = 0;
    //The thread-safe Set of the concurrent package is used to store the MyWebSocket object corresponding to each client.
    private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<>();
    //The connection session with a client needs to be used to send data to the client
    private Session session;
    //parameters for continuous simulation
    private Integer velocityValue = 50;
    //Thread used to perform continuous simulation
    private Thread simulationThread;
    //user name
    private String userName;

    /**
     * Method called when the connection is successful (can receive one parameter)
     */
    @OnOpen
    public void onOpen(@PathParam("userName") String userName,Session session) {
        this.userName =userName;
        this.session = session;
        webSocketSet.add(this);
        addOnlineCount();
        log.info("The connection was established successfully: the current number of people online is (" + getOnlineCount() + "people)");
    }

    /**
     * Processing logic after receiving client message
     * @param message message sent by the client
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        log.info("Received a message from the client:" + message);
        if (message. startsWith("updateVelocityValue:")) {
            int newVelocityValue = Integer.parseInt(message.substring("updateVelocityValue:".length()));
            this.velocityValue = newVelocityValue;
        } else {
            startSimulation();
        }
    }

    /**
     * Thread that executes continuous simulation
     */
    private void startSimulation() {
        stopSimulation(); // first stop the existing simulation thread
        simulationThread = new Thread(() -> {
            while (simulationThread != null) {
                try {
                    ApplicationContext context = SpringContextUtils. getApplicationContext();
                    MyResponseDto myResponseDto =null;
                    if (context == null) {
                        System.out.println("ApplicationContext is null");
                    } else {
                        //Get execution status information of process simulation
                        EmulationOutcomeServiceImpl emulationOutcomeService = context.getBean(EmulationOutcomeServiceImpl.class);
                        if (emulationOutcomeService == null) {
                            System.out.println("emulationOutcomeService is null");
                        } else {
                            myResponseDto = emulationOutcomeService.sustainEmulation(velocityValue);
                        }
                        //Get user information according to user name (set current simulation time and percentage progress)
                        UserServiceImpl userService = context. getBean(UserServiceImpl. class);
                        if (userService == null) {
                            System.out.println("userService is null");
                        } else {
                            User user = userService. getUserByUserName(userName);
                            if (user != null){
                                //The time the engine returns
                                long time = Long. parseLong(myResponseDto. getTime());
                                //Get the start time entered by the user
                                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                                Date startDate = null;
                                try {
                                    startDate = sdf.parse(DateUtil.forString(user.getStartEmulationTime(), "yyyy-MM-dd HH:mm:ss"));
                                } catch (ParseException e) {
                                    throw new RuntimeException(e);
                                }
                                long startTime = startDate.getTime() / 1000; // Convert milliseconds to seconds
                                //Convert the merged current time into a date string, assign it to the myResponseDto object, and return to the front end
                                Date date = new Date(time + startTime);
                                String formattedDate = sdf. format(date);
                                myResponseDto.setTime(formattedDate);
                                //Set the percentage progress algorithm
                                Date startDateTime = user. getStartEmulationTime();
                                Date endDateTime = user.getEndEmulationTime();
                                Date currentDateTime = sdf.parse(formattedDate);
                                long totalTime = endDateTime.getTime() - startDateTime.getTime();
                                long elapsedTime = currentDateTime.getTime() - startDateTime.getTime();
                                double progress = (double) elapsedTime / totalTime;
                                myResponseDto.setProgress((int) (progress * 100) + "%");
                            }
                        }
                    }
                    String jsonStr = JSON.toJSONString(myResponseDto);
                    sendInfo(jsonStr);
                    Thread.sleep(velocityValue);
                } catch (Exception e) {
                    log.info("Connection disconnected");
                    throw new RuntimeException(e);
                }
            }
        });
        //Start the thread
        simulationThread.start();
    }

    //Close the thread of continuous simulation (Scheme 2: mark the thread as interrupted)
    private void stopSimulation() {
        System.out.println("---");
        if (simulationThread != null & amp; & amp; simulationThread.isAlive()) {
            simulationThread.interrupt();
            try {
                simulationThread.join();
            } catch (InterruptedException e) {
                Thread. currentThread(). interrupt();
            }
            simulationThread = null;
        }
    }


    /**
     * Send a message to the front end.
     */
    public static void sendInfo(String message) throws IOException {
        log.info(message);
        for (WebSocketServer item : webSocketSet) {
            try {
                item.sendMessage(message);
            } catch (IOException e) {
                continue;
            }
        }
    }
    //Send a message
    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
    }

    /**
     * Called when closing the connection
     */
    @OnClose
    public void onClose() {
        webSocketSet.remove(this);
        subOnlineCount();
        this.stopSimulation();
        log.info("A connection is closed! The current online number is" + getOnlineCount());
    }

    /**
     * Called when an error occurs
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("An error occurred");
        error.printStackTrace();
    }

    public static synchronized int getOnlineCount() {
        return onlineCount;
    }

    public static synchronized void addOnlineCount() {
        WebSocketServer.onlineCount + + ;
    }

    public static synchronized void subOnlineCount() {
        WebSocketServer.onlineCount--;
    }
}

The above is only a very shallow use, if it is helpful to you, Sanlian supports it, it really can’t be more detailed! ! !