1. 程式人生 > >2、【網路程式設計】TCP報文段/網路位元組序/主機位元組序/網-主位元組序轉換函式

2、【網路程式設計】TCP報文段/網路位元組序/主機位元組序/網-主位元組序轉換函式

一、TCP報文段格式

    TCP雖然是面向位元組流的,但TCP傳送的資料單元卻是報文段。一個TCP報文段分為首部和資料兩個部分。TCP報文段首部的前20個位元組是固定的,後面有4n位元組是根據需要增加的選項。TCP首部的最小長度是20位元組,最大長度是60位元組。

1、埠號

    (1)源埠:源埠和IP地址的作用是標識報文的返回地址,佔16位。

    (2)目的埠:目的埠指明接收方計算機上的應用程式介面,佔16位。

    TCP報頭中的源埠號和目的埠號同IP資料報中的源IP與目的IP唯一確定一條TCP連線。

2、序號和確認序號

    序號和確認號

:各佔32位,是TCP可靠傳輸的關鍵部分。序號是本報文段傳送的資料組的第一個位元組的序號。在TCP傳送的流中,每一個位元組一個序號。

    e.g:一個報文段的序號為300,此報文段資料部分共有100位元組,則下一個報文段的序號為400。所以序號確保了TCP傳輸的有序性。確認號,即ACK,指明下一個期待收到的位元組序號,表明該序號之前的所有資料已經正確無誤的收到。確認號只有當ACK標誌為1時才有效。比如建立連線時,SYN報文的ACK標誌位為0

3、資料偏移(首部長度)

    佔4位,由於首部可能含有可選項內容,因此TCP報頭的長度是不確定的,報頭不包含任何任選欄位則長度為20位元組,4位首部長度欄位所能表示的最大值為1111,轉化為10進製為15,15*32/8 = 60,故報頭最大長度為60位元組。

首部長度也叫資料偏移,是因為首部長度實際上指示了資料區在報文段中的起始偏移值。

4、保留

    佔6位,為將來定義新的用途保留,現在一般置0。

5、控制標誌位

    URG ACK PSH RST SYN FIN,共6個,每個標誌位佔1位,每一個標誌位表示一個控制功能。

    (1)URG:緊急指標標誌,為1時表示緊急指標有效,為0時則忽略緊急指標。

    (2)ACK:確認序號標誌,為1時表示確認號有效,為0表示報文中不含確認資訊,忽略確認號欄位。

    (3)PSH:push標誌,為1表示是帶有push標誌的資料,指示接收方在接收到該報文段以後,應儘快將這個報文段交給應用程式,而不是在緩衝區排隊。

    (4)RST:重置連線標誌,用於重置由於主機崩潰或其他原因而出現錯誤的連線。或者用於拒絕非法的報文段和拒絕連線請求

    (5)SYN:同步序號,用於建立連線過程,在連線請求中,SYN=1和ACK=0表示該資料段沒有使用捎帶的確認域,而連線應答捎帶一個確認,即SYN=1和ACK=1。

    (6)FIN:finish標誌,用於釋放連線,為1時表示傳送方已經沒有資料傳送了,即關閉本方資料流。

6、視窗

    滑動視窗大小,用來告知傳送端接受端的快取大小,以此控制傳送端傳送資料的速率,從而達到流量控制。視窗大小時一個16bit欄位,因而視窗大小最大為65535。

7、校驗和

    奇偶校驗,此校驗和是對整個的 TCP 報文段,包括 TCP 頭部和 TCP 資料,以 16 位字進行計算所得。由傳送端計算和儲存,並由接收端進行驗證。

8、緊急指標

    只有當 URG 標誌置 1 時緊急指標才有效。緊急指標是一個正的偏移量,和順序號欄位中的值相加表示緊急資料最後一個位元組的序號。 TCP 的緊急方式是傳送端向另一端傳送緊急資料的一種方式。

9、選項和填充

    最常見的可選欄位是最長報文大小,又稱為MSS(Maximum Segment Size),每個連線方通常都在通訊的第一個報文段(為建立連線而設定SYN標誌為1的那個段)中指明這個選項,它表示本端所能接受的最大報文段的長度。選項長度不一定是32位的整數倍,所以要加填充位,即在這個欄位中加入額外的零,以保證TCP頭是32的整數倍。

10、資料部分

    TCP 報文段中的資料部分是可選的。在一個連線建立和一個連線終止時,雙方交換的報文段僅有 TCP 首部。如果一方沒有資料要傳送,也使用沒有任何資料的首部來確認收到的資料。在處理超時的許多情況中,也會發送不帶任何資料的報文段。

二、主機位元組序/網路位元組序及之間的轉換函式

1、主機位元組序(HBO,Host Byte Order)

    就是我們平常說的大端和小端模式不同的CPU有不同的位元組序型別,這些位元組序是指整數在記憶體中儲存的順序,這個叫做主機序。l例如:intel x86結構採用小端模式,IBM power PC(非NT)採用的是大端模式。

