This is the second day of my participation in the More text Challenge. For more details, see more text Challenge

This article is participating in the “Java Theme Month – Java Development in action”. See the link to the event for more details

[TOC]

HTTP requests are the most widely used protocol between our development and users. In HTTP, we can simply get the content (page) we need through the browser. But he has his limits. Today our main character WebSocket will show off its capabilities

HTTP shortcomings

  • Only the client can initiate an HTTP request and the server can respond. The server cannot proactively send information to the client. So some sites in the real-time solution is the use of the page timer function. In short, the client periodically sends requests to the server

This leads to a waste of resources.

  • HTTP is memoreless. The server does not know what the client did before each request, but we usually feel that the browser knows what we did before. These are the cookies that the web site adds to the server during the request. For us, we feel like we have memories. But it is not

  • After HTTP1.1 adopted short connection, long connection two ways. HTTP requests are also sent with a three-way handshake mechanism. So each connection consumes resources. After 1.1, HTTP actually uses a persistent connection for a certain period of time, which can reduce the cost of resources

  • Some people may have doubts about the long connection mentioned above, but the HTTP protocol is developed based on the TCP protocol. So nature has the property of long connections.

HTTP websocket difference

  • HTTP is memoryless because of its short connection nature. To solve this problem, each Request is composed of General+Request Head+Request Paylaod+Response Headers. Heads are what the browser needs to remember, and passing them around every time is performance intensive.

  • Websocket because of the long connection feature, a connection can always two-way communication. Websocket is less focused in terms of carrier and only needs to communicate the information currently needed. Historical information is available on both sides.

Websocket principle

Usage scenarios

Integrate springboot websocket

Environment to prepare

  • Springboot based on the introduction of webSocket JAR. Since we already inherited SpringBoot, we don’t need to add the version number

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

Copy the code
  • Then add the following configuration to the project to configure the beans required by WebSocket and let Spring manage them

@Configuration
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter(a) {
        return newServerEndpointExporter(); }}Copy the code
  • Then we can write webSocket accept and send events. I encapsulate this base, abstracting a WebSocket first

public abstract class BaseWebSocket extends  BaseController{
    /** * a static variable that records the number of connections currently online. It should be designed to be thread safe. * /
    private int onlineCount = 0;

    /** * The thread safe Set of the concurrent package, which holds the MyWebSocket object for each client. * /
    public CopyOnWriteArraySet<BaseWebSocket> webSocketSet = new CopyOnWriteArraySet<BaseWebSocket>();

    /** * Connect to a client session that needs to send data to the client */
    public Session session;

    private Logger log = LoggerFactory.getLogger("BaseWebSocket");


    /** * Connection established successfully called method *@param session
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("sid") String sid) throws IOException {
        this.session = session;
        // Add to set
        webSocketSet.add(this);
        // Add 1 to the number of lines
        addOnlineCount();
        log.debug("New connection added! The number of people currently online is" + getOnlineCount());
        // Send a message
        MultiMap multiMap = new MultiMap();
        if (null! =session.getQueryString()&&!"".equals(session.getQueryString())) {
            UrlEncoded.decodeTo(session.getQueryString(), multiMap, "UTF-8");
        }
        sendInfo(defaultMessage(multiMap));
    }

    /** * the connection closes the called method */
    @OnClose
    public void onClose(a) {
        // Delete from set
        webSocketSet.remove(this);
        // The number of lines is reduced by 1
        subOnlineCount();
        log.info("There is a connection closed! The number of people currently online is" + getOnlineCount());
    }

