【原文鏈接】:https://blog.tecchen.xyz ,博文同步發布到博客園。
由於精力有限,對文章的更新可能不能及時同步,請點擊上面的原文鏈接訪問最新內容。
歡迎訪問我的個人網站:https://www.tecchen.xyz 。
前言
本來我只是想整理下前后端如何傳輸數據這種交互過程,大概流程如下:
- 前台使用ajax通過get/post等方式提交數據到后端
- 后端如何獲取參數
- 經過業務處理后,返回前端對應的響應數據
- 前端接受到響應后,進行數據展示
但整理過程中,發現更多的相關知識也需要一起學習整理下,就展開了下面這個博客。
出身網絡工程專業,由於大學基本上沒有上過課;
工作后基本上用Java進行開發,7年來一直是個弱弱的單身小程序員;
部分是摘自百度百科或者其他網站,再根據自己工作這么多年的理解,編寫測試的相關代碼;
各種協議標准及報文格式等也是經過理論及實踐的驗證,三兩句的文字不能詮釋其本質;
種種前提使得本文大體方向應該還是正確的,內容並不精准,僅供學習理解用。
具體章節如需詳細展開,請留言,補充后,補充后持續更新。
TCP、HTTP協議
TCP(Transmission Control Protocol 傳輸控制協議)是一種面向連接的、可靠的、基於字節流的傳輸層通信協議。
-
連接建立
TCP是因特網中的傳輸層協議,使用三次握手協議建立連接。當主動方發出SYN連接請求后,等待對方回答SYN+ACK,並最終對對方的 SYN 執行 ACK 確認。這種建立連接的方法可以防止產生錯誤的連接,TCP使用的流量控制協議是可變大小的滑動窗口協議。
TCP三次握手的過程如下:
客戶端發送SYN(SEQ=x)報文給服務器端,進入SYN_SEND狀態。
服務器端收到SYN報文,回應一個SYN (SEQ=y)ACK(ACK=x+1)報文,進入SYN_RECV狀態。
客戶端收到服務器端的SYN報文,回應一個ACK(ACK=y+1)報文,進入Established狀態。
三次握手完成,TCP客戶端和服務器端成功地建立連接,可以開始傳輸數據了。
-
連接終止
建立一個連接需要三次握手,而終止一個連接要經過四次握手,這是由TCP的半關閉(half-close)造成的。具體過程如下圖所示。
TCP連接的終止
TCP連接的終止
(1) 某個應用進程首先調用close,稱該端執行“主動關閉”(active close)。該端的TCP於是發送一個FIN分節,表示數據發送完畢。
(2) 接收到這個FIN的對端執行 “被動關閉”(passive close),這個FIN由TCP確認。
注意:FIN的接收也作為一個文件結束符(end-of-file)傳遞給接收端應用進程,放在已排隊等候該應用進程接收的任何其他數據之后,因為,FIN的接收意味着接收端應用進程在相應連接上再無額外數據可接收。
(3) 一段時間后,接收到這個文件結束符的應用進程將調用close關閉它的套接字。這導致它的TCP也發送一個FIN。
(4) 接收這個最終FIN的原發送端TCP(即執行主動關閉的那一端)確認這個FIN。
既然每個方向都需要一個FIN和一個ACK,因此通常需要4個分節。
TCP狀態機
更多可以學習:
五分鍾讀懂TCP 協議——TCP協議簡介
TCP協議
TCP、UDP 的區別,三次握手、四次揮手
HTTP(HyperText Transfer Protocol,超文本傳輸協議)是互聯網上應用最為廣泛的一種網絡協議。
通常,由HTTP客戶端發起一個請求,建立一個到服務器指定端口(默認是80端口)的TCP連接。HTTP服務器則在那個端口監聽客戶端發送過來的請求。一旦收到請求,服務器(向客戶端)發回一個狀態行,比如"HTTP/1.1 200 OK",和(響應的)消息,消息的消息體可能是請求的文件、錯誤消息、或者其它一些信息。
通過HTTP或者HTTPS協議請求的資源由統一資源標示符(Uniform Resource Identifiers)(或者,更准確一些,URLs)來標識。
HTTP協議采用了請求/響應模型。客戶端向服務器發送一個請求,請求頭包含請求的方法、URL、協議版本、以及包含請求修飾符、客戶信息和內容的類似於MIME的消息結構。服務器以一個狀態行作為響應,響應的內容包括消息協議的版本,成功或者錯誤編碼加上包含服務器信息、實體元信息以及可能的實體內容。
URL、RESTful
統一資源定位符(Uniform Resource Locator,URL)是對可以從互聯網上得到的資源的位置和訪問方法的一種簡潔的表示,是互聯網上標准資源的地址。互聯網上的每個文件都有一個唯一的URL,它包含的信息指出文件的位置以及瀏覽器應該怎么處理它。
基本URL包含模式(或稱協議)、服務器名稱(或IP地址)、路徑和文件名,如“協議://授權/路徑?查詢”。完整的、帶有授權部分的普通統一資源標志符語法看上去如下:協議://用戶名:密碼@子域名.域名.頂級域名:端口號/目錄/文件名.文件后綴?參數=值#標志
REST全稱是Representational State Transfer。點擊了解更多RESTful 架構詳解。
HTTP詳解
HTTP目前分為以下版本,其中目前最流行的是HTTP/1.1版本。
- HTTP/2
- HTTP/1.1
- HTTP/1.0
- HTTP/0.9
各版本區別不作展開講解,HTTP協議的前世今生——各版本HTTP協議對比
HTTP報文格式詳解
發現網友寫的更詳細,就不浪費大家的時間了。Http協議報文格式
數據交互格式
可以通過GET/POST等方式將變量、對象、數組、JSON、XML、file/img、base64圖片、cookies等各種形式的數據提交到后端。
源碼下載:前后端交互的方式的演示代碼
GET
代碼示例:
/**
* GET-變量
*/
@GetMapping("/testVar1")
public String testVar1(/*@RequestParam("param")*/String param) {
return param;
}
/**
* GET-path變量
*/
@GetMapping("/testVar2/{param}")
public String testVar2(@PathVariable/*("param")*/ String param) {
return param;
}
/**
* GET-對象
*/
@GetMapping("/testObject1")
public GetParam testObject1(GetParam obj) {
return obj;
}
/**
* GET-數組
*/
@GetMapping("/testArray1")
public Object testArray1(String[] obj) {
return obj;
}
運行結果:
POST
POST方式可以使用curl、postman、getman、eolinker等工具實現。
/**
* POST-多個變量、對象(form提交)
*/
@PostMapping(value = "/testPostFormVar")
public String testPostVar1(@RequestParam(name = "username") String username,
@RequestParam(name = "password") String password) {
return username + ":" + password;
}
/**
* POST-JSON(ajax)
*/
@PostMapping(value = "/testPostJsonVar")
public PostParam testPostVar2(@RequestBody PostParam param) {
return param;
}
/**
* 通用的post接收方式
*/
@PostMapping(value = "/testPostVar")
public PostParam testPostVar(PostParam param) {
return param;
}
運行結果:
POST方式總結:
content-type | 數據格式 | 后台接受方式 | 專用注解 |
---|---|---|---|
application/x-www-form-urlencoded[默認] | json對象 | 對象 | @RequestParam |
application/json | json字符串 | 對象 | @RequestBody |
$.post() 默認application/x-www-form-urlencoded | json對象 | 對象 | @RequestParam |
數據交互技術
- 服務器渲染
談起服務端渲染,對於動態服務而言,這個世界上跑的大多數頁面都經過服務端的數據渲染,接口->前端賦值->模板渲染。這些都是在服務器完成,在我們查看源碼的時候,可以看到完整的html代碼,包括每個數據值。常用的php模板:Smarty,Blade,Mustache。如果使用Node.js作為服務端的話: ejs,doT,jade等。 - Form、Ajax、jsonp
服務端渲染隨着單頁面應用以及Restful接口的興起,Ajax逐漸成為目前前后端交流最為頻繁的方式。Ajax的核心是XmlHttpRequest。我們通過對該對象的操作來進行異步的數據請求。實際上我們接觸到最多jQuey就有很好的封裝,比如\(.ajax,\).post等,如果用Angular的話我們可以用$http服務,除了這些之外,我們可以使用第三方的Ajax庫qwest等。
跨域代碼示例(使用"http://localhost:9090/jsonp.html"的頁面發起請求):
后端:
@RestController
@RequestMapping("/cdr")
public class CrossDomainRequestsController {
/**
* 通用的post接收方式
*/
@RequestMapping(value = "/testCdr")
public PostParam testPostVar(PostParam param) {
return param;
}
}
前端:
<!DOCTYPE html>
<html>
<head>
<title>跨域</title>
<meta charset="UTF-8">
<script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>
<script>
$(function(){
$("#get").click(function () {
$.ajax({
type: 'get',
url: "http://localhost:8080/cdr/testCdr",
contentType: "application/x-www-form-urlencoded; charset=utf-8",
data:{
"username": "TEC-9",
"password": "wodemima"
},
success : function(data,status){
console.log("數據: \n" + JSON.stringify(data) + "\n狀態: " + status);
}
});
});
$("#post").click(function () {
$.ajax({
type: 'POST',
url: "http://localhost:8080/cdr/testCdr",
contentType: "application/x-www-form-urlencoded; charset=utf-8",
data:{
"username": "TEC-9",
"password": "wodemima"
},
success : function(data,status){
console.log("數據: \n" + JSON.stringify(data) + "\n狀態: " + status);
}
});
});
});
</script>
</head>
<body>
<h1></h1>
<div>
<table>
<thead>
<tr>
<td>請求地址</td>
<td>方式</td>
</tr>
</thead>
<tbody>
<tr>
<td><button id="get">標准的ajax-post提交驗證</button></td>
<td>get</td>
</tr>
<tr>
<td><button id="post">標准的ajax-post提交驗證</button></td>
<td>post</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>
執行結果:
跨域方案一:
直接添加@CrossOrigin注解
@RestController
@RequestMapping("/cdr")
@CrossOrigin
public class CrossDomainRequestsController {
跨域方案二:
JOSNP方式
$("#testJsonp").click(function () {
$.ajax({
type: 'get',
url: "http://localhost:8080/cdr/testJsonp",
contentType: "application/json; charset=utf-8",
dataType: "jsonp", //指定服務器返回的數據類型
jsonpCallback: "printResult", //指定回調函數名稱
data:{
"username": "TEC-9",
"password": "wodemima"
},
success : function(data,status){
// console.log("數據: \n" + JSON.stringify(data) + "\n狀態: " + status);
console.log("success")
}
});
});
@RequestMapping(value = "/testJsonp")
public JSONPObject testJsonp(PostParam param, String callback) {
return new JSONPObject(callback, param);
}
- Comet(不研究)
以即時通信為代表的web應用程序對數據的Low Latency要求,傳統的基於輪詢的方式已經無法滿足,而且也會帶來不好的用戶體驗。於是一種基於http長連接的“服務器推”技術便被hack出來。這種技術被命名為Comet,這個術語由Dojo Toolkit 的項目主管Alex Russell在博文Comet: Low Latency Data for the Browser首次提出,並沿用下來。 - SSE
SSE(Server-Sent Event,服務端推送事件)是一種允許服務端向客戶端推送新數據的HTML5技術。與由客戶端每隔幾秒從服務端輪詢拉取新數據相比,這是一種更優的解決方案。
示例代碼,也可以參考以下鏈接:
服務器推送消息SSE以及springmvc后台實現例子:
SSE技術詳解:使用 HTTP 做服務端數據推送應用的技術
前端:
if (window.EventSource) {
var eventSource = new EventSource("http://localhost:9090/es/testEventSource");
//只要和服務器連接,就會觸發open事件
eventSource.addEventListener("open", function () {
console.log("open和服務器建立連接");
});
//處理服務器響應報文中的load事件
eventSource.addEventListener("load", function (e) {
console.log("load服務器發送給客戶端的數據為:" + e.data);
if(!e.data) {
eventSource.close();
console.log("關閉");
}
});
//如果服務器響應報文中沒有指明事件,默認觸發message事件
eventSource.addEventListener("message", function (e) {
console.log("message服務器發送給客戶端的數據為:" + e.data);
});
//發生錯誤,則會觸發error事件
eventSource.addEventListener("error", function (e) {
console.log("error服務器發送給客戶端的數據為:" + e.data);
});
}
else {
console.log("服務器不支持EvenSource對象");
}
后端:
static Integer randomInt = 0;
@RequestMapping(value = "/testEventSource", produces = "text/event-stream")
public String testEventSource() {
//響應報文格式為:
//data:Hello World
//event:load
//id:140312
//換行符(/r/n)
StringBuffer result = null;
// open/load/message/error
result = new StringBuffer();
if (randomInt == 0) {
result = new StringBuffer();
result.append("event:open");
result.append("\n");
result.append("data:" + randomInt++);
result.append("\n\n");
return result.toString();
}
while (randomInt < 5) {
result = new StringBuffer();
result.append("event:load");
result.append("\n");
result.append("data:" + randomInt++);
result.append("\n\n");
return result.toString();
}
result = new StringBuffer();
result.append("event:load");
result.append("\n");
result.append("data:");
result.append("\n\n");
return result.toString();
}
- Websocket
WebSocket協議是基於TCP的一種新的網絡協議。它實現了瀏覽器與服務器全雙工(full-duplex)通信——允許服務器主動發送信息給客戶端。
學習鏈接:使用 HTML5 WebSocket 構建實時 Web 應用
小結
說了那么多簡單總結下,大家想明白幾點就行了,客戶端與服務端誰先主動,是否強調數據的實時性。
AJAX – 請求 → 響應 (頻繁使用)
Comet – 請求 → 掛起 → 響應 (模擬服務端推送)
Server-Sent Events – 客戶單 ← 服務端 (服務端推送)
WebSockets – 客戶端 ↔ 服務端 (未來趨勢,雙工通信)