背景知識
再談端口號
端口號(Port)標識了一個主機上進行通信的不同的應用程序;
在TCP/IP協(xié)議中, 用 "源IP", "源端口號", "目的IP", "目的端口號", "協(xié)議號" 這樣一個五元組來標識一個通信(可以通過 netstat -n查看);?
端口號范圍劃分
0 - 1023: 知名端口號, HTTP, FTP, SSH 比特科技 等這些廣為使用的應用層協(xié)議, 他們的端口號都是固定的.
1024 - 65535: 操作系統(tǒng)動態(tài)分配的端口號. 客戶端程序的端口號, 就是由操作系統(tǒng)從這個范圍分配的.
認識知名端口號(Well-Know Port Number)
ssh服務器, 使用22端口
ftp服務器, 使用21端口
telnet服務器, 使用23端口
http服務器, 使用80端口
https服務器, 使用443
端口號配置文件可以在/etc/services文件查看
netstat
netstat是一個用來查看網(wǎng)絡狀態(tài)的重要工具.
語法:netstat [選項]
功能:查看網(wǎng)絡狀態(tài)
常用選項:
n 拒絕顯示別名,能顯示數(shù)字的全部轉(zhuǎn)化成數(shù)字
l 僅列出有在 Listen (監(jiān)聽) 的服務狀態(tài)
p 顯示建立相關鏈接的程序名
t (tcp)僅顯示tcp相關選項
u (udp)僅顯示udp相關選項
a (all)顯示所有選項,默認不顯示LISTEN相關
pidof
在查看服務器的進程id時非常方便.
語法:pidof [進程名]
功能:通過進程名, 查看進程id
xargs:將管道內(nèi)容拼接到當前指令之后
pidof [任務名]? |? xargs? ?kill -9
UDP協(xié)議
UDP協(xié)議端格式
?16位UDP長度, 表示整個數(shù)據(jù)報(UDP首部+UDP數(shù)據(jù))的最大長度;
如果校驗和出錯, 就會直接丟棄;
UDP的特點
類似于發(fā)快遞:
無連接: 知道對端的IP和端口號就直接進行傳輸, 不需要建立連接;
不可靠: 沒有確認機制, 沒有重傳機制; 如果因為網(wǎng)絡故障該段無法發(fā)到對方, UDP協(xié)議層也不會給應用層 返回任何錯誤信息;
面向數(shù)據(jù)報: 不能夠靈活的控制讀寫數(shù)據(jù)的次數(shù)和數(shù)量;(整發(fā)整取)
面向數(shù)據(jù)報
應用層交給UDP多長的報文, UDP原樣發(fā)送, 既不會拆分, 也不會合并;
用UDP傳輸100個字節(jié)的數(shù)據(jù): 如果發(fā)送端調(diào)用一次sendto, 發(fā)送100個字節(jié), 那么接收端也必須調(diào)用對應的一次recvfrom, 接收100個 字節(jié); 而不能循環(huán)調(diào)用10次recvfrom, 每次接收10個字節(jié)
UDP協(xié)議首部中有一個16位的最大長度. 也就是說一個UDP能傳輸?shù)臄?shù)據(jù)最大長度是64K(包含UDP首 部).
然而64K在當今的互聯(lián)網(wǎng)環(huán)境下, 是一個非常小的數(shù)字. 如果我們需要傳輸?shù)臄?shù)據(jù)超過64K, 就需要在應用層手動的分包, 多次發(fā)送, 并在接收端手動拼裝;
TCP協(xié)議
TCP全稱為 "傳輸控制協(xié)議(Transmission Control Protocol"). 人如其名, 要對數(shù)據(jù)的傳輸進行一個詳細的控制;
?源/目的端口號: 表示數(shù)據(jù)是從哪個進程來, 到哪個進程去;
32位序號/32位確認號: 控制數(shù)據(jù)接收和發(fā)送;
為什么要有32位序號/32位確認號?
以為TCP是全雙工通信。
4位TCP報頭長度: 表示該TCP頭部有多少個32位bit(有多少個4字節(jié)); 所以TCP頭部最大長度是15 * 4 = 60(報頭總長度 = 4位TCP報頭長度 * 4字節(jié))
6位標志位(決定TCP報文類型):
URG(帶外數(shù)據(jù)): (如果數(shù)據(jù)想插隊,越過緩沖區(qū)直接被讀?。┚o急指針(在有效載荷中的偏移量,只有一個字節(jié)是緊急數(shù)據(jù))是否有效
ACK: 確認數(shù)據(jù)是否收到(正常通信都會置1)
PSH: 提示接收端應用程序立刻從TCP緩沖區(qū)把數(shù)據(jù)讀走
RST: 對方要求重新建立連接; 我們把攜帶RST標識的稱為復位報文段(服務器重啟后,客戶端發(fā)送報文,服務端發(fā)帶RST的報文,鏈接復位)
SYN: 請求建立連接(請求握手); 我們把攜帶SYN標識的稱為同步報文段
FIN: 斷開連接請求, 我們稱攜帶FIN標識的為結(jié)束報文段
16位窗口大小: 填入自己的接收緩沖區(qū)剩余空間大小,流量控制
16位校驗和: 發(fā)送端填充, CRC校驗. 接收端校驗不通過, 則認為數(shù)據(jù)有問題. 此處的檢驗和不光包含TCP首部, 也 包含TCP數(shù)據(jù)部分.
16位緊急指針: 標識哪部分數(shù)據(jù)是緊急數(shù)據(jù);
40字節(jié)頭部選項: 暫時忽略;
send和recv通過設置MSG_OOB讀取/發(fā)送帶外數(shù)據(jù)。
序號是發(fā)送端發(fā)送的數(shù)據(jù)段的序號(1~1000,就發(fā)1),確認序號是應答收到的數(shù)據(jù)段的序號+數(shù)據(jù)大小.(確認序號就是確認你在這個序號之前的數(shù)據(jù)都被我收到了)
超時重傳機制(解決丟包問題)
超時沒有接收到ACK應答有兩種情況:
1.真的丟了
真的丟了也分為兩種情況,正常數(shù)據(jù)段丟了或者確認數(shù)據(jù)段丟了。如果是正常數(shù)據(jù)段丟了,再發(fā)一次就行,如果是ACK應答丟了,再發(fā)一次之后,接收端再發(fā)一次ACK應答就行。
2.還在路上
如果是還在路上,那么重發(fā)會產(chǎn)生數(shù)據(jù)重復問題。
接收端會檢驗數(shù)據(jù)段的32位序號,如果重復就丟棄。
超時時間為(2^n) * 500ms
三次握手
為什么是三次握手?
因為鏈接需要被管理,先描述后組織,維護一個鏈接有時間成本和空間成本。
一次握手和兩次握手可能會有SYN洪水,三次握手是驗證全雙工通信通暢的最小成本。
四次揮手
如果服務端出現(xiàn)大量的close_wait有兩種可能:
1.沒有close文件描述符
2.服務器有壓力
主動斷開連接的乙方為什么要維持一段時間(2 * MSL(60s))的TIME_WAIT狀態(tài)?
1.保證最后一個ACK被收到
2.有可能在斷開時,網(wǎng)絡中有滯留的報文
當端口處于TIME_WAIT狀態(tài)就不能被綁定,要解決這個問題只要在綁定套接字之前加入下面的代碼就行:
socklen_t t = 1;
setsockopt(_listen_sock, SOL_SOCKET, SO_REUSEADDR, &t, sizeof(t));
四次揮手也有可能變成三次揮手。
滑動窗口(類似于環(huán)形隊列)
?既然這樣一發(fā)一收的方式性能較低, 那么我們一次發(fā)送多條數(shù)據(jù), 就可以大大的提高性能(其實是將多個段的等待時 間重疊在一起了).
?那么如果出現(xiàn)了丟包, 如何進行重傳? 這里分兩種情況討論.
情況一: 數(shù)據(jù)包已經(jīng)抵達, ACK被丟了.
只要最新的ACK(同時確認前面的數(shù)據(jù)都被成功接收)應答被收到,就可以忽略前面丟失的ACK?。
情況二: 數(shù)據(jù)包就直接丟了.
當某一段報文段丟失之后, 發(fā)送端會一直收到 1001 這樣的ACK, 就像是在提醒發(fā)送端 "我想要的是 1001" 一樣;
如果發(fā)送端主機連續(xù)三次收到了同樣一個 "1001" 這樣的應答, 就會將對應的數(shù)據(jù) 1001 - 2000 重新發(fā)送;
這個時候接收端收到了 1001 之后, 再次返回的ACK就是7001了(因為2001 - 7000)接收端其實之前就已 經(jīng)收到了, 被放到了接收端操作系統(tǒng)內(nèi)核的接收緩沖區(qū)中;?
流量控制
因此TCP支持根據(jù)接收端的處理能力, 來決定發(fā)送端的發(fā)送速度. 這個機制就叫做流量控制(Flow Control);
流量控制是通過控制發(fā)送緩沖區(qū)的滑動窗口大小實現(xiàn)的:
滑動窗口 = min(擁塞窗口, 接收端緩沖區(qū)剩余空間)
擁塞控制
網(wǎng)絡擁塞狀態(tài):由于網(wǎng)絡擁堵,發(fā)送的報文大量丟失。
少量丟失報文可以通過超時重傳機制解決,但如果從宏觀來看,由于網(wǎng)絡原因造成網(wǎng)絡擁塞狀態(tài),就不能簡單重傳。如果只是簡單重傳只會加重網(wǎng)絡擁堵的狀況。
為了解決這個問題:TCP引入 慢啟動機制, 先發(fā)少量的數(shù)據(jù),探探路, 摸清當前的網(wǎng)絡擁堵狀態(tài), 再決定按照多大的速度傳輸數(shù)據(jù);
擁塞窗口是一個數(shù)字,一開始為1.
?ssthred(慢啟動閾值),擁塞窗口達到ssthred之后線性增長,當線性增長到擁塞狀態(tài)時,更新?lián)砣苊忾撝?,更新ssthred為擁塞避免閾值的一半。
延遲應答
如果接收數(shù)據(jù)的主機立刻返回ACK應答, 這時候返回的窗口可能比較小.(有賭的成分)
假設接收端緩沖區(qū)為1M. 一次收到了500K的數(shù)據(jù); 如果立刻應答, 返回的窗口就是500K;
但實際上可能處理端處理的速度很快, 10ms之內(nèi)就把500K數(shù)據(jù)從緩沖區(qū)消費掉了;
在這種情況下, 接收端處理還遠沒有達到自己的極限, 即使窗口再放大一些, 也能處理過來;
如果接收端稍微等一會再應答, 比如等待200ms再應答, 那么這個時候返回的窗口大小就是1M;
一定要記得, 窗口越大, 網(wǎng)絡吞吐量就越大, 傳輸效率就越高. 我們的目標是在保證網(wǎng)絡不擁塞的情況下盡量提高傳輸 效率; 那么所有的包都可以延遲應答么? 肯定也不是;
數(shù)量限制: 每隔N個包就應答一次;
時間限制: 超過最大延遲時間就應答一次;
具體的數(shù)量和超時時間, 依操作系統(tǒng)不同也有差異; 一般N取2, 超時時間取200ms;
捎帶應答
在延遲應答的基礎上, 我們發(fā)現(xiàn), 很多情況下, 客戶端服務器在應用層也是 "一發(fā)一收" 的. 意味著客戶端給服務器說 了 "How are you", 服務器也會給客戶端回一個 "Fine, thank you";
那么這個時候ACK就可以搭順風車, 和服務器回應的 "Fine, thank you" 一起回給客戶端
面向字節(jié)流
由于緩沖區(qū)的存在, TCP程序的讀和寫不需要一一匹配, 例如:
寫100個字節(jié)數(shù)據(jù)時, 可以調(diào)用一次write寫100個字節(jié), 也可以調(diào)用100次write, 每次寫一個字節(jié);
讀100個字節(jié)數(shù)據(jù)時, 也完全不需要考慮寫的時候是怎么寫的, 既可以一次read 100個字節(jié), 也可以一次 read一個字節(jié), 重復100次;
粘包問題
tcp的報文需要自己設置協(xié)議去把數(shù)據(jù)包分開。
那么如何避免粘包問題呢? 歸根結(jié)底就是一句話, 明確兩個包之間的邊界.
1.? 對于定長的包, 保證每次都按固定大小讀取即可; 例如上面的Request結(jié)構(gòu), 是固定大小的, 那么就從緩沖區(qū)從頭開始按sizeof(Request)依次讀取即可;
2.? 對于變長的包, 可以在包頭的位置, 約定一個包總長度的字段, 從而就知道了包的結(jié)束位置;
3.? 對于變長的包, 還可以在包和包之間使用明確的分隔符(應用層協(xié)議, 是程序猿自己來定的, 只要保證分隔 符不和正文沖突即可);
理解 listen 的第二個參數(shù)
對于服務器, listen 的第二個參數(shù)設置為 2, 并且不調(diào)用 accept
?
?
客戶端狀態(tài)正常, 但是服務器端出現(xiàn)了 SYN_RECV 狀態(tài), 而不是 ESTABLISHED 狀態(tài) 這是因為, Linux內(nèi)核協(xié)議棧為一個tcp連接管理使用兩個隊列:
1. 半鏈接隊列(用來保存處于SYN_SENT和SYN_RECV狀態(tài)的請求)
2. 全連接隊列(accpetd隊列)(用來保存處于established狀態(tài),但是應用層沒有調(diào)用accept取走的請求)文章來源:http://www.zghlxwxcb.cn/news/detail-612197.html
而全連接隊列的長度會受到 listen 第二個參數(shù)的影響. 全連接隊列滿了的時候, 就無法繼續(xù)讓當前連接的狀態(tài)進入 established 狀態(tài)了. 這個隊列的長度通過上述實驗可知, 是 listen 的第二個參數(shù) + 1.文章來源地址http://www.zghlxwxcb.cn/news/detail-612197.html
到了這里,關于網(wǎng)絡傳輸層協(xié)議:UDP和TCP的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!