常見(jiàn)問(wèn)題:
1)描述一次網(wǎng)絡(luò)請(qǐng)求的流程
2)HttpUrlConnection 和 okHttp 關(guān)系 (4.4 以后 HttpUrlConnection內(nèi)部采用 okHttp)
3)Android代碼中實(shí)現(xiàn)WAP方式聯(lián)網(wǎng)
https://blog.csdn.net/asce1885/article/details/7844159
4)服務(wù)器只提供數(shù)據(jù)接收接口,在多線(xiàn)程或多進(jìn)程條件下,如何保證數(shù)據(jù)的有序到達(dá)?
5)網(wǎng)絡(luò)框架對(duì)比和源碼分析。
6)Https請(qǐng)求慢的解決辦法
現(xiàn)在下載速度很慢,試從網(wǎng)絡(luò)協(xié)議的角度分析原因,并優(yōu)化(提示:網(wǎng)絡(luò)的5層都可以涉及)。
7)自己去設(shè)計(jì)網(wǎng)絡(luò)請(qǐng)求框架,怎么做?
8)okHttp源碼。網(wǎng)絡(luò)請(qǐng)求緩存處理,okHttp如何處理網(wǎng)絡(luò)緩存的。CacheInterceptor
一. UDP和TCP簡(jiǎn)介
1.1 UDP的特點(diǎn)
- 面向非連接
- 不維護(hù)連接狀態(tài),支持同時(shí)向多個(gè)客戶(hù)端傳輸相同的消息
- 數(shù)據(jù)包報(bào)頭只有8個(gè)字節(jié),額外開(kāi)銷(xiāo)較小
- 吞吐量只受限于數(shù)據(jù)生成速率、傳輸速率以及機(jī)器性能
- 盡最大努力交付,不保證可靠交付,不需要維持復(fù)雜的鏈接狀態(tài)表
- 面向報(bào)文,不對(duì)應(yīng)用程序提交的報(bào)文信息進(jìn)行拆分或者合并
1.2 TCP和UDP的區(qū)別
- 面向連接 vs 無(wú)連接
- 可靠性
- 有序性
- 速度
- 量級(jí)
1.3 TCP的滑動(dòng)窗口
調(diào)整讀寫(xiě)緩沖區(qū)的大小。
過(guò)程動(dòng)畫(huà)展示:
https://v.youku.com/v_show/id_XNDg1NDUyMDUy.html
TCP使用滑動(dòng)窗口做流量控制與亂序重排
- 保證TCP的可靠性
-
保證TCP的流量控制特性
連接兩端各有發(fā)送窗口與接收窗口。
窗口數(shù)據(jù)的計(jì)算過(guò)程
接收端大小:AdvertisedWindow = MaxRcvBuffer - (LastByteRcvd - LaskByteRead)
客戶(hù)端發(fā)送大小為:LastByteSent - LastByteAcked
有效窗口大?。篍ffectiveWindow = AdvertisedWindow - (LastByteSent - LastByteAcked)
二. TCP 三次握手與四次揮手
2.1 TCP Flags解讀
URG:緊急指針標(biāo)志
ACK:確認(rèn)序號(hào)標(biāo)志
SYN:同步序號(hào),用于建立連接過(guò)程
PSH:push標(biāo)志
RST:重置連接標(biāo)志
FIN:finish標(biāo)志,用于釋放連接
2.2 三次握手
第一次,client 端發(fā)送SYN包到服務(wù)器并進(jìn)入SYN_SEND狀態(tài),等待服務(wù)器確認(rèn)。SYN = 1, seq = x。
第二次是服務(wù)器收到SYN包,必須確認(rèn)客戶(hù)端的SYN(ack = x+1),同時(shí)自己也發(fā)送一個(gè)SYN包,即SYN+ACK包,服務(wù)器進(jìn)入SYN_RECV狀態(tài)。server端, SYN =1, ACK=1, seq=y, ack =x + 1
第三次是client端, 客戶(hù)端收到ACK+SYN包,想服務(wù)器發(fā)送確認(rèn)包ACK。ACK=1, seq=x+1, ack=y+1。 客戶(hù)端和服務(wù)器端進(jìn)入ESTABLISHED 狀態(tài),完成三次握手。
為什么需要三次握手才能建立起連接呢?
答:為了初始化 Sequence Number 的初始值。
首次握手的隱患——SYN超時(shí)
- Server收到Client的SYN,回復(fù)SYN-ACK的時(shí)候未收到ACK確認(rèn)
- Server不斷重試直至超時(shí),Linux默認(rèn)等待63秒才斷開(kāi)連接
針對(duì)SYN Flood的防護(hù)措施 - SYN隊(duì)列滿(mǎn)后,通過(guò)tcp_syncookies參數(shù)回發(fā)SYN Cookie
- 若為正常連接則Client會(huì)回發(fā)SYN Cookie,直接建立連接
建立連接后,Client出現(xiàn)故障怎么辦
保活機(jī)制
- 向?qū)Ψ桨l(fā)送?;钐綔y(cè)報(bào)文,如果未收到響應(yīng)則繼續(xù)發(fā)送
- 嘗試次數(shù)達(dá)到保活探測(cè)數(shù)仍未收到響應(yīng)則中斷連接
2.3 TCP的四次揮手
TCP連接必須經(jīng)過(guò)事件 2MSL 后才真正釋放掉。
“揮手”是為了終止連接。
第一次揮手:Client發(fā)送一個(gè)FIN,用來(lái)關(guān)閉Client 到Server的數(shù)據(jù)傳送,Client進(jìn)入 FIN_WAIT_1狀態(tài)。
第二次揮手:Server收到FIN后,發(fā)送一個(gè)ACK給Client,確認(rèn)序號(hào)為收到序號(hào)+1(與SYN相同,一個(gè)FIN占用一個(gè)序號(hào)),Server進(jìn)入 CLOSE_WAIT狀態(tài);Client進(jìn)入 FIN_WAIT_2。
第三次揮手:Server發(fā)送一個(gè)FIN,用來(lái)關(guān)閉Server到Client的數(shù)據(jù)傳送,Server進(jìn)入 LAST_ACK狀態(tài);
第四次揮手:Client 收到FIN后,Client 進(jìn)入 TIME_WAIT 狀態(tài),接著發(fā)送一個(gè) ACK 給 Server,確認(rèn)序號(hào)為收到序號(hào)+1,Server進(jìn)入CLOSED狀態(tài),完成四次揮手。
為什么會(huì)有TIME_WAIT狀態(tài)?
- 確保有足夠的事件讓對(duì)方收到ACK包
- 避免新舊連接混淆
為什么需要四次握手才能斷開(kāi)連接?
因?yàn)槿p工,發(fā)送方和接收方都需要FIN報(bào)文和ACK報(bào)文。
服務(wù)器出現(xiàn)大量CLOSE_WAIT狀態(tài)的原因
對(duì)方關(guān)閉socket連接,我方忙于讀或?qū)懀瑳](méi)有及時(shí)關(guān)閉連接
- 檢查代碼,特別是釋放資源的代碼
- 檢查配置,特別是處理請(qǐng)求的線(xiàn)程配置
三. 一次網(wǎng)絡(luò)請(qǐng)求的過(guò)程
我們來(lái)表述一個(gè)瀏覽器發(fā)出HTTP請(qǐng)求的過(guò)程:
首先,我們?cè)跒g覽器輸入了URL(例 :www.baidu.com),按下回車(chē),開(kāi)始我們的HTTP請(qǐng)求
3.1 通過(guò)URL找IP
首先我們的瀏覽器是不認(rèn)識(shí)baidu.com這個(gè)域名的,(注意:是baidu.com,不是www.baidu.com。 因?yàn)閣ww是服務(wù)器的名字,而baidu.com是域名,相當(dāng)于這個(gè)服務(wù)器的地址,com是公司的意義,baidu是公司名,www是公司的一個(gè)服務(wù)器名稱(chēng)),要將這個(gè)服務(wù)器的IP地址找到。
如何去找IP地址呢,首先先是本地的緩存,一般是以Hosts文件的形式存在,維持著一個(gè)帶域名的服務(wù)器地址對(duì)IP的對(duì)應(yīng)關(guān)系,路由器緩存(也算是DNS服務(wù)器緩存)
如果沒(méi)有結(jié)果,則會(huì)向上層DNS服務(wù)器詢(xún)問(wèn),上層DNS服務(wù)器的本地緩存中如果沒(méi)有該記錄,則再向上層詢(xún)問(wèn),一直到DNS根服務(wù)器。
在根域名服務(wù)器中雖然沒(méi)有每個(gè)域名的具體信息,但儲(chǔ)存了負(fù)責(zé)每個(gè)域(如COM、NET、ORG等)的解析的域名服務(wù)器的地址信息。根域名服務(wù)器會(huì)將其管轄范圍內(nèi)頂級(jí)域名(如.com)服務(wù)器IP告訴本地DNS服務(wù)器,這樣你的域名查詢(xún)請(qǐng)求會(huì)進(jìn)入到相應(yīng)的頂級(jí)域名服務(wù)器。頂級(jí)域名服務(wù)器收到請(qǐng)求后查看區(qū)域文件記錄,若找到則將其管轄范圍內(nèi)主域名(不帶任何前綴的域名,如 baidu.com)服務(wù)器的IP地址告訴本地DNS服務(wù)器。如果還是沒(méi)有找到,則進(jìn)入到下一級(jí)域名服務(wù)器進(jìn)行查找。如此重復(fù),直到找到正確的結(jié)果為止,返回 IP地址結(jié)果給本地DNS服務(wù)器。
本地DNS服務(wù)器緩存結(jié)果,設(shè)置(Time-To-Live)即一條域名解析記錄在DNS服務(wù)器上緩存時(shí)間,關(guān)于TTL如果IP經(jīng)常改變,那么TTL設(shè)的短一點(diǎn)長(zhǎng)一點(diǎn)都沒(méi)有太大的 影響,而如果IP經(jīng)常不變,可以把TTL時(shí)間拉長(zhǎng),這樣有利于提高命中率。
3.2 對(duì)IP結(jié)果建立TCP連接
自己主機(jī)IP端口的對(duì)目標(biāo)IP的端口(例:http://www.baidu.com http協(xié)議所占用的TCP端口為80端口)三次握手建立TCP連接。
3.3 向服務(wù)器發(fā)送數(shù)據(jù)
瀏覽器將網(wǎng)絡(luò)請(qǐng)求封裝成HTTP報(bào)文,把HTTP報(bào)文通過(guò)TCP的分包,分成一個(gè)個(gè)TCP數(shù)據(jù)包。
IP層把上層傳輸層數(shù)據(jù)包打包成IP層數(shù)據(jù)包,并把該數(shù)據(jù)包發(fā)送到更低層數(shù)據(jù)鏈路層,相反,IP層也把從低層接收來(lái)的數(shù)據(jù)包傳送到更高層TCP或UDP層。(補(bǔ):IP數(shù)據(jù)包是不可靠的,因?yàn)镮P并沒(méi)有做任何事情來(lái)確認(rèn)數(shù)據(jù)包是否按順序發(fā)送的或者有沒(méi)有被破壞,IP數(shù)據(jù)包中含有發(fā)送它的主機(jī)的地址(源地址)和接收它的主機(jī)的地址(目的地址))
通過(guò)這套封裝包過(guò)程,發(fā)送到服務(wù)器端,服務(wù)器端則是一個(gè)拆包的過(guò)程,IP層是不可靠的,所以沒(méi)有確認(rèn)的機(jī)制,而在上層的TCP層則會(huì)對(duì)數(shù)據(jù)包的可靠性進(jìn)行驗(yàn)證,丟失則會(huì)重傳數(shù)據(jù)。保證傳輸?shù)目煽啃?。服?wù)器最終解包會(huì)拼接成一個(gè)完整的HTTP報(bào)文,完成整個(gè)數(shù)據(jù)的發(fā)送。
3.4 服務(wù)器解析,并返回
對(duì)HTTP報(bào)文進(jìn)行解析,根據(jù)HTTP報(bào)文決定它請(qǐng)求了什么。將處理的結(jié)果組裝成響應(yīng)報(bào)文(如www.baidu.com , 請(qǐng)求報(bào)文為GET,要獲取的是缺省值默認(rèn)的index.html這個(gè)主頁(yè),則返回網(wǎng)頁(yè)的源碼,將網(wǎng)頁(yè)源碼添加到響應(yīng)報(bào)文正文中),其中比較關(guān)鍵的是狀態(tài)碼(200OK表示成功沒(méi)毛?。?,然后將響應(yīng)報(bào)文,通過(guò)之前的過(guò)程返還給咱們的主機(jī)IP。
3.5 總結(jié)過(guò)程
- DNS解析
- TCP連接
- 發(fā)送HTTP請(qǐng)求
- 服務(wù)器處理請(qǐng)求并返回HTTP報(bào)文
- 瀏覽器解析渲染頁(yè)面
- 連接結(jié)束
四. HTTP
4.1 簡(jiǎn)介
- 支持客戶(hù)/服務(wù)器模式
- 簡(jiǎn)單快速
- 靈活
- 無(wú)連接
- 無(wú)狀態(tài)
4.2 請(qǐng)求和響應(yīng)報(bào)文


4.3 請(qǐng)求/響應(yīng)的步驟
- 客戶(hù)端連接到Web服務(wù)器
- 發(fā)送HTTP請(qǐng)求
- 服務(wù)器端接收請(qǐng)求并返回HTTP響應(yīng)
- 釋放TCP連接
- 客戶(hù)端瀏覽器解析HTML內(nèi)容
4.4 HTTP狀態(tài)碼
- 1xx:指示消息——表示請(qǐng)求已接收,繼續(xù)處理
- 2xx:成功——表示請(qǐng)求已被成功接收、理解、接收
- 3xx:重定向——要完成請(qǐng)求必須進(jìn)行更進(jìn)一步的操作
- 4xx:客戶(hù)端錯(cuò)誤——請(qǐng)求有語(yǔ)法錯(cuò)誤或請(qǐng)求無(wú)法實(shí)現(xiàn)
- 5xx:服務(wù)器端錯(cuò)誤——服務(wù)器未能實(shí)現(xiàn)合法的請(qǐng)求
4.5 GET和POST區(qū)別
- HTTP報(bào)文層面:GET將請(qǐng)求信息放在URL,POST放在報(bào)文體中
- 數(shù)據(jù)庫(kù)層面:GET符合冪等性和安全性,POST不符合
- 其他層面:GET可以被緩存、被存儲(chǔ),而POST不行
4.6 Cookie和Session的區(qū)別
Cookie的設(shè)置以及發(fā)送過(guò)程
Web Client 1)HTTP Request 向 Web Server
2)HTTP Response + Set-Cookie 回
3)HTTP Request + Cookie 向
4)HTTP Response 回
Session簡(jiǎn)介
- 服務(wù)器端的機(jī)制,在服務(wù)器上保存的信息
- 解析客戶(hù)端請(qǐng)求并操作session id,按需保存狀態(tài)信息
可是使用Cookie實(shí)現(xiàn)。
Cookie和Session 的區(qū)別
- Cookie數(shù)據(jù)存放在客戶(hù)的瀏覽器上,Session數(shù)據(jù)放在服務(wù)器上
- Session相對(duì)于Cookie更安全
- 若考慮減輕服務(wù)器負(fù)擔(dān),應(yīng)當(dāng)使用Cookie
五. Android常見(jiàn)網(wǎng)絡(luò)框架對(duì)比

