官术网_书友最值得收藏!

2.2.1 工作流程

1. HTTP

HTTP是一種網(wǎng)絡(luò)傳輸?shù)膽?yīng)用層協(xié)議,是一種建立在TCP上的無狀態(tài)連接。其工作流程如下。

客戶端發(fā)送一個(gè)HTTP請(qǐng)求,服務(wù)器端收到請(qǐng)求之后,根據(jù)請(qǐng)求的參數(shù)進(jìn)行解析讀取,處理完畢后把相應(yīng)的內(nèi)容通過HTTP響應(yīng)返回到客戶端,交互過程如圖2-4所示。

033-1

圖2-4 HTTP請(qǐng)求交互圖

HTTP結(jié)構(gòu)分為以下部分。

(1)HTTP請(qǐng)求

HTTP請(qǐng)求由狀態(tài)行、請(qǐng)求頭、請(qǐng)求正文三部分組成。其中,狀態(tài)行包括請(qǐng)求方式Method、資源路徑URL、協(xié)議版本Version;請(qǐng)求頭包括訪問域名、Cookie、代理信息等;請(qǐng)求正文包括請(qǐng)求數(shù)據(jù)。

(2)HTTP響應(yīng)

在收到客戶端發(fā)送的HTTP請(qǐng)求后,服務(wù)器端會(huì)根據(jù)請(qǐng)求方式做出具體動(dòng)作,將結(jié)果返回客戶端。請(qǐng)求響應(yīng)包括狀態(tài)行、響應(yīng)頭、響應(yīng)正文三部分組成。其中,狀態(tài)行包括狀態(tài)碼、協(xié)議版本Version;響應(yīng)頭包括發(fā)送響應(yīng)時(shí)間、回應(yīng)數(shù)據(jù)格式;響應(yīng)文正包括響應(yīng)數(shù)據(jù)。

HTTP版本分為1.0、1.1、2.0,各版本說明如下。

1)HTTP/1.*:一次請(qǐng)求-響應(yīng),建立一個(gè)連接,用完關(guān)閉。

2)HTTP/1.1:串行化單線程處理,可以同時(shí)在同一個(gè)TCP鏈接上發(fā)送多個(gè)請(qǐng)求,但是只有響應(yīng)是有順序的,只有上一個(gè)請(qǐng)求完成后,下一個(gè)才能響應(yīng)。一旦有任務(wù)處理超時(shí)等,后續(xù)任務(wù)只能被阻塞(線頭阻塞)。

3)HTTP/2:并行執(zhí)行。某任務(wù)耗時(shí)嚴(yán)重,不會(huì)影響到任務(wù)正常執(zhí)行。

HTTP適用場景:HTTP適用于客戶端主動(dòng)請(qǐng)求服務(wù)器端獲取數(shù)據(jù)的情況,適用于大部分業(yè)務(wù)場景。但客戶端和服務(wù)器端交互時(shí),有些場景需要服務(wù)器端主動(dòng)發(fā)送內(nèi)容至客戶端,HTTP不能高效實(shí)現(xiàn)。

2. WebSocket

傳統(tǒng)HTTP協(xié)議不能滿足客戶端和服務(wù)器端的雙向通信,WebSocket的誕生解決了這個(gè)問題。WebSocket擴(kuò)展了客戶端與服務(wù)器端的通信功能,使服務(wù)器端也能主動(dòng)向客戶端發(fā)送數(shù)據(jù)。

傳統(tǒng)的HTTP協(xié)議是無狀態(tài)的,每次請(qǐng)求(request)都要由客戶端主動(dòng)發(fā)起,再由服務(wù)器端處理后返回響應(yīng)結(jié)果,而服務(wù)器端很難主動(dòng)向客戶端發(fā)送數(shù)據(jù)。這種客戶端是主動(dòng)方、服務(wù)器端是被動(dòng)方的傳統(tǒng)Web模式,對(duì)于信息變化不頻繁的Web應(yīng)用來說造成的麻煩較小,而對(duì)于涉及實(shí)時(shí)信息的Web應(yīng)用卻帶來了很大不便,如帶有即時(shí)通信、實(shí)時(shí)數(shù)據(jù)、訂閱推送等功能的應(yīng)用。傳統(tǒng)的處理方式有輪詢(polling)和Comet技術(shù),而WebSocket本質(zhì)上也是一種輪詢,是優(yōu)化改進(jìn)后的交互方式。

輪詢是最原始的實(shí)現(xiàn)實(shí)時(shí)Web應(yīng)用的解決方案。輪詢是指客戶端以設(shè)定的時(shí)間間隔周期性地主動(dòng)向服務(wù)器端發(fā)送請(qǐng)求,頻繁地查詢是否有新的數(shù)據(jù)改動(dòng)。這種方法會(huì)導(dǎo)致過多不必要的請(qǐng)求,浪費(fèi)流量和服務(wù)器端資源。

