- 分布式系統(tǒng)架構(gòu):技術(shù)棧詳解與快速進(jìn)階
- 張程
- 2429字
- 2020-08-13 13:45:31
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所示。

圖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所示。

圖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)通知客戶端等交互情況,如在線聊天、通信。
- Web前端開發(fā)簡明教程(HTML+CSS+JavaScript+jQuery)
- 網(wǎng)絡(luò)化聯(lián)合仿真的時(shí)間同步
- TMS320C55x DSP原理及應(yīng)用(第3版)
- 網(wǎng)絡(luò)工程設(shè)計(jì)與系統(tǒng)集成(第2版)
- 新型網(wǎng)絡(luò)體系結(jié)構(gòu)
- ARM嵌入式體系結(jié)構(gòu)與接口技術(shù)(Cortex-A8版)
- 計(jì)算思維的結(jié)構(gòu)
- 大學(xué)計(jì)算機(jī)基礎(chǔ)(第6版)
- TMS 320 F28x源碼解讀
- 計(jì)算機(jī)體系結(jié)構(gòu)基礎(chǔ)(第3版)
- GPU高性能運(yùn)算之CUDA
- 嵌入式系統(tǒng)開發(fā)基礎(chǔ)與實(shí)踐教程
- 大模型時(shí)代的基礎(chǔ)架構(gòu):大模型算力中心建設(shè)指南
- 兼容ARM9的軟核處理器設(shè)計(jì):基于FPGA
- 計(jì)算機(jī)組成原理(基于x86-64架構(gòu))