[Java] Server-side message push summary

Preface: When building a real-time message push function, choosing the right solution is crucial to developing efficient real-time applications. The push of messages is nothing more than push and pull data models. This article will introduce four common real-time message push solutions: short polling (pull), long polling (pull), SSE (Server-Sent Events) (push) and WebSocket (Push), and uses Spring Boot as the technical base to show how to implement these functions in Java full-stack development.

Article directory

    • 1. Short Polling
      • What is short polling?
      • Implementation of short polling
      • Characteristics and limitations of short polling
    • 2. Long Polling
      • What is long polling?
      • Implementation of long polling
      • Characteristics and limitations of long polling
    • 3. SSE (Server-Sent Events)
      • What is SSE?
      • Implementation of SSE
      • Features and limitations of SSE
    • 4. WebSocket
      • What is WebSocket?
      • WebSocket implementation
        • 1. Create a WebSocket handler:
        • 2. Configure the WebSocket endpoint:
        • 3. Front-end implementation
      • Features and limitations of WebSocket
  • Summarize
  • Notice

1. Short Polling

What is short polling?

Short polling is a simple real-time message push scheme in which the client obtains the latest messages by periodically sending requests to the server. The server responds immediately after receiving the request, regardless of whether there are new messages. If no new messages are available from the server, the client will send the request again.

Implementation of short polling

In Spring Boot, short polling can be implemented through the HTTP interface and scheduled tasks. Here’s a simple example:

// -- Backend interface
@GetMapping("/short")
public String getShort() {<!-- -->
    long l = System.currentTimeMillis();
    if((l & amp;1) == 1) {<!-- -->
        return "ok";
    }
    return "fail";
}


// --- Front-end page
@org.springframework.stereotype.Controller
public class Controller {<!-- -->

    @GetMapping("/s")
    public String s() {<!-- -->
        return "s";
    }
}

// -- s.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Short polling</title>
</head>
<body>
<p>msg=<span id="message"></span></p>
<script>
    function pollMessage() {<!-- -->
//Send polling request
        const xhr = new XMLHttpRequest();
        xhr.open("GET", "/short", true);
        xhr.onreadystatechange = function () {<!-- -->
            if (xhr.readyState === XMLHttpRequest.DONE) {<!-- -->
                if (xhr.status === 200) {<!-- -->
                    document.getElementById("message").innerHTML = xhr.responseText;
                }
            }
        };
        xhr.send();
    }
    setInterval(()=>{<!-- -->
        pollMessage()
    }, 1000)

</script>
</body>
</html>


In the above example, the getShort() method is used to return the message, while the s method is used to render s.html. The client can periodically call the getShort() interface to obtain the latest news.

Characteristics and limitations of short polling

The implementation of short polling is simple, but it has some characteristics and limitations:

  • High latency: The client needs to send requests periodically, regardless of whether there are new messages. This can cause some latency, especially if messages are updating slowly.
  • High network load: Clients need to send requests frequently, even if messages are not updated. This increases the load on the server and network.
  • Poor real-time performance: Since you need to wait for the next poll to get new messages, short polling has relatively poor real-time performance.

2. Long Polling

What is long polling?

Long polling is an improved polling method that keeps requests pending when there are no new messages until a new message arrives or a timeout occurs. Compared with short polling, long polling can obtain new messages faster and reduce unnecessary requests.

Implementation of long polling

In Spring Boot, you can use asynchronous requests and scheduled tasks to implement long polling. Here’s a simple example:

// -- Request interface

/**
 * Long polling
 * @return
 */
@GetMapping("/long")
public DeferredResult<String> getLong() {<!-- -->
    DeferredResult<String> deferredResult = new DeferredResult<>();

    if (latestMessage != null) {<!-- -->
        deferredResult.setResult(latestMessage);
    } else {<!-- -->
        // Set the timeout using a scheduled task
        TimerTask timeoutTask = new TimerTask() {<!-- -->
            @Override
            public void run() {<!-- -->
                deferredResult.setResult(null);
            }
        };
        Timer timer = new Timer();
        timer.schedule(timeoutTask, 5000); //Set the timeout to 5 seconds

        //Set the callback function to be triggered when the message arrives
        deferredResult.onTimeout(() -> {<!-- -->
            timer.cancel();
            deferredResult.setResult(null);
        });

        deferredResult.onCompletion(timer::cancel);
    }

    return deferredResult;
}

/**
 * Set message
 * @param message
 */
@PostMapping("/send-message")
public void sendMessage(@RequestBody String message) {<!-- -->
    latestMessage = message;
}


