webSocket TCP/IP

我們用websocket和http來研究一下TCP/IP協(xié)議的一些特性何鸡,在上一篇文章《https連接的前幾毫秒發(fā)生了什么》里我們已經(jīng)研究了https建立的過程榆俺。

上一篇是用的wireshark的抓包工具,這一篇將用tcpdump命令行工具呢蛤。

1. tcpdump

Linux系的系統(tǒng)有一個(gè)很好用的抓包工具,叫tcpdump,可以用來抓取網(wǎng)絡(luò)上的tcp包所坯,例如我要抓取8080端口的包,可以執(zhí)行以下命令:

sudo tcpdump port 8080 –n

-n的意思是端口號(hào)用數(shù)字表示挂捅,還可以加上-v -vv顯示更詳細(xì)的信息:

sudo tcpdump port 8080 –n -v

再如我要抓取來自特定源IP和發(fā)往特定目的IP的包芹助,可以用以下命令:

sudo tcpdump src host 10.2.200.11 or dst host 10.2.200.11

指定src host和dst host,并用or/and做條件的交集和并集闲先。

在建立一個(gè)網(wǎng)頁(yè)的websocket之前先要建立一個(gè)http連接状土,為此我們簡(jiǎn)單寫一個(gè)小demo。

2. hello, world的http連接

(1)首先寫以下的html文件:

(2)然后再裝一個(gè)http-server的node包伺糠,監(jiān)聽在8080端口蒙谓,如下所示:

(3)電腦開tcpdump命令,抓取通過8080端口通訊的包:

sudo tcpdump port 8080 –n

(4)用手機(jī)訪問:http://10.2.200.140.8080训桶,tcpdump就會(huì)打出所有傳輸?shù)膖cp包累驮,如下圖所示。

我們拿它打印的這些TCP報(bào)文做一個(gè)研究舵揭。在建立一個(gè)http連接之前谤专,先要建立一個(gè)TCP連接,即上圖的頭3個(gè)報(bào)文午绳。下面研究一下這個(gè)HTTP連接是怎么進(jìn)行的置侍。

3. 一個(gè)完整的HTTP連接

(1)TCP三次握手

第一個(gè)報(bào)文:11 -> 140

10:11:06.151830 IP 10.2.200.11.63826 > 10.2.200.140.8080: Flags [S], seq2153742604, win 65535, options [mss 1460,nop,wscale 5,nop,nop,TS val298297187ecr 0,sackOK,eol], length 0

在10點(diǎn)11分的時(shí)候,IP為10.2.200.11(以后簡(jiǎn)稱11)的63826端口向IP為10.2.200.140(以后簡(jiǎn)稱140)的8080端口發(fā)了一個(gè)TCP的包,帶上了標(biāo)志位SYN墅垮,表示要建立一個(gè)連接惕医,并指明包開始的序列號(hào)seq(單位為字節(jié)),以后傳送的字節(jié)編號(hào)都是以這個(gè)做為起點(diǎn)算色,并告知能接收的最大報(bào)文段長(zhǎng)度mss為1460抬伺,一般mss都為1460.

第二個(gè)報(bào)文:140 -> 11

10:11:06.151917 IP 10.2.200.140.8080 > 10.2.200.11.63826: Flags [S.], seq1007874094, ack2153742605, win 65535, options [mss 1460,nop,wscale 5,nop,nop,TS val493556398ecr298297187,sackOK,eol], length 0

在過了87微秒之后,140進(jìn)行了回復(fù)灾梦,發(fā)送了一個(gè)SYN + ACK的報(bào)文段峡钓,表示同意和11建立連接。

第三個(gè)報(bào)文:11 -> 140

10:11:06.190376 IP 10.2.200.11.63826 > 10.2.200.140.8080: Flags [.], ack 1, win 4117, options [nop,nop,TS val298297310ecr493556398], length 0

11收到SYN之后向140發(fā)送一個(gè)ACK若河,同時(shí)改變接收窗口為4117 * 2 ^ 5 = 131kb能岩,完成三次握手。

什么是接收窗口呢萧福?

(2)接收窗口

第四個(gè)報(bào)文里面拉鹃,140也向11修改了它的接收窗口大小:

10:11:06.190422 IP 10.2.200.140.8080 > 10.2.200.11.63826: Flags [.], ack 1, win 4117, options [nop,nop,TS val493556436ecr298297310], length 0

大小為4117 * 2 ^ 5 = 131kb鲫忍,為什么接收窗口是這個(gè)數(shù)呢膏燕?因?yàn)槿缦耇CP的報(bào)文(頭):

窗口大小只有2個(gè)字節(jié)16位,最大只能表示2 ^ 16 - 1 = 65535即16Kb悟民,當(dāng)初設(shè)計(jì)TCP的人并沒有想到現(xiàn)在的網(wǎng)速會(huì)提升這么快坝辫,16Kb是不夠用的,所以在可選項(xiàng)里面加了一個(gè)wscale(window scale factor)的指數(shù)字段射亏,最大值為14近忙,所以最大的接收窗口大概為1GB.

說了這么多,接收窗口是用來做什么的呢智润?它根據(jù)自身網(wǎng)絡(luò)情況設(shè)置不同大小的值用來控制對(duì)方發(fā)送速度及舍,避免對(duì)方發(fā)送太快,導(dǎo)致網(wǎng)絡(luò)擁塞窟绷。下面講到擁塞控制會(huì)更進(jìn)一步地討論击纬。

(3)發(fā)送數(shù)據(jù)

建立好TCP連接后,11向140發(fā)送了一個(gè)http請(qǐng)求:

10:11:06.193435 IP 10.2.200.11.63826 > 10.2.200.140.8080: Flags [P.], seq 1:404, ack 1, win 4117, options [nop,nop,TS val298297312ecr493556398], length 403: HTTP: GET / HTTP/1.1

