“This is the 15th day of my participation in the First Challenge 2022. For details: First Challenge 2022”

One, foreword

inIMIn order to ensure the real-time performance of message arrival in the system, the following three methods are generally adopted:

  1. Short polling: the client periodically requests the server to pull messages
  2. Long polling: When a request does not receive a new message, it does not return immediately, but “hangs” on the server.
  3. Long connection: When a new message is generated, the server pushes it to the client

The following is an analysis of the applicable scenarios of these three methods.

(1) Short polling

Short polling: the usual “request response” mode.

Applicable scenarios:

  1. Scan code login: Query the status of two-dimensional code frequently in a short time
  2. Weak network request: Retry frequently on the weak network

Disadvantages:

  1. Most requests are useless: In normal network conditions, most requests are useless
  2. The server is overloaded with requests: Frequent access to improve the serverQPS


(2) Long polling

To avoid the problem of “short polling” with high frequency of useless requests, a “long polling” message retrieval mode can be used.

Principle of long polling:

  • If the request does not receive a new message, it does not return immediately. Instead, it “hangs” on the server and waits for a period of time.
  • If a new message is generated during the waiting period, the response is returned immediately.

Applicable scenarios:

  1. Sweep the login code
  2. Weak network request

For example: Scan login, short poll and long poll differences:

  • Short polling: the server returns as soon as the request is processed

  • Long polling: server suspends until request processing is complete or times out

Advantages: Compared with short polling mode

  1. Greatly reduces network overhead and power consumption caused by high-frequency and useless polling on the client in short polling mode
  2. Reduces the server’s ability to process requestsQPS

Disadvantages:

  1. Invalid request: Long polling ends up returning when no message is retrieved within the timeout period, so the problem of “invalid” requests from clients is still not completely resolved.

  2. Server stress: The server hangs requests, reducing QPS for incoming requests, but not polling for backend resources.

    If there are 1000 requests waiting for a message, it might mean that there are 1000 threads constantly polling for the message store resource.


(3) Long connection

Because the server cannot directly push to the client, there are two “curve-saving” methods: short polling and long polling.

Implementation principle: THE client and server maintain a TCP/IP long-link, full-duplex channel.

For example: HTML5, full duplex WebSocket, Web native support, relatively simple implementation.

Advantages:

  1. Support the two-way communication pushed by the server, greatly reducing the polling pressure on the server;
  2. The control cost of data interaction is low, and the network cost of communication is reduced.




Second, the actual combat

There are two main actual combat projects:

  1. Long polling
  2. websocketuse

(1) Long polling

There are two ways to achieve this:

  1. Method 1: block and wait for time to return
  2. Method two: Establish connection and request processing separation

1) Method 1: block

Code implementation:

@Slf4j
@RestController
@RequestMapping("/async")
public class AsyncController {
    @GetMapping("/sync")
    public String sync(a) {
        log.info("= = = > start sync");
        
        // simulate processing requests
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        log.info("= = = > end sync");
        return "sync"; }}Copy the code

Testing:

$ curl http://localhost:8080/async/sync
sync
Copy the code

Log output result:

2022-02-02 12:03:35.387 - INFO 10699 [nio-8080-exec-1] AsyncController: ===> Start sync2022-02-02 12:03:40.387 - INFO 10699 [nio-8080-exec-1] AsyncController   : ===> 结束 sync
Copy the code


2) Method two: establish connection and request processing separation

Used hereSpring MVCAsynchronous request processing for:DeferredResult.

Spring MVC will process the returned value after invoking the processor method. If the returned value is found to be an asynchronous request type, it will not immediately respond to the client. Instead, it directly suspends the request and interrupts the processing process of the current request.

DeferredResult is suitable for scenarios where client polling is processed. It can respond to clients in a timely manner to avoid server stress caused by frequent polling requests.

Cooperate withDeferredResultUse:

  1. The client initiates a request
  2. The server receives it, and suspends the request (” hang “), returning if one of two things happens:
    1. Active:DeferredResult#setResult()call
    2. Passive: Timeout
  3. The client you receive the response

Code implementation:

@Slf4j
@RestController
@RequestMapping("/async")
public class AsyncController {

    @Autowired
    private Service service;
    
    @GetMapping(value = "/deferred")
    public DeferredResult<String> DeferredWay(a) {
        log.info("= = = > start deferred");
        DeferredResult<String> result = new DeferredResult<>(5000L."Default value");
        result.onTimeout(() -> log.info("Deferred call timeout"));
        result.onCompletion(() -> log.info("Deferred call complete"));

        // Asynchronous invocation: business processing
        // for example, visit redis repeatedly to query the status of qrcode
        service.scanQrCode(result);

        log.info("= = = > end deferred");
        returnresult; }}Copy the code