引用標準的大端和小端的定義如下

    (1)小端就是低位位元組排放在記憶體的低地址端,高位位元組排放在記憶體的高地址端。小端模式是最符合人的思維的位元組序。

    (2)大端就是高位位元組排放在記憶體的低地址端,低位位元組排放在記憶體的高地址端。大端模式是最直觀的位元組序,只需要把記憶體地址從左到右按照由低到高的順序寫出。

【示例】
    我們要將0x12345678這個資料放入以0x0000開始的記憶體中,則結果為:

記憶體地址       大端模式       小端模式
0x0000         0x12          0x78
0x0001         0x34          0x56
0x0002         0x56          0x34
0x0003         0x78          0x12
2、網路位元組序(NBO,Network Byte Order)

    TCP/IP協議規定,網路資料流u應該採用大端位元組序,即低地址—高位元組。例如:4個位元組的32 bit值以下面的次序傳輸:首先是0~7bit,其次8~15bit,然後16~23bit,最後是24~31bit。

    位元組序,顧名思義位元組的順序,就是大於一個位元組型別的資料在記憶體中的存放順序,一個位元組的資料沒有順序的問題了。所以:在將一個地址繫結到socket的時候,請先將主機位元組序轉換成為網路位元組序,而不要假定主機位元組序跟網路位元組序一樣使用的是Big-Endian。
【示例】

#include <stdio.h>
#include <arpa/inet.h>

int main()
{
    unsigned long a = 0x12345678;
    unsigned char *p = (unsigned char *)(&a);

    printf("主機位元組序:%0x %0x %0x %0x\n", p[0], p[1], p[2], p[3]);

    unsigned long b = htonl(a); //將主機位元組序轉化成了網路位元組序
    p = (unsigned char *)(&b);
    printf("網路位元組序:%0x %0x %0x %0x\n", p[0], p[1], p[2], p[3]);
    return 0;
}
3、IP地址、埠號的主機位元組序、網路位元組序之間的轉換關係

    以IP地址127.0.0.1為例,看看兩者之間是如何轉換的:

//第一步:把IP地址每一部分轉換為8位的二進位制數。
IP地址:127.0.0.1=01111111.00000000.00000000.00000001
//第二步:把獲取的二進位制數轉換為十進位制數作為主機位元組序
主機位元組序:01111111.00000000.00000000.00000001=2130706433   
//第三步:然後把上面的四部分二進位制數從右往左按部分重新排列,
//排列後轉換為十進位制數,作為網路位元組序
網路位元組序:00000001.00000000.00000000.01111111=16777343

    以6000埠為例,看看埠號的主機位元組序與網路位元組序間的轉換:

//埠號本身其實就已經是主機位元組序了
//第一步:將埠號寫為16位的二進位制數,分為前8位和後8位
主機位元組序:00010111  01110000 = 6000
//然後把主機位元組序的前八位與後八位調換位置組成新的16位二進位制數,
//這新的16位二進位制數就是網路位元組序的二進位制表示了。
網路位元組序:01110000  00010111 = 28695
4、相關函式

(1)網路位元組序與主機位元組序之間的轉換函式

    能夠完成主機位元組序和網路位元組序轉換的函式有:htonl()、htons()、ntohl()、ntohs()。其中h表示“host”,n表示“net”,l表示“long”,s表示“short”

htonl()/htons()函式:將主機位元組序轉換為網路位元組序,函式原型如下:

    u_long PASCAL FAR htonl (u_long hostlong);
    u_short PASCAL FAR htons (u_short hostshort);

ntohs()/ntohs()函式:將網路位元組序轉換為主機位元組序,函式原型如下:

    u_long PASCAL FAR ntohl (u_long netlong);
    u_short PASCAL FAR ntohs (u_short netshort);

(2)其他相關函式

    inet_ntoa()函式:接受一個in_addr結構體型別的引數並返回一個以點分十進位制格式表示的IP地址字串。函式原型如下:

    #include<arpa/inet.h> 
    char FAR * PASCAL FAR inet_ntoa (struct in_addr in);

    inet_aton()函式:接受一個以點分十進位制格式表示的IP地址字串轉換成in_addr結構體型別的值,並存入該結構體。函式原型如下:

    //利用in_addr結構體,轉換完的IP字元直接存入結構體中
    #include<arpa/inet.h>
    int inet_aton(const char* string, struct in_addr * addr)
//成功時返回1,失敗為0

    inet_addr()函式:需要一個字串作為其引數,該字串指定了以點分十進位制格式表示的IP地址(例如:192.168.0.16)。而且inet_addr函式會返回一個適合分配給S_addr的u_long型別的數值即將IP地址的字串轉換為主機位元組序)。函式原型如下:

    #include<arpa/inet.h> 
    unsigned long PASCAL FAR inet_addr (const char FAR * cp);