1. 程式人生 > 實用技巧 >Linux C Socket 程式設計

Linux C Socket 程式設計

1 Socket 是什麼

Socket(套接字),就是對 網路上程序通訊端點抽象。一個 Socket 就是網路上程序通訊的一端,提供了應用層程序利用網路協議交換資料的機制

從所處的位置來講,套接字上聯應用程序,下聯網路協議棧,是應用程式通過網路協議進行通訊互動的介面。如下圖所示:

2 Socket 型別

2.1 標準套接字

標準套接字是在傳輸層使用的套接字,分為流式套接字(SOCK_STREAM)和資料報套接字(SOCK_DGRAM)。

標準套接字在接收和傳送時只能操作資料部分(TCP Payload / UDP Payload),而不能對 IP 首部或TCP 首部和 UDP 首部進行操作。

2.1.1 流套接字(SOCK_STREAM)

流套接字(SOCK_STREAM)用於提供 面向連線(可靠)的資料傳輸服務。

流套接字保證資料能夠實現無差錯、無重複發資料,並按順序接收。

流套接字(SOCK_STREAM)使用 TCP(The Transmission Control Protocol)協議 進行資料的傳輸

2.1.2 資料報套接字(SOCK_DGRAM)

資料報套接字(SOCK_DGRAM)用於提供 無連線(不可靠)的資料傳輸服務。

資料報套接字不保證資料傳輸的可靠性,資料有可能在傳輸過程中丟失或出現數據重複,且無法保證順序地接收到資料。

資料報套接字(SOCK_DGRAM)使用 UDP

(User DatagramProtocol)協議 進行資料的傳輸

2.2 原始套接字(SOCK_RAW)

原始套接字(SOCK_RAW)可以做到標準套接字做到的事,更可以做到標準套接字做不到的事。

原始套接字是在傳輸層及傳輸層以下使用的套接字。

原始套接字在接收和傳送時不僅能操作資料部分(TCP Payload / UDP Payload),也能對 IP 首部或TCP 首部和 UDP 首部進行操作。

因此如果我們開發的是更底層的應用,比如傳送一個自定義的 IP 包、UDP 包、TCP 包或 ICMP 包,捕獲所有經過本機網絡卡的資料包(sniffer),偽裝本機的 IP ,拒絕服務攻擊(DOS)等,都可以通過原始套接字(SOCK_RAW)實現。

注意:必須在管理員許可權下才能使用原始套接字。

3 Socket() 函式 介紹

3.1 功能

分配檔案描述符,建立 socket,即建立網路上程序通訊的端點。

3.2 標頭檔案

#include <sys/types.h>
#include <sys/socket.h>

3.3 函式原型

int socket(int domain, int type, int protocol)

3.4 引數

注意:type 和 protocol 不可以隨意組合,如 SOCK_STREAM 不可以跟 IPPROTO_UDP 組合。

具體的組合和應用場景可以參考 4 建立 Socket 及其應用場景

3.4.1 domain

domain:即協議域,又稱為協議族(family),如下所示:

  • AF_INET / PF_INET(2):IPv4,獲取 網路層的資料

  • AF_INET6:IPv6

  • AF_UNIX:UNIX 系統本地通訊

  • AF_PACKET / PF_PACKET(17):乙太網包,獲取 資料鏈路層的資料

注:

  1. AF = Address Family(地址族),PF = Protocol Family(協議族)

  2. 理論上建立 socket 時是指定協議,應該用 PF_xxxx,設定地址時應該用 AF_xxxx。當然 AF_xxxx和 PF_xxxx 的值是相同的,混用也不會有太大的問題。

3.4.2 type

type:指定 socket 型別,如下所示:

  • SOCK_STREAM(1):面向連線的流式套接字(TCP)

  • SOCK_DGRAM(2):面向無連線的資料包套接字(UDP)

  • SOCK_RAW(3):接收 底層資料報文 的原始套接字

  • SOCK_PACKET(10):過時型別,可以使用,但是已經廢棄,以後不保證還能支援,不推薦使用。

3.4.3 protocol

