【基礎知識】TCP三次握手四次揮手|unsigned int與int|struct結構體class類大小計算

TCP,UDP不同詳解,還有一些其他的

1.TCP三次握手與四次揮手

TCP報文格式

上圖中有幾個字段需要重點介紹下:
        (1)序號:Seq序號,佔32位,用來標識從TCP源端向目的端發送的字節流,發起方發送數據時對此進行標記。
        (2)確認序號:Ack序號,佔32位,只有ACK標誌位爲1時,確認序號字段纔有效,Ack=Seq+1。
        (3)標誌位:共6個,即URG、ACK、PSH、RST、SYN、FIN等,具體含義如下:
                (A)URG:緊急指針(urgent pointer)有效。
                (B)ACK:確認序號有效。
                (C)PSH:接收方應該儘快將這個報文交給應用層。
                (D)RST:重置連接。
                (E)SYN:發起一個新連接。(表示這個報文使用來建立新連接用的)
                (F)FIN:釋放一個連接。

        需要注意的是:
                (A)不要將確認序號Ack與標誌位中的ACK搞混了。
                (B)確認方Ack=發起方Seq+1,兩端配對。 

三次握手

TCP服務器進程先創建傳輸控制模塊TCB,準備接受客戶進程的連接請求,然後服務器進程就處於LISTEN(監聽)狀態,等待客戶的連接請求 

  1. 第一次握手:Client將標誌位SYN置爲1,隨機產生一個值seq=J,並將該數據包發送給Server,Client進入SYN_SENT狀態,等待Server確認。(TCP規定,SYN報文段(即SYN=1的報文段)不能攜帶數據,但要消耗掉一個序號。)
  2. 第二次握手:Server收到數據包後由標誌位SYN=1知道Client請求建立連接,Server將標誌位SYN和ACK都置爲1,確認序號ack=J+1,隨機產生一個值seq=K,並將該數據包發送給Client以確認連接請求,Server進入SYN_RCVD狀態。(請注意,這個報文段也不能攜帶數據,但同樣要消耗掉一個序號。)
  3. 第三次握手:Client收到確認後,檢查ack是否爲J+1,ACK是否爲1,如果正確則將標誌位ACK置爲1,ack=K+1,並將該數據包發送給Server,Server檢查ack是否爲K+1,ACK是否爲1,如果正確則連接建立成功,Client和Server進入ESTABLISHED狀態

 簡化一下不看Seq和ack的話過程如下:

  1. 客戶端發送SYN給服務端
  2. 服務端返回SYN+ACK給客戶端
  3. 客戶端確認,返回ACK給服務端

完成三次握手,客戶端與服務器開始傳送數據。

爲什麼客戶端還要發送一次確認呢?

這主要是爲了防止已失效的連接請求報文段突然又傳送到了服務器,因而產生錯誤。

  假定A發出的某一個連接請求報文段在傳輸的過程中並沒有丟失,而是在某個網絡節點長時間滯留了,以致延誤到連接釋放以後的某個時間纔到達B。本來這是一個早已失效的報文段。但B收到此失效的連接請求報文段後,就誤以爲A又發了一次新的連接請求,於是向A發出確認報文段,同意建立連接。假如不採用三次握手,那麼只要B發出確認,新的連接就建立了。

  由於A並未發出建立連接的請求,因此不會理睬B的確認,也不會向B發送數據。但B卻以爲新的運輸連接已經建立了,並一直等待A發來數據,因此白白浪費了許多資源。

  採用TCP三次握手的方法可以防止上述現象發生。例如在剛纔的情況下,由於A不會向B的確認發出確認,B由於收不到確認,就知道A並沒有要求建立連接。

四次揮手

  1. 客戶端發送一個報文給服務端(沒有數據),用來關閉Client到Server的數據傳送,其中FIN設置爲1,Sequence Number置爲u,客戶端進入FIN_WAIT_1狀態
  2. 服務端收到來自客戶端的請求,發送一個ACK給客戶端,Acknowledge置爲u+1,同時發送Sequence Number爲v,服務端年進入CLOSE_WAIT狀態
  3. 服務端發送一個報文給客戶端,用來關閉Server到Client的數據傳送,其中FIN設置爲1,ACK置爲1,Sequence置爲w,Acknowledge置爲u+1,用來關閉服務端到客戶端的數據傳送,服務端進入LAST_ACK狀態
  4. 客戶端收到FIN後,進入TIME_WAIT狀態,接着發送一個ACK給服務端,Acknowledge置爲w+1,Sequence Number置爲u+1,最後客戶端和服務端都進入CLOSED狀態

簡化一下不看Seq和ack:

  1. 第一次揮手:Client發送一個FIN,用來關閉Client到Server的數據傳送,Client進入FIN_WAIT_1狀態。
  2. 第二次揮手:Server收到FIN後,發送一個ACK給Client,確認序號爲收到序號+1(與SYN相同,一個FIN佔用一個序號),Server進入CLOSE_WAIT狀態。
  3. 第三次揮手:Server發送一個FIN,用來關閉Server到Client的數據傳送,Server進入LAST_ACK狀態。
  4. 第四次揮手:Client收到FIN後,Client進入TIME_WAIT狀態,接着發送一個ACK給Server,確認序號爲收到序號+1,Server進入CLOSED狀態,完成四次揮手。

爲什麼建立連接是三次握手,而關閉連接卻是四次揮手呢?

