1. 程式人生 > 其它 >跨域訪問方法介紹(8)--使用 WebSocket

跨域訪問方法介紹(8)--使用 WebSocket

WebSocket 是 HTML5 開始提供的一種在單個 TCP 連線上進行全雙工通訊的協議;使用ws://(非加密)和wss://(加密)作為協議字首。該協議不實行同源政策,只要伺服器支援,就可以通過它進行跨源通訊。本文主要介紹使用 WebSocket 來實現跨域請求,文中所使用到的軟體版本:Chrome90.0.4430.212、Spring Boot2.4.4、jdk1.8.0_181。

1、WebSocket 簡介

WebSocket 使得客戶端和伺服器之間的資料交換變得更加簡單,允許服務端主動向客戶端推送資料。在 WebSocket API 中,瀏覽器和伺服器只需要完成一次握手,兩者之間就直接可以建立永續性的連線,並進行雙向資料傳輸。在 WebSocket API 中,瀏覽器和伺服器只需要做一個握手的動作,然後,瀏覽器和伺服器之間就形成了一條快速通道。兩者之間就直接可以資料互相傳送。

目前,很多網站都使用 Ajax 輪詢來實現推送。輪詢是在特定的的時間間隔(如每1秒),由瀏覽器對伺服器發出HTTP請求,然後由伺服器返回最新的資料給客戶端的瀏覽器。這種傳統的模式帶來很明顯的缺點,即瀏覽器需要不斷的向伺服器發出請求,然而HTTP請求可能包含較長的頭部,其中真正有效的資料可能只是很小的一部分,顯然這樣會浪費很多的頻寬等資源。

HTML5 定義的 WebSocket 協議,能更好的節省伺服器資源和頻寬,並且能夠更實時地進行通訊。

瀏覽器通過 WebSocket 物件向伺服器發出建立 WebSocket 連線的請求,連線建立以後,客戶端和伺服器端就可以通過 TCP 連線直接交換資料。當獲取 WebSocket 連線後,就可以通過 send() 方法來向伺服器傳送資料,並通過 onmessage 事件來接收伺服器返回的資料。

1.1、WebSocket 語法

let Socket = new WebSocket(url, [protocol]);

1.2、WebSocket 屬性

屬性 描述
WebSocket.readyState 只讀屬性 readyState 表示連線狀態,可以是以下值:
0 - 表示連線尚未建立。
1 - 表示連線已建立,可以進行通訊。
2 - 表示連線正在進行關閉。
3 - 表示連線已經關閉或者連線不能開啟。
WebSocket.bufferedAmount 只讀屬性 bufferedAmount 表示已被 send() 放入佇列中正在等待傳輸,但是還沒有發出的 UTF-8 文字位元組數。

1.3、WebSocket 事件

事件 事件處理程式 描述
open WebSocket.onopen 連線建立時觸發
message WebSocket.onmessage 客戶端接收服務端資料時觸發
error WebSocket.onerror 通訊發生錯誤時觸發
close WebSocket.onclose 連線關閉時觸發

1.4、WebSocket 例項

WebSocket 協議本質上是一個基於 TCP 的協議。為了建立 WebSocket 連線,客戶端瀏覽器首先要向伺服器發起一個 HTTP 請求,這個請求和通常的 HTTP 請求不同,包含了一些附加頭資訊,其中附加頭資訊"Upgrade: WebSocket"表明這是一個申請協議升級的 HTTP 請求,伺服器端解析這些附加的頭資訊然後產生應答資訊返回給客戶端,客戶端和伺服器端的 WebSocket 連線就建立起來了,雙方就可以通過這個連線通道自由的傳遞資訊,並且這個連線會持續存在直到客戶端或者伺服器端的某一方主動的關閉連線。

2、WebSocket 實戰

2.1、服務端實現(SpringBoot 版)

2.1.1、引入依賴

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

2.1.2、注入 ServerEndpointExporter

package com.abc.demo.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();
    }
}

2.1.3、WebSocket 實現類

package com.abc.demo.websocket;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;

@ServerEndpoint(value = "/websocket")
@Component
public class WebSocketTest {
    private static Logger logger = LoggerFactory.getLogger(WebSocketTest.class);

    @OnOpen
    public void onOpen(Session session) {
        logger.info("有新連線加入:{}", session.getId());
        sendMessage(session);
    }

    @OnClose
    public void onClose(Session session) {
        logger.info("有一連線關閉:{}", session.getId());
    }

    @OnMessage
    public void onMessage(String message, Session session) {
        logger.info("服務端收到客戶端[{}]的訊息:{}", session.getId(), message);
    }

    @OnError
    public void onError(Session session, Throwable error) {
        logger.error("發生錯誤");
        error.printStackTrace();
    }

    private void sendMessage(Session session) {
        new Thread(() -> {
            try {
                for (int i = 0; i <= 10 && session.isOpen(); i++) {
                    session.getBasicRemote().sendText("服務端訊息" + i);
                    Thread.sleep(1000 * 5);
                }
                if (session.isOpen()) {
                    session.getBasicRemote().sendText("服務端訊息傳送完畢。");
                }
                session.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();
    }
}

2.2、客戶端實現

websocket.html

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>websocket測試</title>

<script type="text/javascript">
    let websocket = null;
    function initSocket() {
        if (window.WebSocket) {
            websocket = new WebSocket("ws://localhost:8081/websocket");
        } else {
            alert('你的瀏覽器不支援WebSocket');
            return;
        }
        
        websocket.onerror = function() {
            console.log('發生錯誤');
        };
    
        websocket.onopen = function(event) {
            console.log("建立連線");
        }
    
        websocket.onmessage = function(event) {
            document.getElementById('message').innerHTML += event.data + '<br/>';
        }
    
        websocket.onclose = function() {
            console.log("連線關閉");
        }
        
        //視窗關閉事件,關閉websocket連線。
        window.onbeforeunload = function() {
            websocket.close();
        }
    }
    initSocket();
    
    function closeWebSocket() {
        websocket.close();
    }

    function send() {
        var message = document.getElementById('text').value;
        websocket.send(message);
    }
</script>
</head>
<body>
    <input id="text" type="text" />
    <button onclick="send()">Send</button>
    <button onclick="closeWebSocket()">Close</button>
    <div id="message"></div>
</body>

</html>

2.3、測試

把 websocket.html 放到 tomcat(埠:8080) 的webapps\ROOT 下,並啟動 SpringBoot 應用(埠:8081)。

參考:https://www.runoob.com/html/html5-websocket.html