protocol:指定協議,如下所示:

  • 0:自動選擇 type 型別對應的預設協議。

  • IPPROTO_IP(0):接受 TCP 型別的資料幀

  • IPPROTO_ICMP(1):接受 ICMP 型別的資料幀

  • IPPROTO_IGMP(2)接受 IGMP 型別的資料幀

  • IPPROTO_TCP(6):接受 TCP 型別的資料幀

  • IPPROTO_UDP(17):接受 UDP 型別的資料幀

  • ETH_P_IP(0x800):接收發往本機 MAC 的 IP 型別的資料幀

  • ETH_P_ARP(0x806):接受發往本機 MAC 的 ARP 型別的資料幀

  • ETH_P_RARP(0x8035):接受發往本機 MAC 的 RARP 型別的資料幀

  • ETH_P_ALL(0x3):接收發往本機 MAC 的所有型別 IP ARP RARP 的資料幀,接收從本機發出的所有型別的資料幀。(混雜模式開啟的情況下,會接收到非發往本地 MAC 的資料幀)

3.5 返回值

  • 成功:返回一個檔案描述符

  • 失敗:返回 -1,並設定 errno

3.6 備註

詳情檢視 man 手冊:man 2 socket

4 建立 Socket 及其應用場景

5 bind() 函式

5.1 功能

將 IP 地址資訊繫結到 socket。

5.2 標頭檔案

#include <sys/types.h>          
#include <sys/socket.h>

5.3 函式原型

int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

5.4 引數

5.4.1 sockfd

通訊 socket

5.4.2 addr

要繫結的地址資訊(包括IP地址,埠號)。

通用地址結構體定義:

struct sockaddr 
{
    sa_family_t sa_family;   // 地址族, AF_xxx
    char        sa_data[14]; // 包括 IP 和埠號
}

新型的地址結構體定義:(檢視新型的結構體資訊: gedit /usr/include/linux/in.h )

struct sockaddr_in 
{
  __kernel_sa_family_t    sin_family;    // 地址族,IP 協議。預設:AF_INET
  __be16                  sin_port;      // 埠號
  struct in_addr          sin_addr;      // 網路 IP 地址

  unsigned char           __pad          // 8 位的預留介面
};

5.4.3 addrlen

地址資訊大小

5.5 返回值

  • 成功:返回 0

  • 失敗:返回 -1,並設定 errno

5.6 備註

詳細檢視 man 手冊:man 2 bind

6 listen() 函式

6.1 功能

監聽指定埠,socket() 建立的 socket 是主動的,呼叫 listen 使得該 socket 成為 監聽 socket ,變主動為被動。

6.2 標頭檔案

#include <sys/socket.h>

6.3 函式原型

int listen(int sockfd, int backlog);

6.4 引數

6.4.1 sockfd

通訊 socket

6.4.2 backlog

同時能處理的最大連線要求

6.5 返回值

  • 成功:返回 0

  • 失敗:返回 -1,並設定 errno

6.6 備註

詳細檢視 man 手冊:man 2 listen

7 accept() 函式

7.1 功能

提取出 監聽 socket 的等待連線佇列中 第一個連線請求,建立 一個新的 socket,即 連線 socket

新建立的 連線 socket 用於傳送資料和接受資料。

7.2 標頭檔案

#include <sys/socket.h>

7.3 函式原型

#include <sys/types.h>          
#include <sys/socket.h>

7.4 引數

7.4.1 sockfd

監聽 socket,即 在 呼叫 listen() 後的 監聽 socket。

7.4.2 addr

(可選)指標,指向一緩衝區,其中接收為通訊層所知的連線實體的地址。Addr引數的實際格式由套介面建立時所產生的地址族確定。

7.4.3 addrlen

(可選)指標,輸入引數,配合addr一起使用,指向存有addr地址長度的整型數。

7.5 返回值

  • 成功:指向 新的 socket(連線 socket)的檔案描述符。

  • 失敗:返回 -1,並設定 errno

7.6 備註

詳細檢視 man 手冊:man 2 listen

8 connect() 函式

8.1 功能

傳送連線請求

8.2 標頭檔案

#include <sys/types.h>          
#include <sys/socket.h>

8.3 函式原型

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

8.4 引數

8.4.1 sockfd

通訊 socket

8.4.2 addr

要連線的伺服器地址

8.4.3 addrlen

地址資訊大小

8.5 返回值

  • 成功:返回 0

  • 失敗:返回 -1,並設定 errno

8.6 備註

詳細檢視 man 手冊:man 2 connect

9 sendto() 函式

9.1 功能

將資料由指定的 socket 傳給對方主機

9.2 標頭檔案