@Slf4j
@Service
public class Service {

    @Async
    @Override
    public void scanQrCode(DeferredResult<String> result) {

        log.info("-- > Start business call");

        // simulate multiple queries
        // For example, query redis once per second
        try {
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        log.info("-- > End business call");

        result.setResult("Scan successful"); }}Copy the code

Testing:

Log output result:

2022-02-02 12:52:12.428 - INFO 12275 [nio-8080-exec-3] AsyncController: ===> Start Deferred2022-02-02 12:52:12.428 - INFO 12275 [nio-8080-exec-3] AsyncController   : ===> 结束 deferred
2022-02-02 12:52:12.428 - INFO 12275 [  AsyncThread-2] Service: --> Start Service invocation2022-02-02 12:52:16.428 - INFO 12275 [  AsyncThread-2] Service: --> End the Service call2022-02-02 12:52:16.434 - INFO 12275 [nio-8080-exec-4] AsyncController: Deferred Call completedCopy the code




(2)wssuse

The procedure is as follows: 0. Add a dependency

  1. newwebsocketThe configuration file
  2. Writing external interfaces
  3. The front page

Add dependencies:

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

Configuration file:

@Configuration
public class WebsocketConfig {

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

Write external interface:

@Slf4j
@Controller
@ServerEndpoint(value = "/wss")
public class WssController {

    private static AtomicInteger onlineCount = new AtomicInteger(0);


    @OnOpen
    public void onOpen(Session session) {
        onlineCount.incrementAndGet();
        log.info("New connection joined: {}, number of current online users: {}", session.getId(), onlineCount.get());
        System.out.println("session open. ID:" + session.getId());
    }

    /** * the connection closes the called method */
    @OnClose
    public void onClose(Session session) {
        onlineCount.decrementAndGet(); // The number of lines is reduced by 1
        log.info("There is a connection closed: {}, currently online: {}", session.getId(), onlineCount.get());
        System.out.println("session close. ID:" + session.getId());
    }

    /** * The method called after receiving the client message */
    @OnMessage
    public void onMessage(String message, Session session) {
        log.info("Server receives message from client [{}] :{}", session.getId(), message);
        this.sendMessage("Hello, " + message, session);
    }

    private void sendMessage(String message, Session toSession) {
        try {
            log.info("The server sends a message to the client [{}] {}", toSession.getId(), message);
            toSession.getBasicRemote().sendText(message);
        } catch (Exception e) {
            log.error("Server failed to send message to client:", e); }}/** ** is called when an error occurs
    @OnError
    public void onError(Session session, Throwable error) {}}Copy the code

Front page:

<! DOCTYPEHTML>
<html>
<head>
<title>My WebSocket</title>
</head>

<body>
    <input id="text" type="text" />
    <button onclick="send()">Send</button>
    <button onclick="closeWebSocket()">Close</button>
    <div id="message"></div>
</body>

<script type="text/javascript">
    var websocket = null;

    if ('WebSocket' in window) {
        websocket = new WebSocket("ws://localhost:8080/wss");
    } else {
        alert('Not support websocket')}// Connection error callback method
    websocket.onerror = function() {
        setMessageInnerHTML("error");
    };

    // The callback method for successfully establishing the connection
    websocket.onopen = function(event) {
        //setMessageInnerHTML("open");
    }

    // The callback method that received the message
    websocket.onmessage = function(event) {
        setMessageInnerHTML(event.data);
    }

    // A callback method to close the connection
    websocket.onclose = function() {
        setMessageInnerHTML("close");
    }

    // Listen for window closing events, when the window is closed, actively close websocket connection, to prevent the connection is not closed, the server will throw exceptions.
    window.onbeforeunload = function() {
        websocket.close();
    }

    // Displays the message on the web page
    function setMessageInnerHTML(innerHTML) {
        document.getElementById('message').innerHTML += innerHTML + '<br/>';
    }

    // Close the connection
    function closeWebSocket() {
        websocket.close();
    }

    // Send a message
    function send() {
        var message = document.getElementById('text').value;
        websocket.send(message);
    }
</script>
</html>
Copy the code

Open theindex.htmlSend a message:

Log output result:

2022-02-02 20:58:00.423-wssController: new connection added:1, the current number of online users is:1
session open. ID:1
2022-02-02 20:58:18.841- WssController: indicates that the server receives data from the client [1] message:1
2022-02-02 20:58:18.841- WssController: server to client [1] Send message Hello,1
2022-02-02 20:58:36.065- WssController: indicates that the server receives data from the client [1] message:2
2022-02-02 20:58:36.065- WssController: server to client [1] Send message Hello,2
Copy the code