// -- Front-end request

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Long polling</title>
</head>
<body>
<p>msg=<span id="message"></span></p>
<p>Number of requests: <span id="cnt"></span></p>
<script>
    var cnt = 0
    function pollMessage() {<!-- -->
//Send polling request
        const xhr = new XMLHttpRequest();
        xhr.open("GET", "/long", true);
        xhr.onreadystatechange = function () {<!-- -->
            if (xhr.readyState === XMLHttpRequest.DONE) {<!-- -->
                if (xhr.status === 200) {<!-- -->
                    document.getElementById("message").innerHTML = xhr.responseText;
                }
            }
        };
        xhr.send();
    }

    setInterval(()=>{<!-- -->
         + + cnt;
        document.getElementById('cnt').innerHTML = cnt.toString()
        pollMessage()
    }, 5000)

</script>
</body>
</html>



In the above example, the getLong() method returns a DeferredResult object, which triggers the callback function when a new message arrives. If no new messages arrive within the timeout period, the DeferredResult object will return null.

Characteristics and limitations of long polling

Long polling has the following characteristics and limitations:

  • Reduce the number of requests: Long polling can obtain new messages faster, and can reduce the number of unnecessary requests compared to short polling.
  • Reduce network load: When there are no new messages, long polling keeps requests pending, reducing frequent requests, thereby reducing the load on the server and network.
  • Relative real-time improvement: Long polling can obtain new messages faster. Compared with short polling, the real-time performance is improved. However, you still need to wait for the next poll to get new messages.

3. SSE (Server-Sent Events)

What is SSE?

When using Server-Sent Events (SSE), a persistent connection is established between the client (usually a browser) and the server, allowing the server to actively send data to the client. This one-way communication mode in which the server actively pushes data enables real-time updated data to be transmitted to the client in real time without the client making a polling request.

SSE works as follows:

  1. Establishing a connection: The client creates a connection to the server in the browser by using the EventSource object. The client sends an HTTP request to the server, and the header of the request contains Accept: text/event-stream to indicate that the client wants to receive SSE data. The server responds to this request and establishes a persistent HTTP connection.
  2. Keep connected: The server keeps the connection with the client open and keeps sending data. This connection is one-way and only allows the server to send data to the client. The client cannot send data to the server.
  3. Server-sent events: The server uses the Content-Type: text/event-stream header to indicate that the response is an SSE stream. The server encapsulates the data in a specific SSE format. Each event begins with data:, followed by the actual data content, and optional other fields, such as event: > and id:. The data sent by the server can be in any text format, usually JSON.
  4. The client receives events: The client listens to events sent by the server through the EventSource object. When the server sends an event, the EventSource object will trigger the corresponding event handler, and developers can obtain the event data in the handler and perform corresponding operations. A common event is the message event, which indicates the receipt of a new message.
  5. Disconnect: When the client no longer needs to receive events from the server, it can close the connection. The client can call the close() method of the EventSource object to explicitly close the connection, or the browser will automatically close the connection when the page is unloaded.

Implementation of SSE

In Spring Boot, you can use the SseEmitter class to implement SSE. Here’s a simple example:

@RestController
public class SSEController {<!-- -->
    private SseEmitter sseEmitter;

    @GetMapping("/subscribe")
    public SseEmitter subscribe() {<!-- -->
        sseEmitter = new SseEmitter();
        return sseEmitter;
    }

    @PostMapping("/send-message")
    public void sendMessage(@RequestBody String message) {<!-- -->
        try {<!-- -->
            if (sseEmitter != null) {<!-- -->
                sseEmitter.send(SseEmitter.event().data(message));
            }
        } catch (IOException e) {<!-- -->
            e.printStackTrace();
        }
    }
}


//-s.html

<!DOCTYPE html>
<html>
<head>
    <title>SSE Demo</title>
</head>
<body>
<h1>SSE Demo</h1>
<div id="message-container"></div>

<script>
    //Create an EventSource object and specify the SSE server endpoint
    var eventSource = new EventSource('/subscribe');
    console.log("eventSource=", eventSource)
    //Listen to the message event and receive messages sent from the server
    eventSource.addEventListener('message', function(event) {<!-- -->
        var message = event.data;
        console.log("message=", message)
        var messageContainer = document.getElementById('message-container');
        messageContainer.innerHTML + = '<p>' + message + '</p>';
    });
</script>
</body>
</html>

In the above example, the client can subscribe to SSE events by accessing the /subscribe interface, and the server will return a SseEmitter object. When a new message arrives, call the send() method of the SseEmitter object to send the message.

Characteristics and limitations of SSE