#include <sys/types.h>          
#include <sys/socket.h>

9.3 函式原型

int sendto (int sockfd , const void * msg, int len, unsigned int flags, const
struct sockaddr * to , int tolen);

9.4 引數

9.4.1 sockfd

已建立連線的 socket,如果利用 UDP 協議則不需建立連線。

9.4.2 msg

傳送資料的緩衝區。

9.4.3 len

緩衝區長度。

9.4.4 flags

呼叫方式標誌位,一般設為 0 。

9.4.5 to

用來指定要傳送的網路地址,結構 sockaddr

9.4.6 tolen

sockaddr 的長度

9.5 返回值

  • 成功:返回實際傳送出去的字元數

  • 失敗:返回 -1,並設定 errno

9.6 備註

詳細檢視 man 手冊:man 2 sendto

10 recvfrom() 函式

10.1 功能

接收遠端主機經指定的 socket 傳來的資料,並把資料傳到由引數 buf 指向的記憶體空間。

10.2 標頭檔案

#include <sys/types.h>          
#include <sys/socket.h>

10.3 函式原型

int recvfrom(int sockfd,void *buf,int len,unsigned int flags, struct sockaddr *from,int *fromlen);

10.4 引數

10.4.1 sockfd

已建立連線的 socket,如果利用 UDP 協議則不需建立連線。

10.4.2 buf

接收資料緩衝區。

10.4.3 len

緩衝區長度。

10.4.4 flags

呼叫方式標誌位,一般設為 0 。

10.4.5 from

(可選)指標,指向裝有源地址的緩衝區,結構 sockaddr

10.4.6 fromlen

(可選)指標,指向 from 緩衝區長度值,sockaddr 的結構長度

10.5 返回值

  • 成功:返回實際接受到的字元數

  • 失敗:返回 -1,並設定 errno

10.6 備註

詳細檢視 man 手冊:man 2 recvfrom

11 位元組序

位元組序,是 大於一個位元組型別的資料在記憶體中的存放順序,由 CPU 架構決定,與作業系統無關。是在跨平臺和網路程式設計中,時常要考慮的問題。

11.1 高低地址

在記憶體中,棧是向下生長的,以char arr[4]為例,(因為 char 型別資料只有一個位元組,不存在位元組序的問題)依次輸出每個元素的地址,可以發現,arr[0] 的地址最低,arr[3] 的地址最高,如圖:

11.2 高低位元組

在十進位制中靠左邊的是高位,靠右邊的是低位,在其他進位制也是如此。

例如: 0x12345678,從高位到低位的位元組依次是 0x12、0x34、0x56 和 0x78。

11.3 位元組序分類 - 大小端模式

位元組序被分為兩類:

  1. 大端模式(Big-endian):記憶體的 低地址 存放 資料的高位元組,記憶體的 高地址 存放 資料的低位元組。(與人類閱讀順序一致)

  2. 小端模式(Little-endian),是指記憶體的 低地址 存放 資料的低位元組,記憶體的 高地址 存放 資料的高位元組

大端模式 CPU 代表是 IBM Power PC,小端模式 CPU 代表是 Intel X86、ARM。

11.4 大小端示例

以 0x12345678 為例,兩種模式在記憶體中的儲存情況,如下表所示:

11.5 判斷大小端

利用 C 語言 union 聯合體所有成員共用同一塊記憶體的特性,可以用聯合體快速實現判斷大小端。

#include <stdio.h>
union u
{
    char c[4];
    int i;
};
int main(void)
{
    union u test;
    int j;

    test.i = 0x12345678;

    for(j = 0; j < sizeof(test.c); j++)
    {
        printf("0x%x\n",test.c[j]);
    }

    return 0;
}

執行後結果:

可以看出,我的機器是小端位元組序。

11.6 網路位元組序與本機位元組序

網路位元組序(NBO,Network Byte Order),是 TCP/IP 中規定好的一種資料表示格式。它與具體的 CPU 型別、作業系統等無關,從而可以保證資料在不同主機之間傳輸時能夠被正確解釋。

網路位元組序採用大端(Big-endian)位元組序排序方式。

主機位元組順序(HBO,Host Network Order),與機器 CPU 相關,資料的儲存順序由 CPU 決定。

11.6.1 轉換函式

socket 程式設計中經常會用到 4 個網路位元組順序與本地位元組順序之間的轉換函式:htons()、ntohl()、 ntohs()、htons()。