六. Https請(qǐng)求慢的解決辦法
DNS,攜帶數(shù)據(jù),直接訪問(wèn)IP
七. HTTP和HTTPS
HTTP、TCP、IP
HTTP、SSL/TLS、TCP、IP 后者比前者多了一層即 SSL/TLS
加密方式
- 對(duì)稱(chēng)加密:加密和解密用的是同一個(gè)密鑰
- 非對(duì)稱(chēng)加密:加密使用的密鑰和解密使用的密鑰是不相同的
- 哈希算法:將任意長(zhǎng)度的信息轉(zhuǎn)換為固定長(zhǎng)度的值,算法不可逆
- 數(shù)字簽名:證明某個(gè)消息或者文件是某人發(fā)出/認(rèn)同的
區(qū)別
- HTTPS需要到CA申請(qǐng)證書(shū),HTTP不需要
- HTTPS密文傳輸,HTTP明文傳輸
- 連接方式不同,HTTPS默認(rèn)使用443端口,HTTP使用80端口
- HTTPS=HTTP+加密+認(rèn)證+完整性保護(hù),較HTTP安全
SSL(Security Sockets Layer,安全套接層)
- 為網(wǎng)絡(luò)通信提供安全及數(shù)據(jù)完整性的一種安全協(xié)議
- 是操作系統(tǒng)對(duì)外的API,SSL3.0后更名為T(mén)LS
- 采用身份認(rèn)證和數(shù)據(jù)加密保證網(wǎng)絡(luò)通信的安全和數(shù)據(jù)的完整性
HTTPS數(shù)據(jù)傳輸流程
- 瀏覽器將支持的加密算法信息發(fā)送給服務(wù)器
- 服務(wù)器選擇一套瀏覽器支持的加密算法,以證書(shū)的形式回發(fā)瀏覽器
- 瀏覽器驗(yàn)證證書(shū)的合法性,并結(jié)合證書(shū)公鑰加密信息發(fā)送給服務(wù)器
- 服務(wù)器使用私鑰解密信息,驗(yàn)證哈希,加密響應(yīng)信息回發(fā)瀏覽器
- 瀏覽器解密響應(yīng)信息,并對(duì)消息進(jìn)行驗(yàn)證,之后進(jìn)行加密交互數(shù)據(jù)
HTTPS夠安全嗎?
瀏覽器默認(rèn)填充htpp://,請(qǐng)求需要進(jìn)行跳轉(zhuǎn),有被劫持的風(fēng)險(xiǎn)
可以使用HSTS(HTTP Strict Transport Security)優(yōu)化
八. Socket
Socket是對(duì)TCP/IP 協(xié)議的抽象,是操作系統(tǒng)對(duì)外開(kāi)放的接口