這里钾麸,它帶上了一個(gè)PUSH的標(biāo)志位,表示它是一個(gè)比較緊急的報(bào)文炕桨,要求對(duì)方立即把數(shù)據(jù)從緩存里面發(fā)送給應(yīng)用程序饭尝,不能再繼續(xù)緩存了。

它發(fā)送的字節(jié)號(hào)為[1, 404)献宫,這個(gè)數(shù)字是tcpdump顯示的相對(duì)于握手的協(xié)議初始序列號(hào)顯示的偏移钥平,它是一個(gè)左閉右開的表示,所以這個(gè)報(bào)文總共發(fā)送了403個(gè)字節(jié)的數(shù)據(jù)。它是一個(gè)GET請(qǐng)求涉瘾。

然后后140收到后給11回復(fù)了一個(gè)ACK:

10:11:06.193467 IP 10.2.200.140.8080 > 10.2.200.11.63826: Flags [.], ack 404, win 4105, options [nop,nop,TS val493556439ecr298297312], length 0

ACK 404表示期待收到第404字節(jié)的數(shù)據(jù)知态,也就是說前面403個(gè)字節(jié)的數(shù)據(jù)已經(jīng)都確認(rèn)收到。

然后140進(jìn)行了http響應(yīng):

10:11:06.194840 IP 10.2.200.140.8080 > 10.2.200.11.63826: Flags [P.], seq 1:290, ack 404, win 4105, options [nop,nop,TS val493556440ecr298297312], length 289: HTTP: HTTP/1.1 200 OK

10:11:06.200295 IP 10.2.200.11.63826 > 10.2.200.140.8080: Flags [.], ack 290, win 4108, options [nop,nop,TS val298297318ecr493556440], length 0

10:11:06.200315 IP 10.2.200.140.8080 > 10.2.200.11.63826: Flags [P.], seq 290:458, ack 404, win 4105, options [nop,nop,TS val493556445ecr298297318], length 168: HTTP

10:11:06.204847 IP 10.2.200.11.63826 > 10.2.200.140.8080: Flags [.], ack 458, win 4103, options [nop,nop,TS val298297321ecr493556445], length 0

140總共發(fā)送了457個(gè)字節(jié)的數(shù)據(jù)立叛,分成了兩個(gè)包發(fā)送负敏。而本地的html文件大小為:

所以可以認(rèn)為http報(bào)文頭占用了457 - 168 = 289字節(jié)。

(4)關(guān)閉連接

第一個(gè)報(bào)文:11 -> 140 FIN

10:11:36.359973 IP 10.2.200.11.63826 > 10.2.200.140.8080: Flags [F.], seq 404, ack 458, win 4103, options [nop,nop,TS val298327416ecr493556445], length 0

11等了30s后覺得不用再請(qǐng)求數(shù)據(jù)了秘蛇,于是要把連接關(guān)閉了其做,它向140發(fā)送一個(gè)FIN的報(bào)文。為什么要等30s才關(guān)閉呢赁还?這是HTTP請(qǐng)求的Connection: keep-alive字段影響的妖泄,因?yàn)橥粋€(gè)域可能要請(qǐng)求多個(gè)資源,不能一個(gè)請(qǐng)求完了就把連接關(guān)閉了艘策。如果不關(guān)閉又占用端口號(hào)資源蹈胡,我們知道端口號(hào)最多只有65535個(gè)。

第二個(gè)報(bào)文:140 -> 11 ACK

10:11:36.360021 IP 10.2.200.140.8080 > 10.2.200.11.63826: Flags [.], ack 405, win 4105, options [nop,nop,TS val493586567ecr298327416], length 0

140收到這個(gè)包后向11發(fā)送一個(gè)ACK朋蔫,這個(gè)時(shí)候連接處于半關(guān)閉狀態(tài)罚渐,即11不可再向140發(fā)送數(shù)據(jù)了,但140還可以向11發(fā)送斑举。

第三個(gè)報(bào)文:140 -> 11 FIN

10:11:36.360537 IP 10.2.200.140.8080 > 10.2.200.11.63826: Flags [F.], seq 458, ack 405, win 4105, options [nop,nop,TS val493586567ecr298327416], length 0

140也要把連接關(guān)閉了搅轿,于是它向11發(fā)送FIN

第四個(gè)報(bào)文:11 -> 140 ACK

10:11:36.368758 IP 10.2.200.11.63826 > 10.2.200.140.8080: Flags [.], ack 459, win 4103, options [nop,nop,TS val298327458ecr493586567], length 0

11收到后,向它發(fā)了一個(gè)ACK富玷,此時(shí)連接完全關(guān)閉璧坟。然后主動(dòng)關(guān)閉方11將進(jìn)入TIME_WAIT狀態(tài)

(5)MSS和TIME_WAIT

TIME_WAIT時(shí)間為2MSL,MSL的意思是maximum segment livetime赎懦,即報(bào)文段的最大生存時(shí)間雀鹃,標(biāo)準(zhǔn)建議為2分鐘,實(shí)際實(shí)現(xiàn)有的為30s励两。在TIME_WAIT狀態(tài)下黎茎,上一次建立連接的套接字(socket)將不可再重新啟用,也就是同一個(gè)網(wǎng)卡/IP不可再建立同樣端口號(hào)的連接当悔,如上面是10.2.200.11.63826傅瞻,如果再重新創(chuàng)建系統(tǒng)將會(huì)報(bào)錯(cuò)。為什么要等待這個(gè)時(shí)間呢盲憎?主要是為了避免有些報(bào)文段在網(wǎng)絡(luò)上滯留嗅骄,被對(duì)方收到的時(shí)候如果剛好又啟用了一個(gè)完全一樣的套接字,那么就會(huì)被認(rèn)為是這個(gè)連接的數(shù)據(jù)饼疙。因此為了讓所有“迷路”的報(bào)文徹底消失后溺森,才能啟用相同的套接字。但是有時(shí)候你會(huì)覺得兩分鐘不能重新啟動(dòng)相同的socket,有點(diǎn)麻煩屏积,所以要把它禁了医窿,可以在創(chuàng)建socket的時(shí)候,指定SO_REUSEADDR的選項(xiàng)炊林,這樣就不用等待TIME_WAIT的時(shí)間了姥卢。