htonl()--"Host to Network Long"        // 長整型資料主機位元組順序轉網路位元組順序
ntohl()--"Network to Host Long"        // 長整型資料網路位元組順序轉主機位元組順序
htons()--"Host to Network Short"       // 短整型資料主機位元組順序轉網路位元組順序
ntohs()--"Network to Host Short"       // 短整型資料網路位元組順序轉主機位元組順序

在使用小端位元組序的系統中,這些函式會把位元組序進行轉換。

在使用大端位元組序的系統中,這些函式會定義成空巨集。

12 程式碼示例

12.1 標準套接字(SOCK_STREAM - TCP)

12.1.1 TCP Socket 通訊過程

12.1.1.1 伺服器

1. 建立連線階段

  • 呼叫 socket(),分配檔案描述符,建立 伺服器 socket

  • 呼叫 bind(),將 socket 與本地 IP 地址和埠繫結

  • 呼叫 listen(),監聽指定埠,socket() 建立的 socket 是主動的,呼叫 listen 使得該 socket 成為監聽 socket ,變主動為被動

  • 呼叫 accept(),獲得 連線 socket,阻塞等待客戶端發起連線

2. 資料互動階段

  • 呼叫 read(),阻塞等待客戶端傳送的資料請求,收到請求後從 read() 返回,處理客戶端請求

  • 呼叫 write(),將資料傳送給客戶端

3. 關閉連線

  • 當 read() 返回 0 的時候,說明客戶端發來了 FIN 資料包,即關閉連線,呼叫 close() 關閉 連線 socket 和 監聽 socket

12.1.1.2 客戶端

1. 建立連線階段

  • 呼叫 socket(),分配檔案描述符,建立 客戶端 socket

  • 呼叫 connect(),向伺服器傳送建立連線請求

2. 資料互動階段

  • 呼叫 write(),向伺服器傳送資料

  • 呼叫 read(),阻塞等待伺服器應答

3. 關閉連線

  • 當沒有資料傳送的時候,呼叫 close() 關閉 客戶端 socket ,即關閉連線,向伺服器傳送 FIN 資料報

12.1.2 單個客戶端單個伺服器的 TCP 通訊

Linux-C TCP簡單例子 - nanfeibuyi - https://blog.csdn.net/nanfeibuyi/article/details/88540144 - 例子1

12.1.3 多執行緒實現 - 單個客戶端單個伺服器的 TCP 通訊

Linux-C TCP簡單例子 - nanfeibuyi - https://blog.csdn.net/nanfeibuyi/article/details/88540144 - 例子2

12.1.4 多路複用實現 - 單個客戶端單個伺服器的 TCP 通訊

Linux-C TCP簡單例子 - nanfeibuyi - https://blog.csdn.net/nanfeibuyi/article/details/88540144 - 例子3

12.1.5 多個客戶端單個伺服器的 TCP 通訊

Linux-C TCP簡單例子 - nanfeibuyi - https://blog.csdn.net/nanfeibuyi/article/details/88540144 - 例子4

12.1.6 多執行緒實現 - 多個客戶端單個伺服器的 TCP 通訊

Linux-C TCP簡單例子 - nanfeibuyi - https://blog.csdn.net/nanfeibuyi/article/details/88540144 - 例子5

12.1.7 多路複用實現 - 多個客戶端單個伺服器的 TCP 通訊

Linux-C TCP簡單例子 - nanfeibuyi - https://blog.csdn.net/nanfeibuyi/article/details/88540144 - 例子6

12.2 標準套接字(SOCK_DGRAM- UDP)

12.2.1 UDP Socket 通訊過程

12.2.1.1 伺服器

  1. 建立連線階段
  • 呼叫 socket(),分配檔案描述符,建立 伺服器 socket

  • 呼叫 bind(),將 socket 與本地 IP 地址和埠繫結

  1. 資料互動階段
  • 呼叫 recvfrom(),阻塞,接受客戶端的資料

  • 呼叫 sendto(),將資料傳送給客戶端

  1. 關閉連線
  • 呼叫 close() 關閉 伺服器 socket

12.2.1.2 客戶端

  1. 建立連線階段
  • 呼叫 socket(),分配檔案描述符,建立 客戶端 socket
  1. 資料互動階段
  • 呼叫 sendto(),向伺服器傳送資料

  • 呼叫 recvfrom(),阻塞,接受伺服器的資料

  1. 關閉連線
  • 呼叫 close() 關閉 客戶端 socket ,即關閉連線。