8.1 Socket通信流程

8.2 UDP Demo
UDP Server
public class UDPServer {
public static void main(String[] args) throws Exception {
// 服務(wù)端接受客戶(hù)端發(fā)送的數(shù)據(jù)報(bào)
DatagramSocket socket = new DatagramSocket(65001); // 監(jiān)聽(tīng)的端口號(hào)
byte[] buff = new byte[100];// 存儲(chǔ)從客戶(hù)端接收到的內(nèi)容
DatagramPacket packet = new DatagramPacket(buff, buff.length);
//接收客戶(hù)端發(fā)送來(lái)的內(nèi)容,并將內(nèi)容封裝進(jìn)DatagramPacket對(duì)象中
socket.receive(packet);
byte[] data = packet.getData(); // 從DatagramPacket對(duì)象中獲取到真正存儲(chǔ)的數(shù)據(jù)
// 將數(shù)據(jù)從二進(jìn)制轉(zhuǎn)換成字符串形式
String content = new String(data, 0, packet.getLength());
System.out.println(content);
// 將要發(fā)送給庫(kù)護(hù)短的數(shù)據(jù)轉(zhuǎn)換成二進(jìn)制
byte[] sendedContent = String.valueOf(content.length()).getBytes();
// 服務(wù)端給客戶(hù)端發(fā)送數(shù)據(jù)報(bào)
// 從DatagramPacket兌現(xiàn)中獲取到數(shù)據(jù)的來(lái)源地址與端口號(hào)
DatagramPacket packetToClient = new DatagramPacket(sendedContent, sendedContent.length, packet.getAddress(), packet.getPort());
socket.send(packetToClient); // 發(fā)送數(shù)據(jù)給客戶(hù)端
}
}
UDPClient
public class UDPClient {
public static void main(String[] args) throws Exception {
// 客戶(hù)端發(fā)數(shù)據(jù)報(bào)給服務(wù)端
DatagramSocket socket = new DatagramSocket();
// 要發(fā)送給服務(wù)端的數(shù)據(jù)
byte[] buf = "Hellow world".getBytes();
// 將IP地址封裝成InetAddress對(duì)象
InetAddress address = InetAddress.getByName("127.0.0.1");
// 將要發(fā)送給服務(wù)端的數(shù)據(jù)封裝成DatagramPacket對(duì)象 需要天蝎上ip地址與端口號(hào)
DatagramPacket packet = new DatagramPacket(buf, buf.length, address, 65001);
// 發(fā)送數(shù)據(jù)給服務(wù)端
socket.send(packet);
// 客戶(hù)端接收服務(wù)端發(fā)送過(guò)來(lái)的數(shù)據(jù)
byte[] data = new byte[100];
// 創(chuàng)建DatagramPacket對(duì)象用來(lái)存儲(chǔ)服務(wù)端發(fā)送過(guò)來(lái)的數(shù)據(jù)
DatagramPacket receivedPacket = new DatagramPacket(data, data.length);
// 將接收到的數(shù)據(jù)存儲(chǔ)到DatagramPacket對(duì)象中
socket.receive(receivedPacket);
// 將服務(wù)器發(fā)送過(guò)來(lái)的數(shù)據(jù)取出來(lái)并打印到控制臺(tái)
String content = new String(receivedPacket.getData(), 0, receivedPacket.getLength());
System.out.println(content);
}
}
8.3 TCP Demo
TCP server
public class TCPServer {
public static void main(String[] args) throws Exception {
// 創(chuàng)建Socket,并將socket綁定到65000端口
ServerSocket ss = new ServerSocket(65000);
// 死循環(huán),使得socket一直等待并處理客戶(hù)端發(fā)送過(guò)來(lái)的請(qǐng)求
while (true) {
// 監(jiān)聽(tīng)65000端口,直到客戶(hù)端返回連接信息后才返回
Socket socket = ss.accept();
// 獲取客戶(hù)端的請(qǐng)求信息后,執(zhí)行相關(guān)業(yè)務(wù)邏輯
new LengthCalculator(socket).start();
}
}
}
public class LengthCalculator extends Thread {
private Socket socket;
public LengthCalculator(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
// 獲取socket的輸出流
OutputStream os = socket.getOutputStream();
// 獲取socket的輸入流
InputStream is = socket.getInputStream();
int ch = 0;
byte[] buff = new byte[1024];
// buff主要用來(lái)讀取輸入的內(nèi)容,存成byte數(shù)組,ch主要用來(lái)獲取讀取數(shù)組的額大小
ch = is.read(buff);
// 將接收流的byte數(shù)組轉(zhuǎn)換成字符串,這里獲取的內(nèi)容是客戶(hù)端發(fā)送過(guò)來(lái)的字符
String content = new String(buff, 0, ch);
System.out.println(content);
// 往輸出流里寫(xiě)入獲得的字符串的長(zhǎng)度,回發(fā)給客戶(hù)端
os.write(String.valueOf(content.length()).getBytes());
// 不要忘記關(guān)閉輸入輸出流以及socket
is.close();
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
TCP client
public class TCPClient {
public static void main(String[] args) throws Exception {
// 創(chuàng)建Socket,并指定連接的是本機(jī)的端口號(hào)65000的服務(wù)端socket
Socket socket = new Socket("127.0.0.1", 65000);
// 獲取輸出流
OutputStream os = socket.getOutputStream();
// 獲取輸入流
InputStream is = socket.getInputStream();
// 將要傳遞給server的字符串參數(shù)轉(zhuǎn)換成byte數(shù)組,并數(shù)組寫(xiě)入到輸出流中
os.write(new String("helloworld").getBytes());
int ch = 0;
byte[] buff = new byte[1024];
// buff主要用來(lái)讀取輸入的內(nèi)容,存成byte數(shù)組,ch主要用來(lái)獲取讀取數(shù)組的長(zhǎng)度
ch = is.read(buff);
// 將接收流的byte數(shù)組轉(zhuǎn)換成字符串,這里是從服務(wù)端回發(fā)回來(lái)的字符串參數(shù)的長(zhǎng)度
String content = new String(buff, 0, ch);
System.out.println(content);
// 不要忘記關(guān)閉輸入輸出流以及socket
is.close();
os.close();
socket.close();
}
}