另外還有一個(gè)時(shí)間叫RTT(round trip time),即一個(gè)報(bào)文段的往返時(shí)間铛铁,可以理解為我發(fā)一個(gè)數(shù)據(jù)給你隔显,你再回我一個(gè)ACK這個(gè)往返過程的時(shí)間,這個(gè)時(shí)間是動(dòng)態(tài)計(jì)算的饵逐,在下面講擁塞控制的時(shí)候?qū)?huì)提及這個(gè)時(shí)間括眠。

接下來討論兩個(gè)問題。

4. 為什么TCP握手要三次倍权?

為什么不是兩次掷豺、四次呢?有人說三次是建立一個(gè)可靠連接最少的次數(shù)薄声,那為什么不是兩次呢当船??jī)纱魏孟褚部梢园。拖翊螂娫挘?/p>

甲:喂默辨,你聽得到嗎德频?

乙:我聽得到

然后甲就可以開始說話了。再舉另外一個(gè)例子做說明缩幸,假設(shè)有三個(gè)山頭:A壹置、B、C表谊,A山頭想要聯(lián)合B山頭的人晚上六點(diǎn)去攻打B山頭的人钞护,因?yàn)槿绻挥幸粋€(gè)山頭的人去攻打C的話會(huì)陣亡,所以A和B需要進(jìn)行握手爆办。

于是:

A就派了只鴿子帶上SYN的消息過去找B

B收到后又派了只鴿子帶上ACK + SYN的消息回復(fù)A

A收到后又派了只鴿子帶上ACK去回復(fù)B

這個(gè)就好像我們的三次握手难咕,但是三次就夠了嗎?假設(shè)第三次A發(fā)的ACK C沒有收到距辆,這時(shí)候B就要猶豫了:會(huì)不會(huì)A不知道我同意了余佃,如果A不知道我同意那么它可能不會(huì)去攻打了,然后我去了就得被滅了跨算。由于A不知道它的回復(fù)有沒有被收到咙冗,所以它可能會(huì)想到B可能會(huì)怕它不會(huì)出擊,所以A也猶豫了漂彤。

因此三次握手并不能保證雙方完全地信任對(duì)方,即使是四次、五次也是同樣道理挫望,至少有一方無法信任另一方立润,另外一方一想到對(duì)方可能不信它,它也會(huì)變得不信對(duì)方媳板。

但是這個(gè)例子并不是說TCP連接建立是不可靠的桑腮,實(shí)際的場(chǎng)景往往是只要雙方確認(rèn)對(duì)方都在就好了,如下:

甲:你活著嗎蛉幸?我想和你通話

乙:我活著呢破讨,我們開始通話吧

因此最少的握手次數(shù)應(yīng)該是兩次,三次可以提高可靠性奕纫,四次提陶、五次就沒必要了,就會(huì)陷入上面山頭攻打無限循環(huán)確認(rèn)的漩渦匹层。如下:

甲:你活著嗎隙笆?我想和你通話

乙:我活著呢,我們開始通話吧

甲:好的

最后的“好的”可能有點(diǎn)多余升筏,但是它顯得比較有“人情味”撑柔。

難道兩個(gè)山頭通信真的沒有辦法解決嗎?有辦法您访,我們將在下面的擁塞控制提到铅忿。

5. 為什么揮手要四次

分析了握手次數(shù)的原因,很容易可以知道為什么揮手要四次了灵汪。前兩次揮手讓連接處于半關(guān)閉狀態(tài)檀训,此時(shí)主動(dòng)關(guān)閉方不可再向被動(dòng)關(guān)閉方發(fā)送數(shù)據(jù),而被動(dòng)關(guān)閉可繼續(xù)向主動(dòng)關(guān)閉方發(fā)送數(shù)據(jù)识虚。如下圖所示:

所以四次的原因是可以有一個(gè)處于半關(guān)閉的狀態(tài)肢扯。

接下來看一下四層網(wǎng)絡(luò)協(xié)議。

6. 四層網(wǎng)絡(luò)協(xié)議

如下圖所示担锤,我們從發(fā)送數(shù)據(jù)的角度看四層網(wǎng)絡(luò)協(xié)議:

假設(shè)我要用HTTP發(fā)送一個(gè)文本蔚晨,那么它會(huì)最后會(huì)被層層包裝成這樣一個(gè)報(bào)文:

在廣域網(wǎng)是用的IP地址進(jìn)行報(bào)文轉(zhuǎn)發(fā),而到了局域網(wǎng)需要靠物理地址發(fā)送給對(duì)應(yīng)的主機(jī)肛循。IP是點(diǎn)到點(diǎn)铭腕,負(fù)責(zé)發(fā)送給對(duì)應(yīng)的主機(jī),而TCP是端到端多糠,即根據(jù)端口號(hào)累舷,負(fù)責(zé)發(fā)送給對(duì)應(yīng)的應(yīng)用程序。

(1)物理地址

每個(gè)網(wǎng)卡都有全球唯一的物理地址夹孔,路由器向同一個(gè)局域網(wǎng)所有主機(jī)發(fā)送收到的數(shù)據(jù)包被盈,本機(jī)的網(wǎng)卡比較一下包里指明的物理地址和本機(jī)的物理地址是否一致析孽,如果一致則接收,否則則丟棄只怎。所以可以在局域網(wǎng)監(jiān)聽發(fā)給其它人的數(shù)據(jù)包袜瞬,當(dāng)然也有一些反監(jiān)聽的手段。

(2)網(wǎng)際層ARP

ARP是一個(gè)地址解析協(xié)議身堡,當(dāng)我訪問10.2.200.140的時(shí)候我需要知道它的物理地址是多少邓尤,因?yàn)樗呀?jīng)是一個(gè)局域網(wǎng)的IP地址了。我怎么知道它的物理地址是多少呢贴谎?我就向局域網(wǎng)的機(jī)器廣播一個(gè)ARP請(qǐng)求:

09:51:32.966852 ARP, Request who-has 10.2.200.140 tell 10.2.200.11, length 28

過了33微秒一小會(huì)的功夫就有人告訴我了:

09:51:32.966885 ARP, Reply 10.2.200.140 is-at 98:5a:eb:89:a5:7e (oui Unknown), length 28

這個(gè)很可能是路由器告訴我的汞扎,上面的tcpdump輸出沒有打印源IP。

可以通過arp -an的命令擅这,查看電腦上的arp表澈魄,如下圖所示:

(3)網(wǎng)際層traceroute有一個(gè)很好用的命令叫traceroute,它可以追蹤路由路徑蕾哟,它的原理是向目的主機(jī)發(fā)送ICMP報(bào)文一忱,發(fā)送第一個(gè)報(bào)文時(shí),設(shè)置TTL為0谭确,TTL即Time to Live帘营,是報(bào)文的生存時(shí)間,由于它是0逐哈,所以下一個(gè)路由器由到這個(gè)報(bào)文后芬迄,不會(huì)再繼續(xù)轉(zhuǎn)發(fā)了,會(huì)給源主機(jī)發(fā)送ICMP出錯(cuò)的報(bào)文昂秃,就可以知道第一個(gè)路由的IP地址禀梳,同理,設(shè)置TTL為1肠骆,就可以知道第二個(gè)路由的IP地址算途,依次類推。

如在北京traceroute廣東電信蚀腿,運(yùn)行命令:

>traceroutegd.189.cn

控制臺(tái)將不斷地打印經(jīng)過的路由嘴瓤,traceroute每次都會(huì)發(fā)三個(gè)報(bào)文:

可以看到為了到廣東電信官網(wǎng)的服務(wù)器,經(jīng)過了這么一個(gè)過程——首先發(fā)給了直接路由器進(jìn)行轉(zhuǎn)發(fā)莉钙,然后又在局域網(wǎng)的路由轉(zhuǎn)發(fā)了幾次廓脆,最后出來到了北京聯(lián)通,中間又經(jīng)過了北京電信和上海電信的路由器磁玉,最后到了廣州電信的路由器停忿。我們會(huì)發(fā)現(xiàn)每次走的路由可能會(huì)不一樣,它是活的蚊伞。這里又涉及到路由轉(zhuǎn)發(fā)席赂,本文不繼續(xù)探討吮铭。

每個(gè)報(bào)文都有一個(gè)TTL最大跳數(shù),每經(jīng)過一個(gè)路由就會(huì)把它減1氧枣,當(dāng)減到0的時(shí)候沐兵,就不再繼續(xù)轉(zhuǎn)發(fā)了。避免某些報(bào)文被無限循環(huán)轉(zhuǎn)發(fā)便监,造成網(wǎng)絡(luò)資源的浪費(fèi)烧董。TTL位于IP報(bào)文的第9個(gè)字節(jié)。

(3)網(wǎng)際層Ping

另外一個(gè)很常用的命令是Ping,如Ping一下127.0.0.1可以看一下本機(jī)的網(wǎng)絡(luò)協(xié)議是否工作正常扇商,Ping一下某個(gè)服務(wù)器,看這個(gè)服務(wù)器有沒有開控汉,Ping一下某個(gè)域名,看它的IP地址是多少街佑。Ping還可以這么用,例如Ping一下baidu:

可以看到要到百度服務(wù)器中間經(jīng)過了64 – 49 = 15跳,所以可推測(cè)百度用的是Linux服務(wù)器璃搜,為什么呢这吻,因?yàn)長(zhǎng)inux默認(rèn)的最大TTL = 64鬼贱,而49和64最為接近舟误。

Ping一下美國(guó)亞馬遜:

到美國(guó)亞馬遜,經(jīng)過了255 – 217 = 38跳,所以推測(cè)亞馬遜用的Unix服務(wù)器疚顷,Unix服務(wù)器默認(rèn)的最大TTL為255.

再Ping一下中國(guó)版的w3school:

到中國(guó)版的w3school用了128 – 107 = 21跳,Windows的默認(rèn)最大跳數(shù)為128,所以w3school用的是windows操作系統(tǒng) 酗洒,因?yàn)樗玫氖茿SP,所以它必定是windows系統(tǒng)。

繼續(xù)回到demo實(shí)驗(yàn)的討論雹锣,下面分析一些異常的情況。

7. Reset報(bào)文

假設(shè)現(xiàn)在我把8080端口的http-server給殺了攒射,然后再訪問寇窑,會(huì)怎么樣呢?會(huì)抓取到以下報(bào)文:

11:38:09.120488 IP 10.2.200.11.57049 > 10.2.200.140.8080: Flags [S], seq1663158265, win 65535, options [mss 1460,nop,wscale 5,nop,nop,TS val822068310ecr 0,sackOK,eol], length 0

11:38:09.120524 IP 10.2.200.140.8080 > 10.2.200.11.57049: Flags [R.], seq 0, ack1663158266, win 0, length 0

第一個(gè)報(bào)文還是SYN的報(bào)文,但是第二個(gè)報(bào)文服務(wù)器直接返回了RST,告訴對(duì)方不可建立連接。服務(wù)返回異常RST報(bào)文可能有以原因:

服務(wù)器沒開服務(wù)

請(qǐng)求超時(shí)

服務(wù)程序突然掛了

在一個(gè)已關(guān)閉的socket上收到數(shù)據(jù)

8. 擁塞控制

現(xiàn)在我要上傳一個(gè)文件,觀察報(bào)文發(fā)送的情況,如下圖所示:

上面0.70s的時(shí)間內(nèi),發(fā)送了1448 * 9 = 17k的數(shù)據(jù)(Mss 1460)

這個(gè)時(shí)候突然網(wǎng)卡了,又會(huì)怎么樣呢碴里?如下圖所示:

