自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

深入 Linux 內(nèi)核理解 socket 的本質(zhì)

開發(fā) 網(wǎng)絡(luò) Linux
本文將從一個初學(xué)者的角度開始聊起,讓大家了解 Socket 是什么以及它的原理和內(nèi)核實現(xiàn)。

本文將從一個初學(xué)者的角度開始聊起,讓大家了解 Socket 是什么以及它的原理和內(nèi)核實現(xiàn)。

一、Socket 的概念

 Socket 就如同我們?nèi)粘I钪械牟孱^與插座的連接關(guān)系。在網(wǎng)絡(luò)編程中,Socket 是一種實現(xiàn)網(wǎng)絡(luò)通信的接口或機制。 想象一下,插頭插入插座后,電流得以流通,實現(xiàn)了能量的傳遞。而在網(wǎng)絡(luò)世界里,當(dāng)一個程序使用 Socket 與另一臺機子建立“連接”時,就如同插頭成功插入了插座,數(shù)據(jù)能夠在兩者之間進行流通和交換。

例如,當(dāng)我們在網(wǎng)上聊天時,發(fā)送方的程序通過 Socket 將消息發(fā)送出去,接收方的程序通過對應(yīng)的 Socket 接收這些消息。又比如在下載文件時,下載程序通過 Socket 與提供文件的服務(wù)器建立連接,從而能夠獲取到所需的文件數(shù)據(jù)。

二、Socket 的使用場景

我們想要將數(shù)據(jù)從 A 電腦的某個進程發(fā)到 B 電腦的某個進程。如果需要確保數(shù)據(jù)能發(fā)給對方,就選可靠的 TCP 協(xié)議;如果數(shù)據(jù)丟了也沒關(guān)系,就選擇不可靠的 UDP 協(xié)議。初學(xué)者一般首選 TCP。

這時就需要用 socket 進行編程,首先創(chuàng)建關(guān)于 TCP 的 socket:

#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
 
int main() {
    int sock_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (sock_fd == -1) {
        std::cerr << "Failed to create socket" << std::endl;
        return 1;
    }
 
    // 后續(xù)代碼...
 
    return 0;
}

這個方法會返回 sock_fd,它是 socket 文件的句柄。

對于服務(wù)端,得到 sock_fd 后,依次執(zhí)行 bind()、listen()、accept() 方法,等待客戶端的連接請求;對于客戶端,得到 sock_fd 后,執(zhí)行 connect() 方法向服務(wù)端發(fā)起建立連接的請求,此時會發(fā)生 TCP 三次握手。

連接建立完成后,客戶端可以執(zhí)行 send() 方法發(fā)送消息,服務(wù)端可以執(zhí)行 recv() 方法接收消息,反之亦然。

三、Socket 的設(shè)計

現(xiàn)在我們拋開socket,重新設(shè)計一個內(nèi)核網(wǎng)絡(luò)傳輸功能。我們想要將數(shù)據(jù)從 A 電腦的某個進程發(fā)到 B 電腦的某個進程,從操作上來看,就是發(fā)數(shù)據(jù)給遠端和從遠端接收數(shù)據(jù),也就是寫數(shù)據(jù)和讀數(shù)據(jù)。

但這里有兩個問題:

  • 接收端和發(fā)送端可能不止一個,因此需要用 IP 和端口做區(qū)分,IP 用來定位是哪臺電腦,端口用來定位是這臺電腦上的哪個進程。
  • 發(fā)送端和接收端的傳輸方式有很多區(qū)別,如可靠的 TCP 協(xié)議、不可靠的 UDP 協(xié)議,甚至還需要支持基于 icmp 協(xié)議的 ping 命令。

為了支持這些功能,需要定義一個數(shù)據(jù)結(jié)構(gòu) sock,在 sock 里加入 IP 和端口字段。這些協(xié)議雖然各不相同,但有一些功能相似的地方,可以將不同的協(xié)議當(dāng)成不同的對象類(或結(jié)構(gòu)體),將公共的部分提取出來,通過“繼承”的方式復(fù)用功能。

于是,定義了一些數(shù)據(jù)結(jié)構(gòu):

sock 是最基礎(chǔ)的結(jié)構(gòu),維護一些任何協(xié)議都有可能會用到的收發(fā)數(shù)據(jù)緩沖區(qū)。

