There was a project involving instant messaging technology on the Web side before, so I took time to take stock of the existing instant messaging technology on the Web, the mainstream are: short polling, Comet (long polling + IFrame based HTTP stream), SSE, websocket, there are a lot of articles for in-depth text introduction, this paper directly with the form of drawing comb. The following will be a brief introduction one by one, if there is any improper place welcome to point out.

Short polling

Short polling is traditional polling, using setTimeout or setInterval for timed requests.

The dotted purple line represents data updates (same as the following image)

/ / the front
getResultByPolling(){
  this.timer = setInterval(this.server.ajax.bind(this, {url:'/demo/polling'.success:(res) = >{
      if(res.data.end)
        clearInterval(this.timer); }}),200)}Copy the code
// Server side
@RequestMapping(value = "/polling")
public JSONObject polling(HttpServletRequest request) {

    JSONObject object = new JSONObject();
    object.put("end".false);

    int number = (int)(Math.random()*20);
    if(number > 2 && number < 5) {// New data appears in the simulation
        object.put("end".true);
    }

    return Result.SUCCESS.toJson(object);
}
Copy the code

Short polling is still the choice of most people, including many large factories. The main reason, simplicity, especially on the server side, requires no special treatment. After all, you can’t do it without an interface.

Second, the Comet

Comet refers to a “server push” technology based on HTTP long connections, including long polling and HTTP streaming based on IFrame.

1. Long polling

Long polling is a copy of short polling, or an upgrade. It is divided into two types, one is blocking and the other is non-blocking (asynchronous).

A, block type

It can be understood that the server periodically obtains data.

/ / the front
getResultByLongPollingBlock(){
  this.server.ajax({
    url:'/demo/long-polling/block'.success:(res) = >{
      console.log('Long polling (blocking)',res.data); }})}Copy the code
