- | - |
---|---|
應用層 | Http/Telent/Ftp/Email/DNS |
SPDY | HTTP2.0繼承 |
SSL/TLS | Secure Sockets Layer |
傳輸層 | TCP/UDP |
網絡層 | IP/ICMP/ARP |
鏈路層 | 設備驅動程序及接口 |
TCP
高可靠性的端到端通信
基于
byte
流,每一條TCP連接只能點對點全雙工
,可以同時進行信號的雙向傳輸-
分組:將應用層數據流打包成若干小數據塊,并封裝在IP分組中(幾百 ~ 40byte)
Internet自身無法確保可靠的分組傳輸,超負荷可以隨意丟棄分組,所以TCP實現了自己的確認機制來確保數據的成功傳輸
每個報文段包含一個序列號和數據完整性校驗和,發送后啟動一個定時器;
另一端對收到的數據進行確認,對失序的數據重新排列,丟棄重復數據;并向發送者會送小的確認分組,如果發送者沒有在指定的窗口時間內收到確認信息,發送者就認為分組已被破壞或銷毀,并重新發送數據
報文
三次握手
四次揮手
性能
Nagle算法
:將大量的小數據分組綁定在一起,發送全尺寸,提高網絡效率延遲確認
:由于TCP規定每次都要向發送者回送確認分組,且確認分組報文很小,所以TCP允許在發往相同方向的輸出數據分組中對其進行"捎帶",即將返回的確認信息與輸出的數據分組結合在一起。所以確認分組會在一個特定的時間窗口(100 ~ 200 ms)內將輸出確認放在緩沖區,以尋找能夠捎帶的輸出數據分組。如果沒有等到,則放在單獨的分組中傳輸TCP慢啟動
:為了防止網絡突然過載和擁塞,限制了TCP端口在任意時刻可以傳輸的分組數。每成功接收一個分組,就可以發送兩個分組,等待確認后,可以發送四個分組,一次類推。表現為某個HTTP事務有大量數據要發送,是不能一次將所有分組都發送出去的;新連接的傳輸速度會比已經交換過一定數量的連接慢一些TIME_WAIT
:當某個TCP端點關閉連接時,會在內存中維護一個小的控制塊,用來記錄最近關閉連接的IP地址和端口號。這類信息指揮維持一小段時間,以防止創建、關閉并重新創建兩個具備相同IP、端口號的連接;但在高并發情況下可能引起端口耗盡問題,要解決這個問題可以使用負載均衡(增加機器數量)
HTTP
建立在TCP協議之上,所以瓶頸及其優化技巧都是基于TCP協議本身的特性
年份 | 版本 | 改動內容 |
---|---|---|
1991 | 0.9 | 只有GET方式 |
1996 | 1.0 | GET、POST、HEAD方式,加入Header |
*1997 | 1.1 | 多種提交方式,默認都是持續連接 |
2009 | SPDY | |
2015 | 2.0 |
Request
GET /index.html HTTP/1.1
Accept: mime type
Accept-Encoding:
If-Modified-Since: EEE, dd MMM yyyy HH:mm:ss z // 文件是否修改
Referer: http://www.it315.org/index.jsp // 說明請求來自哪里,防盜鏈
User-Agent: Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0) 客戶端代理
Cookie // 向服務器發送Cookie
Range: byte=100-999 // 多線程多點續傳
username=ccy&userpwd=118
Response
Content-Type: mime type; charset=utf-8
Content-Encoding: gzip
statusCode | reasonPhrase |
---|---|
204 | 返回內容為空httpResponse.getEntity() == null
|
302 | 重定向 |
304 | 未修改,沒有消息體;header信息可能不完成,需要和緩存合并 |
404 | Not Found |
409 | 資源沖突 |
長短鏈接
短連接
HTTP的主要缺點是,每個TCP連接只能發送一個請求,發送完畢后就關閉;keep-alive
確保服務器將保持這個tcp連接一段時間直到會話保持的時間超過keepaliveTime
時,client和server端將主動釋放tcp連接
- 降低三次握手時延
- 減少打開鏈接的數量
- 避免慢啟動
HTTP/1.0
需要主動聲明Connection: keep-alive
,HTTP1.1
默認都是- 如果服務器提供的是一個接口服務(除了動態內容,幾乎沒有引用任何靜態內容)不建議開啟;如果服務器提供的是Web站點服務(一個頁面除了動態內容,還包含非常多的JS、圖片、css文件等)建議開啟(打開1個網頁會建立10~50個TCP連接)
長連接(推送)
客戶端和服務端之間始終建立著一個通信連接,在連接沒有中斷之前,可以隨時進行通信
- 斷開重連、心跳檢測
- 可以通過HTTP輪詢的方式實現偽長連接。(頻繁握手、協議冗余)
緩存
參見 HttpHeaderParser.parseCacheHeaders
Cache.Entry | Req Header | 說明 |
---|---|---|
ttl | Cache-Control | no-cache/no-store、max-age(stale-while-revalidate/must-revalidate/proxy-revalidate) |
serverDate | Date | 服務器時間 |
ttl | Expires | 在該時間后被認為失效,可以和Date配合計算出存活時間 |
lastModified | Last-Modified | 文件最后修改時間 |
etag | ETag | 根據特殊算法計算的一串字符,通過對比字符判斷是否修改過 |
-
Cache-Control
設置的內容優先級較高會覆蓋其他設置
文件上傳
- application/x-www-form-urlencoded 在發送前默認編碼所有字符
- multipart/form-data 不對字符編碼,瀏覽器對上傳實體內容中的每個字段用分割線進行分割,兩個分割線間的內容成為一個分區,每個分區包含兩個部分,一部分是對表單字段元素進行描述,另外一部分是表單字段元素的主體內容
<form method="post" enctype="multipart/form-data">
<input type="file"/>
</from>
Content-Type:multipart/form-data; boundary=--------------------------7dc2af520870
--------------------------7dc2af520870
- 服務端要獲得上傳的文件,需要用request.getHeader("Content-Type")來取得實體內容的分界字符串,通過request.getInputStream()得到上傳的整個post實體流,然后根據HTTP協議,把文件部分給篩選出來,保存在服務端磁盤中;其他表單值不能直接使用request.getParamter()獲得
HTTPS
OpenSSL
是一個強大的安全套接字層密碼庫,囊括主要的密碼算法、常用的密鑰和證書封裝管理功能及SSL協議
客戶端 | 服務端 |
---|---|
發送SSL/TLS信息、算法信息 + 隨機數RNC(Random Number Client) | - |
- | 回復SSL/TLS信息、算法信息、證書 + 隨機數RNS(Random Number Server) |
驗證證書;產生新的隨機數PMS(Pre-Master Secret),使用【三個隨機數】構建主密鑰MS,并用證書里的公鑰向加密PMS發送給服務端 | - |
- | 通過私鑰獲得PMS,同樣使用【三個隨機數】構建主密鑰MS |
至此握手階段結束,接下來使用普通的HTTP協議,只不過使用會話密鑰
加密內容
HTTP2.0 & SPDY
頭信息壓縮機制
:Header壓縮后發送,同時客戶端和服務端同時維護一張頭信息,所有字段都生成一個索引號,以后就只發送索引號多路復用/管道化鏈接
:針對HTTP高延遲的問題,通過多個請求stream共享一個tcp連接的方式,降低延遲同時提高帶寬的利用率請求優先級
:允許給每個request設置優先級,重要的請求會優先得到響應服務端推送
基于HTTPS的加密協議傳輸
API
HttpUrlConnection
HttpURLConnection conn = null;
try {
conn = (HttpURLConnection) new URL(encodeURL).openConnection();
conn.setConnectTimeout(timeoutMs);
conn.setReadTimeout(timeoutMs);
conn.setUseCaches(false);
conn.setDoOutput(true); // conn.setRequestMethod("POST");
conn.setRequestProperty("If-Modified-Since", "");
int nRC = conn.getResponseCode(); // 觸發getInputStream(connect()); connected = true;
if (nRC == HttpURLConnection.HTTP_OK) {
conn.getInputStream();
} else if (nRC == 304) {
}
} finally {
conn.disconnect(); // input/outputStream closed
}
AsyncHttpClient
Volley
OKHttp
OkHttpClient httpClient = new OkHttpClient();
Request request = new Request.Builder()
.url("http://www.baidu.com/")
.build();
Call call = httpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Request request, IOException e) {
mTvConten.setText(request.toString());
}
@Override
public void onResponse(Response response) throws IOException {
String htmlStr = response.body().string();
mTvConten.setText(htmlStr);
}
});
Retrofit
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.client(client) // 自定義client
.build();
// 定義連網接口
GitHubService service = retrofit.create(GitHubService.class);
// Call 模式
Call<User> repos = service.getUserByName("CatDog118");
// Rxjava 模式
Observable<User> getUserByName(@Path("user") String user);
// execute同步請求; enqueue異步請求
repos.enqueue(new Callback<User>() {
public void onResponse(Call<User> call, Response<User> response) {}
public void onFailure(Call<User> call, Throwable t) {}
});
// 取消請求
call.cancel();