Comet技術(shù)又可以分為長輪詢和流技術(shù)。長輪詢改進(jìn)了上述輪詢技術(shù),減少了無用的請(qǐng)求。它會(huì)為某些數(shù)據(jù)設(shè)定過期時(shí)間,當(dāng)數(shù)據(jù)過期后才會(huì)向服務(wù)端發(fā)送請(qǐng)求。這種機(jī)制適合數(shù)據(jù)改動(dòng)不是特別頻繁的情況。流技術(shù)通常是指客戶端使用一個(gè)隱藏的窗口與服務(wù)器端建立一個(gè)HTTP長連接,服務(wù)器端會(huì)不斷更新連接狀態(tài)以保持HTTP長連接存活,然后通過這條長連接主動(dòng)將數(shù)據(jù)發(fā)送給客戶端。在大并發(fā)環(huán)境下,流技術(shù)可能會(huì)考驗(yàn)服務(wù)端的性能。

WebSocket是一種網(wǎng)絡(luò)通信協(xié)議,是H5提出的一種協(xié)議規(guī)范,主要用于客戶端和服務(wù)器端實(shí)時(shí)通信,本質(zhì)是基于TCP進(jìn)行網(wǎng)絡(luò)傳輸。

WebSocket工作流程如下。瀏覽器通過腳本向服務(wù)器端發(fā)出建立WebSocket類型連接的請(qǐng)求,WebSocket連接建立成功后,客戶端和服務(wù)器端就可以通過TCP連接傳輸數(shù)據(jù),不需要每次傳輸都帶上重復(fù)的頭部數(shù)據(jù),所以它的數(shù)據(jù)傳輸量比輪詢要少很多。交互過程如圖2-5所示。

035-1

圖2-5 前后端用WebSocket交互效果

注意

WebSocket可以實(shí)現(xiàn)由服務(wù)器主動(dòng)向客戶端推送消息的功能,而不需要依賴來自客戶端的請(qǐng)求,增加了服務(wù)器的主動(dòng)性和靈活性。

HTTP的通信只能由客戶端發(fā)起,而WebSocket的通信在客戶端與服務(wù)器握手建立連接后,雙方即可相互傳送信息,直至任意一方主動(dòng)斷開連接結(jié)束。

WebSocket依賴的是TCP協(xié)議,因此在連接建立后,傳輸數(shù)據(jù)量比依賴HTTP的傳輸數(shù)據(jù)量要小,提高了傳輸?shù)男省M瑫r(shí),WebSocket是長連接,受網(wǎng)絡(luò)限制較大,使用時(shí)需著重考慮重連等問題,另外WebSocket做實(shí)時(shí)推送,特別是多頁面復(fù)雜邏輯的實(shí)時(shí)推送,成本較高。

前端通過WebSocket調(diào)用后端代碼的具體實(shí)現(xiàn)如代碼清單2-6所示。

代碼清單2-6 前端通過WebSocket調(diào)用后端代碼

<%@ page language="java" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<title>WebSocket</title>
</head>
<body>
    Welcome WebSocket
    <input id="text" type="text" />
    <button onclick="send()">發(fā)送消息</button>
    <button onclick="closeWebSocket()">關(guān)閉WebSocket連接</button>
    <div id="message"></div>
</body>

<script type="text/javascript">
    var websocket = null;
    // 判斷當(dāng)前瀏覽器是否支持WebSocket
    if ('WebSocket' in window) {
        websocket = new WebSocket("ws:// localhost:8080/websocket");
    } else {
        alert('當(dāng)前瀏覽器Not support websocket')
    }

    // 連接發(fā)生錯(cuò)誤的回調(diào)方法
    websocket.onerror = function() {
        setMessageInnerHTML("WebSocket連接發(fā)生錯(cuò)誤");
    };

    // 連接成功建立的回調(diào)方法
    websocket.onopen = function() {
        setMessageInnerHTML("WebSocket連接成功");
    }

    // 接收到消息的回調(diào)方法
    websocket.onmessage = function(event) {
        setMessageInnerHTML(event.data);
    }

    // 連接關(guān)閉的回調(diào)方法
    websocket.onclose = function() {
        setMessageInnerHTML("WebSocket連接關(guān)閉");
    }

    // 監(jiān)聽窗口關(guān)閉事件,當(dāng)窗口關(guān)閉時(shí),主動(dòng)去關(guān)閉WebSocket連接,防止連接還沒斷開就關(guān)閉窗口,server端會(huì)拋異常
    window.onbeforeunload = function() {
        closeWebSocket();
    }

    // 將消息顯示在網(wǎng)頁上
    function setMessageInnerHTML(innerHTML) {
        document.getElementById('message').innerHTML += innerHTML;
    }

    // 關(guān)閉WebSocket連接
    function closeWebSocket() {
        websocket.close();
    }
     
    // 發(fā)送消息
    function send() {
        var message = document.getElementById('text').value;
        websocket.send(message);
    }