12.2.2 單個客戶端單個伺服器的 UDP 通訊

程式碼來源:Linux-C UDP簡單例子 - nanfeibuyi - https://blog.csdn.net/nanfeibuyi/article/details/88540233 - 例子1

12.2.3 多執行緒實現 - 單個客戶端單個伺服器的 UDP 通訊

程式碼來源:Linux-C UDP簡單例子 - nanfeibuyi - https://blog.csdn.net/nanfeibuyi/article/details/88540233 - 例子2

12.2.4 多路複用實現 - 單個客戶端單個伺服器的 UDP 通訊

程式碼來源:Linux-C UDP簡單例子 - nanfeibuyi - https://blog.csdn.net/nanfeibuyi/article/details/88540233 - 例子3

12.2.4 UDP 通訊組播

程式碼來源:Linux-C UDP簡單例子 - nanfeibuyi - https://blog.csdn.net/nanfeibuyi/article/details/88540233 - 例子4

12.2.4 UDP 通訊廣播

程式碼來源:Linux-C UDP簡單例子 - nanfeibuyi - https://blog.csdn.net/nanfeibuyi/article/details/88540233 - 例子5

12.3 原始套接字

12.3.1 抓取乙太網上的所有資料幀

程式碼來源:GitHub - zhouyingjiu - https://github.com/zouyingjiu/sniffer


/* 
 *        sniffer.c 
 * 
 *        功能: 
 *                linux rawSocket 抓取乙太網上的所有資料幀 
 * 
 *        引數: 
 *                無 
 * 
 *  注意: 
 *      執行該程式需要 root 許可權 sudo ./  
 */ 
 
#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
 
#ifdef __linux__ 
        #include <unistd.h> 
        #include <errno.h> 
        #include <sys/socket.h> 
        #include <sys/types.h> 
        #include <netinet/in.h> 
        #include <netinet/ip.h> 
        #include <netinet/tcp.h> 
        #include <netinet/udp.h> 
        #include <netinet/ip_icmp.h> 
        #include <net/if_arp.h> 
        #include <netinet/if_ether.h> 
        #include <net/if.h> 
        #include <sys/ioctl.h> 
#elif __win32__ 
        #include <windows.h> 
 
#endif 
 
void UnpackARP(char *buff); 
void UnpackIP(char *buff); 
void UnpackTCP(char *buff); 
void UnpackUDP(char *buff); 
void UnpackICMP(char *buff); 
void UnpackIGMP(char *buff); 
 
int main(int argc, char **argv) 
{ 
        int sockfd, i; 
        char buff[2048]; 
         
      /* 
       *   監聽乙太網上的所有資料幀 
       */ 
        if(0 > (sockfd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL))))         
        { 
                perror("socket error!"); 
         
                exit(-1); 
        } 
 
        while(1) 
        { 
                memset(buff, 0, 2048); 
                 
                int n = recvfrom(sockfd, buff, 2048, 0, NULL, NULL); 
 
                printf("%s\n",buff); 
                 
                printf("開始解析資料包============\n"); 
                 
                printf("大小: %d\n", n); 
                 
                struct ethhdr *eth = (struct ethhdr*)buff; 
                 
                char *nextStack = buff + sizeof(struct ethhdr); 
                 
                int protocol = ntohs(eth->h_proto); 
                switch(protocol)  
                { 
                        case ETH_P_IP: 
                                UnpackIP(nextStack); 
                                break; 
                         
                        case ETH_P_ARP: 
                                UnpackARP(nextStack); 
                                break; 
                } 
                 
                printf("解析結束=================\n\n"); 
        } 
 
        return 0; 
} 
 
void getAddress(long saddr, char *str)  
{ 
        sprintf(str, "%d.%d.%d.%d",                         \ 
                        ((unsigned char*)&saddr)[0],         \ 
                        ((unsigned char*)&saddr)[1],         \ 
                        ((unsigned char*)&saddr)[2],         \ 
                        ((unsigned char*)&saddr)[3]); 
} 
 
void UnpackARP(char *buff)  
{ 
        printf("ARP資料包\n"); 
} 
 