上面1.45s的時(shí)間內(nèi),總共發(fā)送了9個(gè)包,5kb數(shù)據(jù)。

正常情況經(jīng)常一次連續(xù)發(fā)送1448 * 6 = 8k數(shù)據(jù),網(wǎng)卡即帶寬下降的時(shí)候是如何控制發(fā)送速度的呢?先來看一下什么是接收窗口和擁塞窗口。

(1)接收窗口和擁塞窗口

在上傳的過程中,服務(wù)器可能會(huì)不斷地調(diào)整它的接收窗口大小:

14:03:38.479417 IP ec2-54-153-103-33.us-west-1.compute.amazonaws.com.https > 10.2.200.140.56342: Flags [.], ack 59651, win 850, options [nop,nop,TS val954992962ecr598512063,nop,nop,sack 1 ], length 0

如收到上面的ACK報(bào)文后,服務(wù)器的接收窗口rwnd為:

rwnd = 850 * 2 ^ 5 = 27200 B

我本機(jī)自己有一個(gè)擁塞窗口cwnd掌栅,這個(gè)窗口用來控制我的發(fā)送速度猾封,避免網(wǎng)絡(luò)擁塞,這個(gè)擁塞窗口是動(dòng)態(tài)變化的,下面會(huì)提到。實(shí)際的發(fā)送窗口大小為:

發(fā)送窗口 = min(cwnd, rwnd)

當(dāng)cwnd > rwnd的時(shí)候是對(duì)方的接收能力限制了我的發(fā)送速度,而當(dāng)rwnd > cwnd的時(shí)候,是我的網(wǎng)絡(luò)情況造成了發(fā)送比較慢的情況。

發(fā)送窗口又是如何決定發(fā)送速度的呢:

(2)發(fā)送窗口

假設(shè)現(xiàn)在要發(fā)送hello, world這個(gè)文本局待,已經(jīng)知道發(fā)送窗口為5B薛耻,最大報(bào)文段MSS減掉報(bào)文頭占用的空間之后還剩下2B蝙搔,那么發(fā)送如下圖所示:

當(dāng)我收到ACK報(bào)文之后泉褐,如ACK:3,那么就可以將我的發(fā)送窗口向右移動(dòng)兩個(gè)字節(jié)矩欠,然后繼續(xù)發(fā)送發(fā)送窗口里未發(fā)送的報(bào)文财剖,如下圖所示:

如果沒有收到對(duì)方的ACK,那么發(fā)送窗口將不可向右移動(dòng)癌淮,也就是說不會(huì)發(fā)送了躺坟,如果ACK回復(fù)得慢,或者發(fā)送窗口本身比較比小乳蓄,那么發(fā)送的速度就沒那么快了咪橙。這就是發(fā)送窗口控制發(fā)送速度的原理。當(dāng)對(duì)方的帶寬下降時(shí)虚倒,它減少它的接收窗口來控制我的發(fā)送速度美侦,而當(dāng)我的網(wǎng)卡的時(shí)候我減少我的擁塞窗口控制發(fā)送速度。

但是怎么知道網(wǎng)卡了呢魂奥?

(3)慢啟動(dòng)和擁塞避免

由于建立完連接后菠剩,發(fā)送方不知道當(dāng)前的網(wǎng)絡(luò)情況怎么樣,所以它會(huì)非常地謹(jǐn)慎耻煤,先慢慢地發(fā)具壮,如果對(duì)方的ACK回復(fù)很及時(shí),那么說明可以繼續(xù)加大發(fā)送的量哈蝇,并且指數(shù)位地增加棺妓,這個(gè)就是慢啟動(dòng)。如下訪問一個(gè)Linux服務(wù)器的網(wǎng)址:

可以看到炮赦,服務(wù)在收到一個(gè)GET請(qǐng)求后進(jìn)行響應(yīng)怜跑,第一次同時(shí)只發(fā)3個(gè)包,并且從時(shí)間間隔上我們可以肯定它是故意的吠勘。也就是說它是一個(gè)慢啟動(dòng)性芬,為什么第一次是3個(gè)呢,因?yàn)長(zhǎng)inux 2的系統(tǒng)的初始化擁塞窗口initcwnd為3MSS剧防,3MSS說明第一次只能發(fā)3個(gè)包(每個(gè)包不能超過最大報(bào)文段的長(zhǎng)度)批旺,不同操作系統(tǒng)的initcwnd值如下所示,參考

Linux3據(jù)說是因?yàn)榻邮芰斯雀璧慕ㄗh诵姜,所以改成了10MSS汽煮。

具體慢啟動(dòng)的過程如下圖表所示:

擁塞窗口會(huì)以指數(shù)倍增長(zhǎng)搏熄,一直增長(zhǎng)到擁塞閾值ssthresh,假設(shè)這個(gè)值為192暇赤。然后再以遞增的方式增加擁塞窗口心例,這個(gè)階段叫擁塞避免。也就說當(dāng)cwnd < ssthresh時(shí)是慢啟動(dòng)的過程鞋囊,而當(dāng)cwnd > ssthresh時(shí)是擁塞避免止后。一直增長(zhǎng)到合適的帶寬大小。

在慢啟動(dòng)和擁塞避免過程中溜腐,可能會(huì)遇到網(wǎng)絡(luò)擁塞的情況译株,造成丟包的情況,具體表現(xiàn)為很長(zhǎng)時(shí)間沒有收到對(duì)方的ACK挺益,或者收到重復(fù)的ACK歉糜。

(4)超時(shí)重傳

假設(shè)很長(zhǎng)時(shí)間沒有收到對(duì)方發(fā)送的ACK,這個(gè)時(shí)間超過了定時(shí)器的范圍望众,導(dǎo)致進(jìn)行重傳匪补,如下圖所示:

上圖總共重傳了三次,第一次重傳隔了約1.2s烂翰,第二次隔了2.4s夯缺,第三次隔了3.5s,我們觀察到超時(shí)重傳的時(shí)間間隔會(huì)增加甘耿,并且發(fā)生超時(shí)之后最多只會(huì)發(fā)送一個(gè)報(bào)文踊兜,這個(gè)時(shí)候它進(jìn)入了慢啟動(dòng)的過程,如下圖表所示:

當(dāng)本機(jī)收到上傳服務(wù)器的ACK之后佳恬,又繼續(xù)發(fā)了兩個(gè)報(bào)文:

這個(gè)與上面的描述一致润文,即重新進(jìn)入了慢啟動(dòng)。

到這里我們就可以解決兩個(gè)山頭如何可靠地通信殿怜、保證同時(shí)去攻打另一個(gè)山頭的問題了。很簡(jiǎn)單曙砂,A派了只鴿子發(fā)一個(gè)消息給B之后头谜,B給他回了一個(gè)ACK,假設(shè)一只鴿子從B飛到A需要1個(gè)小時(shí)鸠澈,B派出去鴿子之后如果過了兩個(gè)小時(shí)柱告,B沒有收到A發(fā)送的一個(gè)重復(fù)的消息給它,即沒有進(jìn)行超時(shí)重傳笑陈,就可以認(rèn)為B派出去的那只鴿子A已經(jīng)收到了际度。那要是剛好不巧A派出去的第二只鴿子不見了呢,那A又再繼續(xù)超時(shí)重傳涵妥,如果需要重傳很多次的話乖菱,那就放棄吧,就像TCP一樣≈纤客觀條件不允許鹉勒,沒有辦法。

有一種情況不用等超時(shí)吵取,可以馬上進(jìn)行重傳禽额。

(5)快速重傳和快速恢復(fù)

假設(shè)本機(jī)向服務(wù)器按順序發(fā)了三個(gè)包,但是這三個(gè)包可能并沒有按順序到達(dá)皮官,有可能第三個(gè)包先到了脯倒,這個(gè)時(shí)候服務(wù)器收到了亂序的數(shù)據(jù),于是它馬上產(chǎn)生一個(gè)重復(fù)的ACK捺氢,要求重新獲取從第一個(gè)包開始的數(shù)據(jù)藻丢。收到重復(fù)ACK時(shí),不應(yīng)該馬上進(jìn)行重傳讯沈,因?yàn)榭赡芎芸靵y序的另外兩個(gè)又及時(shí)到了郁岩。但是當(dāng)收到三個(gè)重復(fù)的ACK時(shí)就可以認(rèn)為那個(gè)包已經(jīng)丟了,需要進(jìn)行重傳缺狠,不用等到超時(shí)问慎,這個(gè)就叫做快速重傳。如下圖所示:

快速重傳之后就進(jìn)入了快速恢復(fù)的階段挤茄。和超時(shí)重傳不一樣的地方是如叼,超時(shí)重傳認(rèn)為當(dāng)前的網(wǎng)絡(luò)情況十分糟糕,所以一下子把擁塞窗口cwnd置成了1穷劈,重新進(jìn)入慢啟動(dòng)笼恰。而快速恢復(fù)認(rèn)為當(dāng)前網(wǎng)絡(luò)并沒有那壞,它把擁塞窗口cwnd置成了當(dāng)前擁塞窗口的一半加3歇终,ssthresh置成老擁塞窗口的一半:如下圖所示:

這個(gè)過程就叫做快速恢復(fù)社证,當(dāng)收到一個(gè)新數(shù)據(jù)的ACK時(shí),將退出快速恢復(fù)评凝,將cwnd置為ssthresh追葡,進(jìn)入擁塞避免。

(6)慢啟動(dòng)的缺點(diǎn)

慢啟動(dòng)的優(yōu)點(diǎn)是在比較擁塞的網(wǎng)絡(luò)奕短,慢啟動(dòng)可以避免擁塞進(jìn)一步地加劇宜肉,但是它的缺點(diǎn)也是明顯的,對(duì)于正常的網(wǎng)絡(luò)翎碑,慢啟動(dòng)將降低傳輸?shù)男拭担绫緛硪粋€(gè)RTT就可以傳完的數(shù)據(jù),現(xiàn)在要分成幾個(gè)RTT(假設(shè)發(fā)送的數(shù)據(jù)量剛好是這樣)日杈,特別是Linux 2的服務(wù)器initcwnd只有3MSS遣铝,所以可以手動(dòng)把它改大佑刷,如改成10,可執(zhí)行以下命令:

sudo ip route change default via 192.168.1.1 dev eth0 proto static initcwnd 10

快速恢復(fù)的引入也是考慮到了慢啟動(dòng)的缺點(diǎn)翰蠢。

然后再討論一個(gè)很出名的算法

9. Nagle算法

假設(shè)要通過http發(fā)送hello, world這12個(gè)字節(jié)项乒,但是實(shí)際上要發(fā)送多少個(gè)字節(jié)呢?如下:

12:12:57.091926 IP 10.2.200.140.http-alt > 10.2.200.11.60882: Flags [P.], seq 288:301, ack 378, win 4105, options [nop,nop,TS val678005709ecr845655611], length 13: HTTP

http數(shù)據(jù)總共發(fā)送了300個(gè)字節(jié)梁沧,也就是說http報(bào)文頭就占用了288個(gè)字節(jié)檀何,但是這還不包括其它報(bào)文頭,如下所示:

也就是說為了發(fā)送12個(gè)字節(jié)的數(shù)據(jù)廷支,總共得發(fā)送356個(gè)字節(jié)频鉴,有效內(nèi)容僅占了4%不到。因此在那個(gè)需要用電話撥號(hào)上網(wǎng)的年代恋拍,這個(gè)代價(jià)就有點(diǎn)大了垛孔,所以Nagle算法的核心思想是:等數(shù)據(jù)積累多了再一起發(fā)出去,大概等待200ms施敢,這樣可以提高網(wǎng)絡(luò)的吞吐率周荐。