在 Linux 內(nèi)核 2.6 相關(guān)的源碼中,sock 結(jié)構(gòu)體的定義可能類似于:

struct sock {
    // 相關(guān)字段
    struct sk_buff_head sk_receive_queue; // 接收數(shù)據(jù)緩沖區(qū)
    struct sk_buff_head sk_write_queue;  // 發(fā)送數(shù)據(jù)緩沖區(qū)
    // 其他可能的字段
};

inet_sock 特指用了網(wǎng)絡(luò)傳輸功能的 sock,在 sock 的基礎(chǔ)上還加入了 TTL、端口、IP 地址這些跟網(wǎng)絡(luò)傳輸相關(guān)的字段信息。比如 Unix domain socket,用于本機進程之間的通信,直接讀寫文件,不需要經(jīng)過網(wǎng)絡(luò)協(xié)議棧。

可能的定義:

struct inet_sock {
    struct sock sk; // 繼承自 sock
    __be32 port;    // 端口
    __be32 saddr;   // IP 地址
    // 其他相關(guān)字段
};

inet_connection_sock 是指面向連接的 sock,在 inet_sock 的基礎(chǔ)上加入面向連接的協(xié)議里相關(guān)字段,比如 accept 隊列、數(shù)據(jù)包分片大小、握手失敗重試次數(shù)等。雖然現(xiàn)在提到面向連接的協(xié)議就是指 TCP,但設(shè)計上 Linux 需要支持擴展其他面向連接的新協(xié)議。

例如:

struct inet_connection_sock {
    struct inet_sock inet; // 繼承自 inet_sock
    struct request_sock_queue accept_queue; // accept 隊列
    // 其他相關(guān)字段
};

tcp_sock 就是正兒八經(jīng)的 TCP 協(xié)議專用的 sock 結(jié)構(gòu),在 inet_connection_sock 基礎(chǔ)上還加入了 TCP 特有的滑動窗口、擁塞避免等功能。同樣 UDP 協(xié)議也會有一個專用的數(shù)據(jù)結(jié)構(gòu),叫 udp_sock。

大概如下:

struct tcp_sock {
    struct inet_connection_sock icsk; // 繼承自 inet_connection_sock
    // TCP 特有的字段,如滑動窗口、擁塞避免等相關(guān)字段
};

有了這套數(shù)據(jù)結(jié)構(gòu),將它跟硬件網(wǎng)卡對接一下,就實現(xiàn)了網(wǎng)絡(luò)傳輸?shù)墓δ堋?/p>

四、提供 Socket 層

由于這里面的代碼復(fù)雜,還操作了網(wǎng)卡硬件,需要較高的操作系統(tǒng)權(quán)限,再考慮到性能和安全,于是將它放在操作系統(tǒng)內(nèi)核里。

為了讓用戶空間的應(yīng)用程序使用這部分功能,將這部分功能抽象成簡單的接口,將內(nèi)核的 sock 封裝成文件。創(chuàng)建 sock 的同時也創(chuàng)建一個文件,文件有個文件描述符 fd,通過它可以唯一確定是哪個 sock。將fd暴露給用戶,用戶就可以像操作文件句柄那樣去操作這個 sock 。

struct file{
    //文件相關(guān)的字段
    .....
    void *private_data; //指向sock
}

創(chuàng)建socket時,其實就是創(chuàng)建了一個文件結(jié)構(gòu)體,并將private_data字段指向sock。

有了 sock_fd 句柄后,提供了一些接口,如 send()、recv()、bind()、listen()、connect() 等,這些就是 socket 提供出來的接口。

所以說,socket 其實就是個代碼庫或接口層,它介于內(nèi)核和應(yīng)用程序之間,提供了一堆接口,讓我們?nèi)ナ褂脙?nèi)核功能,本質(zhì)上就是一堆高度封裝過的接口。

我們平時寫的應(yīng)用程序里代碼里雖然用了socket實現(xiàn)了收發(fā)數(shù)據(jù)包的功能,但其 實真正執(zhí)行網(wǎng)絡(luò)通信功能的,不是應(yīng)用程序,而是linux內(nèi)核。