void UnpackIP(char *buff)  
{ 
        struct iphdr *ip = (struct iphdr*)buff; 
        char *nextStack = buff + sizeof(struct iphdr); 
        int protocol = ip->protocol; 
        char data[20]; 
 
        getAddress(ip->saddr, data); 
        printf("來源ip %s\n", data); 
         
        bzero(data, sizeof(data)); 
 
        getAddress(ip->daddr, data); 
        printf("目標ip %s\n", data); 
 
        switch(protocol) 
         { 
                case 0x06: 
                        UnpackTCP(nextStack); 
                        break; 
 
                case 0x17: 
                        UnpackUDP(nextStack); 
                        break; 
                 
                case 0x01: 
                        UnpackICMP(nextStack); 
                        break; 
 
                case 0x02: 
                        UnpackIGMP(nextStack); 
                        break; 
 
                default: 
                        printf("unknown protocol\n"); 
                        break; 
        } 
} 
 
void UnpackTCP(char *buff)  
{ 
        struct tcphdr *tcp = (struct tcphdr*)buff; 
         
        printf("傳輸層協議:tcp\n"); 
     
        printf("來源埠:%d\n", ntohs(tcp->source)); 
        printf("目標埠:%d\n", ntohs(tcp->dest)); 
} 
 
void UnpackUDP(char *buff)  
{ 
        struct udphdr *udp = (struct udphdr*)buff; 
         
        printf("傳輸層協議:udp\n"); 
     
        printf("來源埠:%d\n", ntohs(udp->source)); 
        printf("目的埠:%d\n", ntohs(udp->dest)); 
} 
 
void UnpackICMP(char *buff)  
{ 
        printf("ICMP資料包\n");         
} 
 
void UnpackIGMP(char *buff)  
{ 
        printf("IGMP資料包\n"); 
} 

12.3.2 抓取乙太網上的所有資料幀,匹配 HTTP 協議併發送 TCP RST

程式碼來源:我的 Github - https://github.com/PikapBai/sniffer_cmpHTTP_sendTCP

13 參考資料

  1. 套接字 - 百度百科 - https://baike.baidu.com/item/套接字/9637606?fromtitle=socket&fromid=281150&fr=aladdin

  2. RAW SOCKET - 百度百科 - https://baike.baidu.com/item/RAW SOCKET/995623?fromtitle=原始套接字&fromid=23692610&fr=aladdin#ref_[1]_4263346

  3. 原始套接字簡介 - chengqiuming - https://blog.csdn.net/chengqiuming/article/details/89577351

  4. 原始套接字概述 - anton_99 - https://blog.csdn.net/anton_99/article/details/95646879?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param

  5. Linux 原始套接字抓取底層報文 - 2603898260 - https://blog.csdn.net/s2603898260/article/details/85020006?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param

  6. Linux-C TCP 簡單例子 - nanfeibuyi - https://blog.csdn.net/nanfeibuyi/article/details/88540144

  7. 【Linux網路程式設計】socket程式設計“網路位元組順序”和“主機位元組順序” - qq_20553613 - https://blog.csdn.net/qq_20553613/article/details/86385271

  8. 網路位元組序 - 百度百科 - https://baike.baidu.com/item/網路位元組序/12610557?fr=aladdin

  9. 位元組序(大小端)理解 - sunflower_della - https://blog.csdn.net/sunflower_della/article/details/90439935

  10. 理解大小端位元組序 - fan-yuan - https://www.cnblogs.com/fan-yuan/p/10406315.html

  11. linux網路程式設計之TCP/IP的TCP socket通訊過程(含例項程式碼) - 知乎 - linux伺服器開發專欄 - https://zhuanlan.zhihu.com/p/148739946

  12. Linux C Socket UDP程式設計詳解及例項分享 - 知乎 - linux伺服器開發專欄 - https://zhuanlan.zhihu.com/p/131402832

  13. Linux-C UDP簡單例子 - nanfeibuyi - https://blog.csdn.net/nanfeibuyi/article/details/88540233

  14. 《圖解 TCP/IP》(第 5 版)[日]竹下隆史 /[日]村山公保/ [日]荒井透 / [日]苅田幸雄

  15. 淺談linux下原始套接字 SOCK_RAW 的內幕及其應用 - 知乎 - linux伺服器開發專欄 - https://zhuanlan.zhihu.com/p/254912774

  16. GitHub - zhouyingjiu - https://github.com/zouyingjiu/sniffer