這是因爲服務端在LISTEN狀態下,收到建立連接請求的SYN報文後,把ACK和SYN放在一個報文裏發送給客戶端。而關閉連接時,當收到對方的FIN報文時,僅僅表示對方不再發送數據了但是還能接收數據,我們也未必把全部數據都發給了對方,所以我們可以立即close,也可以發送一些數據給對方後,再發送FIN報文給對方表示同意關閉連接。因此我們的ACK和FIN一般會分開發送。

之前討論的是一方主動關閉,另一方被動關閉的情況,實際中還會出現同時發起主動關閉的情況,具體流程如下圖 

è¿éåå¾çæè¿°

 

2.

 

3.單工,半雙工,全雙工

單工

單工數據傳輸只支持數據在一個方向上傳輸(在同一時間只有一方能接受或發送信息,不能實現雙向通信)

舉例:電視,廣播。

半雙工

半雙工數據傳輸允許數據在兩個方向上傳輸,但是,在某一時刻,只允許數據在一個方向上傳輸(它實際上是一種切換方向的單工通信;在同一時間只可以有一方接受或發送信息,可以實現雙向通信。)

舉例:對講機。

全雙工

全雙工數據通信允許數據同時在兩個方向上傳輸。(因此,全雙工通信是兩個單工通信方式的結合,它要求發送設備和接收設備都有獨立的接收和發送能力;在同一時間可以同時接受和發送信息,實現雙向通信。)

舉例:電話通信。

4.unsigned int 和 int相加

int main()
{
	int a = -6;
	unsigned int b = 4;
	if(a+b > 0)
		printf("a+b>0\n");//這句話被打印
	else
		printf("a+b<0\n");
	int z = a+b;
	if(z > 0)
		printf("z>0");
	else
		printf("z<0");//這句話被打印
}

下面解釋一下原因:當int和unsigned in相加時,要將int轉化爲unsigned int(有人說總是向能表示範圍更大的類型轉換),而int小於0,所以它的最高位是符號位,爲1,所以轉化的結果是一個很大的正數,在第一個if語句中,是兩個“正數”相加,結果自然就大於0了。而在z = a+b這一句時(相當於把unsigned int轉化爲int),它把a+b的結果看做一個int類型,而a+b最高位爲1,所以z是一個負數,所以打印的是第二個語句。

 

5.struct結構體大小計算(在32/64位操作系統)

預備知識:基本類型佔用字節

32位和64位的位數分別是什麼意思?

這個位數指的是CPU 裏面的通用寄存器的數據寬度爲64位,也就是說一個地址佔二進制位數是32/64。所以32位系統的指針佔大小是4字節,64位系統就是8字節。(有人說自己是64位系統爲什麼指針大小打印是4字節,因爲編譯器是32位的,跟編譯器而不是自己的電腦有關)

在32位操作系統和64位操作系統上,基本數據類型分別佔多少字節呢?

32位操作系統:

char : 1    int :4    short : 2    unsigned int : 4    long : 4    unsigned long : 4    long long : 8     float : 4    double : 8    指針 : 4

64位操作系統

char : 1    int :4    short : 2    unsigned int : 4    long : 8    unsigned long : 8    long long : 8     float : 4    double : 8    指針 : 8

 

默認對齊方式

在默認對齊方式下,結構體成員的內存分配滿足下面三個條件

  1. 結構體第一個成員的地址和結構體的首地址相同
  2. 結構體每個成員地址相對於結構體首地址的偏移量(offset)是該成員大小的整數倍,如果不是則編譯器會在成員之間添加填充字節(internal adding)。
    1. 成員變量是數組時,按照類型長度對齊,而不是數組長度對齊(比如char a[10]就是按1而不是10來對齊)
    2. 如果成員是struct結構體,根據上一條,並不是按該結構體大小對齊,而是按該結構體中數據類型最大的成員來對齊
    3. union聯合體同上
      1. union內存大小需要滿足以下2個條件:

        1. 大於或等於union中最長的成員變量的長度(char c[10]算10字節);

        2.  能整除其他成員變量長度(char c[10]算1字節)

  3. 結構體總的大小要是其成員中最大size(注意是最長的數據類型,而不是最長的變量)的整數倍,如果不是編譯器會在其末尾添加填充字節(trailing padding)。

指定對齊方式

可以使用#pragma pack(N)來指定結構體成員的對齊方式
對於指定的對齊方式,其成員的地址偏移以及結構的總的大小也有下面三個約束條件

  1. 結構體第一個成員的地址和結構體的首地址相同
  2. 結構體每個成員的地址偏移需要滿足:N大於等於該成員的大小,那麼該成員的地址偏移需滿足默認對齊方式(地址偏移是其成員大小的整數倍);N小於該成員的大小,那麼該成員的地址偏移是N的整數倍
  3. 結構體總的大小需要時N的整數倍,如果不是需要在結構體的末尾進行填充。
  4. 如果N大於結構體成員中最大成員的大小,則N不起作用,仍然按照默認方式對齊。

參考:

講的很細,例子可以看

指定對齊方式例子看這個

6.class類大小計算

空類:1
沒有虛函數:sizeof(數據成員)的和
有虛函數:sizeof(數據成員)的和(除靜態成員外)+sizeof(Virtual表指針)即4+父類成員

若類中包含成員,則類對象的大小隻包括其中非靜態成員經過對齊所佔的空間,對齊方式和結構體相同。


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章