在操作系統(tǒng)內(nèi)核空間里,實現(xiàn)網(wǎng)絡(luò)傳輸功能的結(jié)構(gòu)是sock,基于不同的協(xié)議和應(yīng)用場景,會被泛化為各種類型的xx_sock,它們結(jié)合硬件,共同實現(xiàn)了網(wǎng)絡(luò)傳輸功能。為了將這部分功能暴露給用戶空間的應(yīng)用程序使用,于是引入了socket層,同時將sock嵌入到文件系統(tǒng)的框架里,sock就變成了一個特殊的文件,用戶就可以在用戶空間使用文件句柄,也就是socket_fd來操作內(nèi)核sock的網(wǎng)絡(luò)傳輸能力。

五、Socket 如何實現(xiàn)網(wǎng)絡(luò)通信

以最常用的 TCP 協(xié)議為例,實現(xiàn)網(wǎng)絡(luò)傳輸功能分為建立連接和數(shù)據(jù)傳輸兩個階段。

1. 建立連接

在客戶端,執(zhí)行 socket 提供的 connect(sockfd, "ip:port") 方法時,會通過 sockfd 句柄找到對應(yīng)的文件,再根據(jù)文件里的信息指向內(nèi)核的 sock 結(jié)構(gòu),通過這個 sock 結(jié)構(gòu)主動發(fā)起三次握手。

在服務(wù)端,握手次數(shù)還沒達到“三次”的連接叫半連接,完成好三次握手的連接叫全連接,它們分別會用半連接隊列和全連接隊列來存放,這兩個隊列會在執(zhí)行 listen() 方法的時候創(chuàng)建好。當(dāng)服務(wù)端執(zhí)行 accept() 方法時,就會從全連接隊列里拿出一條全連接。

雖然都叫隊列,但半連接隊列其實是個哈希表,而全連接隊列其實是個鏈表。

在 Linux 內(nèi)核 2.6 版本的源碼中,相關(guān)的代碼實現(xiàn)可能位于網(wǎng)絡(luò)子系統(tǒng)的部分。例如,建立連接的過程可能涉及到 tcp_connect() 等函數(shù)。

2. 數(shù)據(jù)傳輸

為了實現(xiàn)發(fā)送和接收數(shù)據(jù)的功能,sock 結(jié)構(gòu)體里帶了一個發(fā)送緩沖區(qū)和一個接收緩沖區(qū),其實就是個鏈表,上面掛著一個個準(zhǔn)備要發(fā)送或接收的數(shù)據(jù)。

當(dāng)應(yīng)用執(zhí)行 send() 方法發(fā)送數(shù)據(jù)時,會通過 sock_fd 句柄找到對應(yīng)的文件,根據(jù)文件指向的 sock 結(jié)構(gòu),找到這個 sock 結(jié)構(gòu)里帶的發(fā)送緩沖區(qū),將數(shù)據(jù)放到發(fā)送緩沖區(qū),然后結(jié)束流程,內(nèi)核看心情決定什么時候?qū)⑦@份數(shù)據(jù)發(fā)送出去。

接收數(shù)據(jù)流程也類似,當(dāng)數(shù)據(jù)送到 Linux 內(nèi)核后,先放在接收緩沖區(qū)中,等待應(yīng)用程序執(zhí)行 recv() 方法來拿。

當(dāng)應(yīng)用進程執(zhí)行 recv() 方法嘗試獲?。ㄗ枞麍鼍跋拢┙邮站彌_區(qū)的數(shù)據(jù)時,如果有數(shù)據(jù),取走就好;如果沒數(shù)據(jù),就會將自己的進程信息注冊到這個 sock 用的等待隊列里,然后進程休眠。如果這時候有數(shù)據(jù)從遠端發(fā)過來了,數(shù)據(jù)進入到接收緩沖區(qū)時,內(nèi)核就會取出 sock 的等待隊列里的進程,喚醒進程來取數(shù)據(jù)。

當(dāng)多個進程通過 fork 的方式 listen 了同一個 socket_fd,在內(nèi)核它們都是同一個 sock,多個進程執(zhí)行 listen() 之后,都會將自身的進程信息注冊到這個 socket_fd 對應(yīng)的內(nèi)核 sock 的等待隊列中。在 Linux 2.6 以前,會喚醒等待隊列里的所有進程,但最后其實只有一個進程會處理這個連接請求,其他進程又重新進入休眠,會消耗一定的資源,這就是驚群效應(yīng)。在 Linux 2.6 之后,只會喚醒等待隊列里的其中一個進程,這個問題被修復(fù)了。