SSE has the following features and limitations:

  • Better real-time performance: SSE uses persistent connections, which can achieve better real-time performance than short polling and long polling.
  • One-way communication: SSE is one-way and only allows the server to push messages to the client. The client cannot send messages to the server.
  • Not applicable to lower version browsers: SSE is part of HTML5 and does not support lower version browsers. When using SSE, you need to ensure client browser compatibility.

4. WebSocket

What is WebSocket?

WebSocket is a two-way communication protocol that allows full-duplex communication over a single persistent connection. Different from the previously introduced solutions, WebSocket provides two-way communication capabilities and can achieve real-time two-way data transmission.

WebSocket implementation

In Spring Boot, you can use the Spring WebSocket module to implement WebSocket functionality. Here’s a simple example:

1. Create a WebSocket handler:
@Component
public class WebSocketHandler extends TextWebSocketHandler {<!-- -->
    private List<WebSocketSession> sessions = new CopyOnWriteArrayList<>();

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {<!-- -->
        sessions.add(session);
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {<!-- -->
        for (WebSocketSession webSocketSession : sessions) {<!-- -->
            webSocketSession.sendMessage(message);
        }
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {<!-- -->
        sessions.remove(session);
    }
}
2. Configure WebSocket endpoint:
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {<!-- -->
    @Autowired
    private WebSocketHandler webSocketHandler;

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {<!-- -->
        registry.addHandler(webSocketHandler, "/websocket").setAllowedOrigins("*");
    }
}

In the above example, the WebSocketHandler handler is responsible for handling WebSocket connection, message delivery, and connection closing events. The WebSocketConfig class is used to configure WebSocket endpoints.

3. Front-end implementation
<!DOCTYPE html>
<html>
<head>
    <title>WebSocket Demo</title>
</head>
<body>
<h1>WebSocket Demo</h1>
<div id="message-container"></div>

<script>
    //Create a WebSocket object and specify the server URL
    var socket = new WebSocket('ws://localhost:8080/websocket');

    //Listen to WebSocket connection events
    socket.onopen = function(event) {<!-- -->
        console.log('WebSocket connected');
    };

    //Listen to WebSocket message events
    socket.onmessage = function(event) {<!-- -->
        var message = event.data;
        var messageContainer = document.getElementById('message-container');
        messageContainer.innerHTML + = '<p>' + message + '</p>';
    };

    //Listen to the WebSocket closing event
    socket.onclose = function(event) {<!-- -->
        console.log('WebSocket closed');
    };

    //Send message to server
    function sendMessage() {<!-- -->
        var messageInput = document.getElementById('message-input');
        var message = messageInput.value;
        socket.send(message);
        messageInput.value = '';
    }
</script>

<input type="text" id="message-input" placeholder="Enter message">
<button onclick="sendMessage()">Send</button>
</body>
</html>

Features and limitations of WebSocket

WebSocket has the following characteristics and limitations:

  • Best real-time performance: WebSocket provides true two-way communication, which can realize real-time two-way data transmission and has the best real-time performance.
  • Low latency: Compared to polling and long polling, WebSocket uses a single persistent connection, reducing the overhead of connection establishment and disconnection, thereby reducing latency.
  • Two-way communication: WebSocket allows two-way communication between the server and the client. The server can actively send messages to the client, and the client can also send messages to the server.
  • Higher network load: WebSocket uses long connections, which will occupy certain network resources. In large-scale concurrency scenarios, you need to pay attention to the load of the server.
  • Browser support: Most modern browsers support WebSocket, but care needs to be taken to consider compatibility across different browsers during development.

Summary

This article introduces four common real-time message push solutions: short polling, long polling, SSE and WebSocket, and uses Spring Boot as the technical base to show how to Implement these functions in Java full-stack development.

  • Short polling is a simple real-time message push solution, but it has the limitations of high latency, high network load and poor real-time performance.
  • Long polling improves real-time performance by reducing the number of unnecessary requests by keeping requests pending, but polling is still required to obtain new messages.
  • SSE uses persistent connections to implement one-way real-time message push, which has good real-time performance, but only supports one-way communication from the server to the client.
  • WebSocket provides true two-way communication with optimal real-time performance and low latency, but requires attention to higher network load and browser compatibility.

Choosing an appropriate real-time message push solution depends on specific needs and scenarios. Based on the application requirements and expected user experience, developers can choose an appropriate solution to implement real-time message push functionality.

Note

The above implementations all belong to the demo level. For simple demonstration, all service assurance measures have been deleted, so there are shortcomings including but not limited to the following.

for example

  • sse
  1. No session management
  2. Clear text transmission
  • WebSocket
  1. To distinguish client connections, the current implementation belongs to message broadcasting.

  2. Connection reliability guarantee: heartbeat detection and automatic reconnection, etc.

  3. Clear text transmission of messages