但是在現(xiàn)在光纖的時(shí)代米苹,帶寬和速度已經(jīng)不是太大的問題了计福,如果每個(gè)請(qǐng)求都要延遲200ms,會(huì)造成實(shí)時(shí)性比較差艇抠。所以通常是要把Nagle算法禁掉默怨,可以在創(chuàng)建套接字的時(shí)候設(shè)置TCP_NODELAY標(biāo)志位讯榕。

10. HTTP報(bào)文頭大小限制

(1)請(qǐng)求頭大小限制

標(biāo)準(zhǔn)并沒有規(guī)定http請(qǐng)求頭的大小限制,但是在實(shí)際的實(shí)現(xiàn)上會(huì)有限制匙睹。如nginx限制為4k - 8k愚屁,tomcat最小支持8K。

(2)url長(zhǎng)度限制

如下http報(bào)文格式所示:

URL是在請(qǐng)求行里面的痕檬,并不在請(qǐng)求頭里霎槐,同樣標(biāo)準(zhǔn)也沒有規(guī)定URL有長(zhǎng)度限制,但是實(shí)際的實(shí)現(xiàn)有限制梦谜,如下圖所示:

一個(gè)比較安全的值應(yīng)該是8K丘跌,這樣兼容性最好。同時(shí)需要注意的是GET請(qǐng)求改淑,參數(shù)是在URL里面,而POST請(qǐng)求參數(shù)是在請(qǐng)求數(shù)據(jù)里面浴讯,所以GET請(qǐng)求的數(shù)據(jù)不能太大朵夏。

(3)cookie的長(zhǎng)度限制

cookie是在請(qǐng)求頭里以普通鍵值對(duì)的方式存在,一般一個(gè)domain的cookie不能超過4Kb榆纽,50個(gè)cookie仰猖,不然瀏覽器可能會(huì)不支持捏肢。服務(wù)可以通常Set-Cookie通知客戶端設(shè)置cookie,而客戶端可以用Cookie字段告知服務(wù)現(xiàn)在的cookie數(shù)據(jù)是怎么樣的饥侵,如下所示:

上面的一些基礎(chǔ)問題討論完了鸵赫,我們終于可以來分析websocket了。

11. Websocket

(1)實(shí)現(xiàn)一個(gè)web聊天

怎么實(shí)現(xiàn)一個(gè)http的web的實(shí)時(shí)聊天呢躏升,怎么知道對(duì)方有沒有發(fā)送消息給我呢辩棒?有幾種方法。

第一種辦法使用輪詢膨疏,例如每隔2s就發(fā)一個(gè)請(qǐng)求向服務(wù)端查詢一睁,但是這種方法會(huì)造成資源的浪費(fèi)。

第二種辦法使用Service Worker實(shí)現(xiàn)瀏覽器的Push佃却,這種方法需要先注冊(cè)FCM賬號(hào)者吁,獲取到一個(gè)App Id,用Service Worker監(jiān)聽饲帅,服務(wù)向android.googleapis.com/發(fā)送消息复凳,谷歌服務(wù)器就會(huì)向那個(gè)App Id發(fā)送一個(gè)推送,就實(shí)現(xiàn)了瀏覽器的Push灶泵。但是這種辦法兼容性還不是很好育八,并且大陸的小伙伴無法在正常網(wǎng)絡(luò)環(huán)境收到谷歌服務(wù)器的消息。

所以就有了websocket建立常連接丘逸。為此建立一個(gè)websocket的demo.

(2)websocket的demo

為了實(shí)驗(yàn)单鹿,寫一個(gè)websocket的demo,先裝一個(gè)websocket的Node包深纲,然后監(jiān)聽在8080端口仲锄,接著寫客戶端html5 websocket代碼:

varsocket=newWebSocket("ws://10.2.200.140:8080");socket.onopen=function(){socket.send("長(zhǎng)江長(zhǎng)江,我是黃河");}socket.onmessage=function(event){document.write("收到來自黃河的消息:"+event.data);}

打開這個(gè)頁(yè)面湃鹊,瀏覽器就會(huì)顯示一個(gè)websocket的連接:

然后我們用tcpdump研究websocket連接建立的過程儒喊。

(3)Websocket連接建立

首先還是要先建立tcp連接,完成后客戶端發(fā)送一個(gè)upgrade的http請(qǐng)求:

14:23:36.926775 IP 10.2.200.11.61205 > 10.2.200.140.8080: Flags [P.], seq 1:435, ack 1, win 4117, options [nop,nop,TS val848067548ecr685816156], length 434: HTTP: GET / HTTP/1.1

這個(gè)報(bào)文的詳細(xì)內(nèi)容如下:

服務(wù)端收到后同意握手币呵,返回Switching Protocols怀愧,連接建立,如下報(bào)文:

14:23:36.929714 IP 10.2.200.140.8080 > 10.2.200.11.61205: Flags [P.], seq 1:164, ack 435, win 4104, options [nop,nop,TS val685816195ecr848067548], length 163: HTTP: HTTP/1.1 101 Switching Protocols

詳細(xì)內(nèi)容如下所示:

(4)傳送數(shù)據(jù)

發(fā)送“hello, world”12字節(jié)內(nèi)容余赢,用ws只需要發(fā)送18字節(jié)芯义,這比http 300個(gè)字節(jié)要少了很多:

14:24:36.009503 IP 10.2.200.11.61205 > 10.2.200.140.8080: Flags [P.], seq 492:510, ack 168, win 4112, options [nop,nop,TS val848126486ecr685863159], length 18: HTTP

14:24:36.009556 IP 10.2.200.140.8080 > 10.2.200.11.61205: Flags [.], ack 510, win 4101, options [nop,nop,TS val685875098ecr848126486], length 0

具體可以定義消息的類型,例如type = 1表示心跳消息妻柒,type = 2表示用戶發(fā)送的消息扛拨,還可以再定義subtype,并自定義消息內(nèi)容的格式举塔,再封裝一些自定義的消息機(jī)制等等绑警。

(5)關(guān)閉連接