服務(wù)端 listen 的時候,那么多數(shù)據(jù)到一個 socket 怎么區(qū)分多個客戶端的?以 TCP 為例,服務(wù)端執(zhí)行 listen 方法后,會等待客戶端發(fā)送數(shù)據(jù)來??蛻舳税l(fā)來的數(shù)據(jù)包上會有源 IP 地址和端口,以及目的 IP 地址和端口,這四個元素構(gòu)成一個四元組,可以用于唯一標(biāo)記一個客戶端。服務(wù)端會創(chuàng)建一個新的內(nèi)核 sock,并用四元組生成一個 hash key,將它放入到一個 hash 表中。下次再有消息進來的時候,通過消息自帶的四元組生成 hash key 再到這個 hash 表 里重新取出對應(yīng)的 sock 就好了。

六、Socket 怎么實現(xiàn)“繼承”

Linux 內(nèi)核是 C 語言實現(xiàn)的,而 C 語言沒有類也沒有繼承的特性,是通過結(jié)構(gòu)體里的內(nèi)存是連續(xù)的這一特點來實現(xiàn)“繼承”的效果。將要繼承的“父類”,放到結(jié)構(gòu)體的第一位,然后通過結(jié)構(gòu)體名的長度來強行截取內(nèi)存,這樣就能轉(zhuǎn)換結(jié)構(gòu)體,從而實現(xiàn)類似“繼承”的效果。

例如:

struct tcp_sock {
    /* inet_connection_sock has to be the first member of tcp_sock */
    struct inet_connection_sock inet_conn;
    // 其他字段
};
 
struct inet_connection_sock {
    /* inet_sock has to be the first member! */
    struct inet_sock icsk_inet;
    // 其他字段
};
 
// sock 轉(zhuǎn)為 tcp_sock
static inline struct tcp_sock *tcp_sk(const struct sock *sk) {
    return (struct tcp_sock *)sk;
}

七、總結(jié)

socket 中文套接字,可理解為一套用于連接的數(shù)字。

sock 在內(nèi)核,socket_fd 在用戶空間,socket 層介于內(nèi)核和用戶空間之間。

在操作系統(tǒng)內(nèi)核空間里,實現(xiàn)網(wǎng)絡(luò)傳輸功能的結(jié)構(gòu)是 sock,基于不同的協(xié)議和應(yīng)用場景,會被泛化為各種類型的 xx_sock,它們結(jié)合硬件,共同實現(xiàn)了網(wǎng)絡(luò)傳輸功能。為了將這部分功能暴露給用戶空間的應(yīng)用程序使用,于是引入了 socket 層,同時將 sock 嵌入到文件系統(tǒng)的框架里,sock 就變成了一個特殊的文件,用戶就可以在用戶空間使用文件句柄,也就是 socket_fd 來操作內(nèi)核 sock 的網(wǎng)絡(luò)傳輸能力。

服務(wù)端可以通過四元組來區(qū)分多個客戶端。

內(nèi)核通過 C 語言“結(jié)構(gòu)體里的內(nèi)存是連續(xù)的”這一特點實現(xiàn)了類似繼承的效果。

責(zé)任編輯:趙寧寧 來源: 一安未來
相關(guān)推薦

2021-09-10 06:50:03

Node.jsSocket端口

2025-04-18 04:05:00

2021-05-19 07:56:26

Linux內(nèi)核搶占

2012-11-22 10:11:16

LispLisp教程

2017-01-12 19:34:58

2021-12-09 08:09:31

Linux內(nèi)核臟頁

2021-07-26 07:47:36

數(shù)據(jù)庫

2021-07-02 06:54:44

Linux內(nèi)核主調(diào)度器

2021-07-05 06:51:45

Linux內(nèi)核調(diào)度器

2021-07-20 08:02:41

Linux進程睡眠

2020-09-28 08:44:17

Linux內(nèi)核

2024-07-05 10:59:26

2021-08-31 10:32:11

LinuxPage Cache命令

2014-03-12 10:19:54

iOS對象

2023-02-10 08:11:43

Linux系統(tǒng)調(diào)用

2022-11-09 08:12:07

2020-11-20 07:55:55

Linux內(nèi)核映射

2019-07-08 20:00:35

Linux內(nèi)核模塊

2023-11-24 11:24:16

Linux系統(tǒng)

2024-12-03 15:15:22

點贊
收藏

51CTO技術(shù)棧公眾號