    /** * The method to be called when the client message is received@paramMessage Indicates the message sent by the client *@paramThe session cache *@throws IOException
     */
    @OnMessage
    public void onMessage(String message, Session session) throws IOException {
        this.session = session;
        try {
            Map paraMap = (Map) JSONObject.parse(message);
            handlerMessage(paraMap);
        } catch (JSONException e) {
            MultiMap multiMap = new MultiMap();
            UrlEncoded.decodeTo(message, multiMap, "UTF-8");
            handlerMessage(multiMap);
            // Throw new BusinessException(" Passing message format error (Json)");}}/** * Process message acceptance *@paramParaMap Receives the map parameter */
    public void handlerMessage(Map paraMap) {
        try {
            sendInfo(defaultMessage(paraMap));
        } catch(IOException e) { e.printStackTrace(); }}public Object defaultMessage(Map<String, Object> paraMap) {
        Object obj = new Object();
        try {
            obj = defauleMessage(paraMap);
        } catch (BusinessException e) {
            return formatReturnAppData(e.getMessage());
        }
        return obj;
    }
    /** * send data * by default@paramParaMap Specifies the parameter * passed during connection@return* /
    public abstract Object defauleMessage(Map<String, Object> paraMap);

    public static boolean isJson(String content) {
        try {
            JSONObject.parse(content);
            return true;
        } catch (Exception e) {
            return false; }}/** * called when an error occurs@OnError* * /
    public void onError(Session session, Throwable error) {
        log.error("OnMessage method exception"+error.toString());
        error.printStackTrace();
    }


    /** * synchronizes with the synchronized method * Note the difference between session.getBasicRemote() and session.getAsyncremote () *@param message
     * @throws IOException
     */
    public synchronized void sendMessage(Object message) throws IOException {
// this.session.getBasicRemote().sendText(message);
        this.session.getAsyncRemote().sendText(JSONObject.toJSONString(message));
    }


    /** * Send custom messages in groups ** /
    public void sendInfo(Object message) throws IOException {
        for (BaseWebSocket item : webSocketSet) {
            try {
                item.sendMessage(message);
            } catch (IOException e) {
                continue; }}}public  synchronized int getOnlineCount(a) {
        return onlineCount;
    }

    public  synchronized void addOnlineCount(a) {
        onlineCount++;
    }

    public  synchronized void subOnlineCount(a) { onlineCount--; }}Copy the code
  • Then when we add a WebSocket we just need to inherit the abstract WebSocket class and reproduce the defauleMessage method. The following comments are required on the class

@ServerEndpoint(value = "/accident/getAccident")
@Component
public class AccidentGetAccident extends BaseWebSocket {

    AccidentController accidentController;


    @Override
    public Object defauleMessage(Map<String, Object> paraMap) {
        accidentController = ContextUtil.getApplicationContext().getBean(AccidentController.class);
        returnaccidentController.getAccident(paraMap); }}Copy the code

Client connection

  • In the front end, we only need to construct the WebSocket object, which needs to pass one parameter – the connection address

ws = new WebSocket(wsUrl);

Copy the code
  • Then we can override some of the events in WS. The events on the front end correspond to basically the same events on the back end. In this way, the interaction between the front and back ends is completed!
ws.onclose = function () {
    console.log('Link closed');
};
ws.onerror = function() {
    console.log('An exception has occurred.');
};
ws.onopen = function () {
    console.log('New connection');
};
ws.onmessage = function (event) {
    console.log("Received feedback from the server.");
}

Copy the code
  • However, we need to consider a case where our server is down because of some factor. In this case, the client catches the onClose event and the connection ends, but the server may fix it in a short period of time. At this point, we need the client to do a favorite refresh to reconnect. Websockets are normally used on large screens, and sometimes refresh is not considered very convenient, so we need our clients to have a reconnection mechanism.

var lockReconnect = false;// Avoid duplicate connections
    var wsUrl = Ws: / / "127.0.0.1:8088 / accident/getAccident? entId=zhonghuaxingzhong";
    var ws;
    var tt;
    function createWebSocket() {
        try {
            ws = new WebSocket(wsUrl);
            init();
        } catch(e) {
            console.log(e+'catch'); reconnect(wsUrl); }}function init() {
        ws.onclose = function () {
            console.log('Link closed');
            reconnect(wsUrl);
        };
        ws.onerror = function() {
            console.log('An exception has occurred.');
            reconnect(wsUrl);
        };
        ws.onopen = function () {
            // The heartbeat detection is reset
            heartCheck.start();
        };
        ws.onmessage = function (event) {
            setMessageInnerHTML(event.data);
            // Receiving any message indicates that the current connection is normal
            console.log('Message received'); heartCheck.start(); }}function reconnect(url) {
        if(lockReconnect) {
            return;
        };
        lockReconnect = true;
        // Set delay to avoid too many requests
        tt && clearTimeout(tt);
        tt = setTimeout(function () {
            createWebSocket(url);
            lockReconnect = false;
        }, 4000);
    }
    // Check the heartbeat
    var heartCheck = {
        timeout: 3000.timeoutObj: null.serverTimeoutObj: null.start: function(){}}// Display the message on the web page
    function setMessageInnerHTML(innerHTML) {
        document.getElementById('message').innerHTML += innerHTML + '<br/>';
    }
    createWebSocket(wsUrl);

Copy the code

conclusion

  • Springboot wraps WebSocket as a layer. We just need to configure our WebSocket service endpoint to interact with the front end. However, special attention should be paid to the nGINx proxy. Because webSocket and HTTP setup are different. HTTP is a stateless request with no follow-up after the response. But webSocket takes a while to stay connected. Another thing is that WebSockets don’t stay connected all the time. If there is no interaction for a certain period of time, it is automatically disconnected. This ensures that resources are not constantly occupied

Everybody is good! Original is not easy! Springcloud series + JVM series is in the works…