</script>
</html>

服務(wù)器端WebSocket處理方式如代碼清單2-7所示。

代碼清單2-7 服務(wù)器端WebSocket處理代碼

package com.zachary.springboot.blog.pushlian.bean;

import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;

import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;

/**
 * author zachary
 * desc @ServerEndpoint主要是將目前的類定義成一個(gè)WebSocket服務(wù)器端, 注解的值將被用于監(jiān)聽用戶連接的終端訪問URL地址, 客戶端可以通過這個(gè)URL來連接到WebSocket服務(wù)器端
 */
@ServerEndpoint("/websocket")
public class WebSocketTest {

    // 靜態(tài)變量,用來記錄當(dāng)前在線連接數(shù)。應(yīng)該把它設(shè)計(jì)成線程安全的
    private static int onlineCurrCount = 0;

    // concurrent包的線程安全Set,用來存放每個(gè)客戶端對(duì)應(yīng)的MyWebSocket對(duì)象。若要實(shí)現(xiàn)服務(wù)端與單一客戶端通信的話,可以使用Map來存放,其中Key可以作為用戶標(biāo)識(shí)
    private static CopyOnWriteArraySet<WebSocketTest> webSocketSet = new Co-pyOnWriteArraySet<WebSocketTest>();

    // 與某個(gè)客戶端的連接會(huì)話,需要通過它來給客戶端發(fā)送數(shù)據(jù)
    private Session session;

    /**
     * 連接建立成功調(diào)用的方法
     *
     * @param session可選的參數(shù)。session為與某個(gè)客戶端的連接會(huì)話,需要通過它來給客戶端發(fā)送數(shù)據(jù)
     */
    @OnOpen
    public void onOpen(Session session) {
        this.session = session;
        webSocketSet.add(this);           // 加入set中
        addOnlineCurrCount();             // 在線數(shù)加1
        System.out.println("有新連接加入!當(dāng)前在線人數(shù)為" + getOnlineCurrCount());
    }

    /**
     * 連接關(guān)閉調(diào)用的方法
     */
    @OnClose
    public void onClose() {
        webSocketSet.remove(this);        // 從set中刪除
        subOnlineCurrCount();             // 在線數(shù)減1
        System.out.println("有一連接關(guān)閉!當(dāng)前在線人數(shù)為" + getOnlineCurrCount());
    }

    /**
     * 收到客戶端消息后調(diào)用的方法
     *
     * @param message 客戶端發(fā)送過來的消息
     * @param session 可選的參數(shù)
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        System.out.println("來自客戶端的消息:" + message);
        // 群發(fā)消息
        for (WebSocketTest item : webSocketSet) {
            try {
                item.sendMessage(message);
            } catch (IOException e) {
                e.printStackTrace();
                continue;
            }
        }
    }

    /**
     * 發(fā)生錯(cuò)誤時(shí)調(diào)用
     *
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        System.out.println("發(fā)生錯(cuò)誤");
        error.printStackTrace();
    }

    /**
     * 這個(gè)方法與上面幾個(gè)方法不一樣。沒有用注解,是根據(jù)自己需要添加的方法。* @param message
     *
     * @throws IOException
     */
    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
        // this.session.getAsyncRemote().sendText(message);
    }

    public static synchronized int getOnlineCurrCount() {
        return onlineCurrCount;
    }

    public static synchronized void addOnlineCurrCount() {
        WebSocketTest.onlineCurrCount++;
    }

    public static synchronized void subOnlineCurrCount() {
        WebSocketTest.onlineCurrCount--;
    }
}

WebSocket適用于服務(wù)器主動(dòng)通知客戶端等交互情況,如在線聊天、通信。

主站蜘蛛池模板: 襄城县| 和政县| 瑞安市| 包头市| 苍山县| 湘潭县| 隆安县| 曲松县| 鲜城| 和龙市| 黑龙江省| 凌海市| 徐汇区| 保德县| 西乌珠穆沁旗| 来安县| 蒙阴县| 金寨县| 吴忠市| 江陵县| 教育| 阿合奇县| 元朗区| 曲靖市| 张家口市| 东乌珠穆沁旗| 汤阴县| 西宁市| 宁都县| 和平县| 方山县| 广汉市| 襄垣县| 佳木斯市| 枝江市| 云浮市| 宜州市| 彰化市| 龙海市| 辉县市| 长沙县|