30s后求泰,雙方?jīng)]有傳送數(shù)據(jù),websocket連接關(guān)閉计盒,進(jìn)行四次揮手渴频。

14:25:06.017016 IP 10.2.200.140.8080 > 10.2.200.11.61205: Flags [F.], seq 170, ack 510, win 4101, options [nop,nop,TS val685904974ecr848146558], length 0

這樣就實(shí)現(xiàn)了一個(gè)實(shí)時(shí)的web聊天,需要注意的是websocket是一套協(xié)議北启,任何人只要遵守這套協(xié)議就可以使用并和其他人互聯(lián)卜朗,不管你是JS還Android/IOS/C++/Java。ws默認(rèn)監(jiān)聽在80端口暖庄,wss監(jiān)聽在443端口聊替,和http/https一樣。

最后再比較一下websocket和webRTC

(6)Websocket和WebRTC

Websocket是為了解決實(shí)時(shí)傳送消息的問題培廓,當(dāng)然也可以傳送數(shù)據(jù)惹悄,但是不保證傳送的效率和質(zhì)量,而WebRTC可用于可靠地傳輸音視頻數(shù)據(jù)肩钠、文件等泣港。并且可建立P2P連接,不需要服務(wù)進(jìn)行轉(zhuǎn)發(fā)數(shù)據(jù)价匠。虛擬電話当纱、在線面試等現(xiàn)在很多都采用WebRTC實(shí)現(xiàn)。

最后做個(gè)總結(jié)踩窖。這篇文章介紹了很多通信協(xié)議的東西坡氯,分析了TCP/IP的三次握手和四次揮手,并討論了為什么握手是三次洋腮,而揮手是四次箫柳,還講了四層網(wǎng)絡(luò)模型,分析了工作在不同層的協(xié)議和工具啥供,后面又重點(diǎn)分析了TCP的擁塞控制悯恍,包括超時(shí)重傳、慢啟動(dòng)和擁塞避免伙狐、快速重傳和快速恢復(fù)涮毫,接著還講了點(diǎn)HTTP的東西,最后簡(jiǎn)單分析了下Websocket連接的過程和它的特點(diǎn)以及和WebRTC的區(qū)別贷屎。上面可以說是TCP/IP協(xié)議的核心內(nèi)容罢防,我們通過一兩個(gè)demo把它給串了起來,對(duì)讀者應(yīng)該有一個(gè)啟發(fā)作用唉侄,可以更深刻地理解網(wǎng)絡(luò)協(xié)議咒吐,當(dāng)你在寫一個(gè)請(qǐng)求的時(shí)候,你知道它的背后發(fā)生了什么。讀者可以根據(jù)本文再繼續(xù)查閱相關(guān)資料延伸擴(kuò)展渤滞。如有不正確之處還請(qǐng)指出。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末榴嗅,一起剝皮案震驚了整個(gè)濱河市妄呕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌嗽测,老刑警劉巖绪励,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異唠粥,居然都是意外死亡疏魏,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門晤愧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來大莫,“玉大人,你說我怎么就攤上這事官份≈焕澹” “怎么了?”我有些...
    開封第一講書人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵舅巷,是天一觀的道長(zhǎng)羔味。 經(jīng)常有香客問我,道長(zhǎng)钠右,這世上最難降的妖魔是什么赋元? 我笑而不...
    開封第一講書人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮飒房,結(jié)果婚禮上搁凸,老公的妹妹穿的比我還像新娘。我一直安慰自己情屹,他們只是感情好坪仇,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著垃你,像睡著了一般椅文。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上惜颇,一...
    開封第一講書人閱讀 49,760評(píng)論 1 289
  • 那天皆刺,我揣著相機(jī)與錄音,去河邊找鬼凌摄。 笑死羡蛾,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的锨亏。 我是一名探鬼主播痴怨,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼忙干,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了浪藻?” 一聲冷哼從身側(cè)響起捐迫,我...
    開封第一講書人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎爱葵,沒想到半個(gè)月后施戴,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡萌丈,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年赞哗,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片辆雾。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡肪笋,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出度迂,到底是詐尸還是另有隱情涂乌,我是刑警寧澤,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布英岭,位于F島的核電站湾盒,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏诅妹。R本人自食惡果不足惜罚勾,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望吭狡。 院中可真熱鬧尖殃,春花似錦、人聲如沸划煮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)弛秋。三九已至器躏,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蟹略,已是汗流浹背登失。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留挖炬,地道東北人揽浙。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親馅巷。 傳聞我的和親對(duì)象是個(gè)殘疾皇子膛虫,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • 我們用websocket和http來研究一下TCP/IP協(xié)議的一些特性,在上一篇文章《https連接的前幾毫秒發(fā)生...
    極樂君閱讀 1,922評(píng)論 1 6
  • 21.1 引言 TCP提供可靠的運(yùn)輸層钓猬。它使用的方法之一就是確認(rèn)從另一端收到的數(shù)據(jù)走敌。但數(shù)據(jù)和確認(rèn)都有可能會(huì)丟失。T...
    張芳濤閱讀 2,993評(píng)論 0 8
  • 個(gè)人認(rèn)為逗噩,Goodboy1881先生的TCP /IP 協(xié)議詳解學(xué)習(xí)博客系列博客是一部非常精彩的學(xué)習(xí)筆記,這雖然只是...
    貳零壹柒_fc10閱讀 5,051評(píng)論 0 8
  • 1.這篇文章不是本人原創(chuàng)的跌榔,只是個(gè)人為了對(duì)這部分知識(shí)做一個(gè)整理和系統(tǒng)的輸出而編輯成的异雁,在此鄭重地向本文所引用文章的...
    SOMCENT閱讀 13,049評(píng)論 6 174
  • 2016.11.19 今天下午去和樂知團(tuán)隊(duì)師姐交流,發(fā)現(xiàn)了很多我不曾涉及的領(lǐng)域僧须,但這卻奇異般的不會(huì)讓我有距離感纲刀,相...
    午間西瓜閱讀 147評(píng)論 0 0