// Server side
@RequestMapping(value = "/long-polling/block")
public JSONObject longPollingBlock(HttpServletRequest request) {
    log.info("Begin the request.");
    JSONObject object = new JSONObject();
    object.put("end".false);

    // The emulated server is always looking for new data
    while(true) {int number = (int)(Math.random()*20);
        if(number > 2 && number < 5) {// New data appears in the simulation
            object.put("end".true);
            break;
        }
        else{
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    log.info("Request terminated");
    return Result.SUCCESS.toJson(object);
}
Copy the code

B, non-blocking

Asynchronously processed on the server side, polling responses are triggered when data changes and the connection is closed.

/ / the front
getResultByLongPolling(namespace){
  this.server.ajax({
    url:`/demo/long-polling/${namespace}`.success:(res) = >{
      console.log('Long poll',res)
    }
  })
}

modifyData(namespace){
  this.server.ajax({
    url:`/demo/modify/${namespace}`.success:(res) = >{
      console.log('Modify data',res)
    }
  })
}
Copy the code
// Server side

// Long polling - non-blocking
@RequestMapping(value = "/long-polling/{namespace}")
public DeferredResult<JSONObject> longPolling(HttpServletRequest request,
                                          @PathVariable("namespace") String namespace) {
    log.info("Request initiated");
    DeferredResult<JSONObject> deferredResult = new DeferredResult<>(OUT_OF_TIME,Result.OUT_OF_TIME_RESULT.toJson());

    deferredResult.onTimeout(() -> {
        log.info("Call timeout");
    });
    deferredResult.onCompletion(() -> {
        log.info("Call complete, remove namespace:" + namespace + "Surveillance");
        List<DeferredResult<JSONObject>> list = watchRequests.get(namespace);
        list.remove(deferredResult);
        if(list.isEmpty()) { watchRequests.remove(namespace); }}); List<DeferredResult<JSONObject>> list = watchRequests.computeIfAbsent(namespace, (k) ->new ArrayList<>());
    list.add(deferredResult);

    log.info("Request terminated");
    return deferredResult;
}

// Modify the data
@GetMapping(value = "/modify/{namespace}")
public void modifyData(@PathVariable("namespace") String namespace) {

    if (watchRequests.containsKey(namespace)) {
        List<DeferredResult<JSONObject>> deferredResults = watchRequests.get(namespace);
        // Notify all watch of the namespace changes
        for (DeferredResult<JSONObject> deferredResult : deferredResults) {
            deferredResult.setResult(Result.SUCCESS.toJson(namespace + "Changed, time as"+ System.currentTimeMillis())); }}}Copy the code

Long polling differs from short polling: Short polling means that the server sends a response immediately after receiving a request, regardless of whether the data is valid or not. Long polling, on the other hand, waits for data to be sent and sends a response. Advantages: Significantly reduces the number of unnecessary HTTP requests and saves resources in comparison. Disadvantages: Suspended connections can also lead to waste of resources.

2. Htmlfile stream based on iframe

/ / the front
getResultByIframe(){
  let iframe = document.createElement('iframe');
  iframe.src = '/demo/iframe';
  iframe.style = 'display: none';
  document.body.appendChild(iframe);
  // Define the data processing method
  window.addMsg = (data) = >{
    console.log(data); }}Copy the code
// Server side
@RequestMapping("/iframe")
public void streamIframe(HttpServletRequest request, HttpServletResponse response) throws Exception {
    // The emulated server is always looking for new data
    try {
        for (int i = 0; i < 10; i++) {
            PrintWriter out = response.getWriter();
            out.println("<script>parent.addMsg('hello"+i+"')</script>");
            out.flush();
            log.info("emit:{}"."hello" + i);
            Thread.sleep(1000 * 1); }}catch (Exception e) {
        e.printStackTrace();
        return; }}Copy the code

Disadvantages:

  • Cross domain problems.
  • Internet Explorer, Chrome, and Firefox will display the loading is not complete, and the icon will keep rotating.
  • Unable to handle errors or track connection status.
  • During the establishment and closure of HTTP connections, new data generated on the server may be lost because it cannot be sent to the client in time.

COMET technology is not part of the HTML5 standard and is not recommended.

Third, the SSE

SSE, a new feature in HTML5 called Server-Sent Events, is similar to the above iframe stream pattern, but with Ajax instead of iframe retrieving data. The content-type of the response header must be text/event-stream, which indicates that the response content is an event stream. If the client does not actively notify to close the connection, it will wait for the server to continuously send response results.

/ / the front
getResultBySSE(){
  if(!window.EventSource){
    console.log('EventSource is not supported ');
    return ;
  }
  let evtSource = new EventSource(`/demo/sse`);
  evtSource.onopen = function (event) { // The server is successfully connected
    console.log('Connection successful')
  }
  evtSource.onmessage = function(event) {
    console.log('Get data', event.data)
    if(event.data == 'hello9') // The demand front end is actively closed, otherwise the request loop will occur
       evtSource.close();
  }
  evtSource.onerror = function (error) { // Listening error
    console.log('Request error')}}Copy the code
// Server side
@RequestMapping("/sse")
public SseEmitter streamSse(a) {
    final SseEmitter emitter = new SseEmitter(0L); //timeout If this parameter is set to 0, no timeout is set
    taskExecutor.execute(() -> {
        try {
            for(int i=0; i<10; i++){ emitter.send("hello"+i);
                log.info("emit:{}"."hello"+i);
                Thread.sleep(1000*1);
            }
            emitter.complete();
        } catch (Exception e) {
            emitter.completeWithError(e);
            return; // Avoid exceptions caused by front-end browser shutdown or browser crash}});return emitter;
}
Copy the code

Compatibility:

The compatibility of IE can be solved by introducing event-source-polyfill.

Four, Websocket

Websocket is a two-way communication technology based on HTTP and TCP protocol. The client negotiates the upgrade protocol with the WebSocket server through HTTP requests. After the protocol upgrade, the subsequent data exchange follows the WebSocket protocol.

/ / the front
getResultByWebSocket(){
  if(!window.WebSocket){
    console.log('WebSocket not supported ');
    return ;
  }
  this.websocket = new WebSocket("ws://***/websocket/aaa");
  // A connection error occurred
  this.websocket.onerror = function(){
      console.log('websocket:error');
  };
  // The connection was successfully established
  this.websocket.onopen = function(event){
      console.log('websocket:open');
  }
  // The message is received
  this.websocket.onmessage = function(event){
      console.log('New information',event.data);
  }
  // The connection is closed
  this.websocket.onclose = function(){
    console.log('websocket: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 = this.closeWebSocket.bind(this);
}
//websocket sends messages
postMessage(){
  let message = document.getElementById('message').value;
  if(message) this.websocket.send(message);
}
closeWebSocket(){
  this.websocket.close();
}
Copy the code
// Server side

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

/ / ServerEndpointExporter injection
@Configuration
public class WebSocketConfig {
    private static final long serialVersionUID = 7600559593733357846L;

    @Bean
    public ServerEndpointExporter serverEndpointExporter(a) {
        return newServerEndpointExporter(); }}//websocket
@Slf4j
@Component
@ServerEndpoint(value="/websocket/{namespace}")
public class WebsocketController {

    private static String namespace;

    // execute on connection
    @OnOpen
    public void onOpen(@PathParam("namespace") String namespace, Session session) throws IOException{
        this.namespace = namespace;
        log.debug("New connection: {}",namespace);
    }

    // Execute when closed
    @OnClose
    public void onClose(a){
        log.debug("Connection: {} closed".this.namespace);
    }

    // Execute when a message is received
    @OnMessage
    public void onMessage(String message, Session session) throws IOException {
        log.debug("Received message {} from user {}".this.namespace,message);
        session.getBasicRemote().sendText(message);
    }

    // Execute when connection error occurs
    @OnError
    public void onError(Session session, Throwable error){
        log.debug("Error sending connection for user ID: {}".this.namespace); error.printStackTrace(); }}Copy the code

Compatibility:

Fifth, SockJS

Finally, I would like to recommend a librarySockJSSockJS is a JavaScript library that provides a Websocket-like object for browsers. It will use native WebSockets in preference; If the browser does not support sse, use SSE. If SSE also does not support this, use long polling. To use SockJS, you need to use the corresponding server-side library, and there are many languages supported at present.