99精品伊人亚洲|最近国产中文炮友|九草在线视频支援|AV网站大全最新|美女黄片免费观看|国产精品资源视频|精彩无码视频一区|91大神在线后入|伊人终合在线播放|久草综合久久中文

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

深度剖析Linux socket

Linux愛好者 ? 來源:奇伢云存儲(chǔ) ? 作者:奇伢 ? 2021-10-11 15:55 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

socket fd 長(zhǎng)什么樣子?

什么是 socket fd ?粗糙的來講,就是網(wǎng)絡(luò) fd,比如我們最常見的 C/S 客戶端服務(wù)端的編程模式,就是網(wǎng)絡(luò)通信的一種方式。撇開底層和協(xié)議細(xì)節(jié),網(wǎng)絡(luò)通信和文件讀寫從 接口上有本質(zhì)區(qū)別嗎?

其實(shí)沒啥區(qū)別,不就是讀過來和寫過去嘛,簡(jiǎn)稱 IO 。

我們先看一下 socket fd 是什么樣子的?隨便找了個(gè)進(jìn)程

root@ubuntu:~#ll/proc/1583/fd
total0
lrwx------1rootroot64Jul1912:377->socket:[18892]
lrwx------1rootroot64Jul1912:378->socket:[18893]

這里我們看到 fd 7、8 都是一個(gè) socket fd,名字:socket:[18892]

整數(shù)句柄后面一般會(huì)跟一些信息,用于幫助我們了解這個(gè) fd 是什么。舉個(gè)例子,如果是文件 fd,那么箭頭后面一般是路徑名稱?,F(xiàn)在拆解一下這個(gè)名字:

  • socket :標(biāo)識(shí)這是一個(gè) socket 類型的 fd
  • [18892] :這個(gè)是一個(gè) inode 號(hào),能夠唯一標(biāo)識(shí)本機(jī)的一條網(wǎng)絡(luò)連接;

思考下,這個(gè) inode 號(hào),還能再哪里能看到呢?

在 proc 的 net 目錄下,因?yàn)槲疫@個(gè)是一個(gè)走 tcp 的服務(wù)端,所以我們看一下 /proc/net/tcp 文件。這個(gè)文件里面能看到所有的 tcp 連接的信息。

root@ubuntu:~#grep-i"18892"/proc/net/tcp
18:00000000:1F9300000000:00000A00000000:0000000000:000000000000000000188921ffff880197fba58010000100
root@ubuntu:~#grep-i"18893"/proc/net/tcp
28:00000000:1F7C00000000:00000A00000000:0000000000:000000000000000000188931ffff880197fbad0010000100

知識(shí)點(diǎn)又來了,/proc/net/tcp 這個(gè)文件記錄了 tcp 連接的信息,這份信息是非常有用的。包含了 TCP 連接的地址(16進(jìn)制顯示),inode 的信息,連接的狀態(tài)等等。

socket fd 是什么?

環(huán)境聲明

Linux 內(nèi)核版本 4.19

為了方便,如果沒特意說明協(xié)議,默認(rèn) TCP 協(xié)議;

socket 可能你還沒反應(yīng)過來,中文名:套接字 是不是更熟悉點(diǎn)。Linux 網(wǎng)絡(luò)編程甚至可以叫做套接字編程。

有些概念你必須捋一捋 。我們思考幾個(gè)小問題:

socket 跟 tcp/ip 有什么區(qū)別?

就不該把這兩個(gè)東西放在一起比較討論,就不是一個(gè)東西。tcp/ip 是網(wǎng)絡(luò)協(xié)議棧,socket 是操作系統(tǒng)為了方便網(wǎng)絡(luò)編程而設(shè)計(jì)出來的編程接口而已。

理論基礎(chǔ)是各種網(wǎng)絡(luò)協(xié)議,協(xié)議棧呀,啥的。但是如果你要進(jìn)行網(wǎng)絡(luò)編程,落到實(shí)處,對(duì)程序猿來講就是 socket 編程。

對(duì)于網(wǎng)絡(luò)的操作,由 socket 體現(xiàn)為 open -> read/write ->close 這樣的編程模式,這個(gè)統(tǒng)一到文件的一種形式。

socket 的 open 就是 socket(int domain, int type, int protocol) ,和文件一樣,都是獲取一個(gè)句柄。

網(wǎng)絡(luò)抽象層次

網(wǎng)絡(luò)模型一般會(huì)對(duì)應(yīng)到兩種:

  • 完美理論的 OSI 七層模型
  • 現(xiàn)實(shí)應(yīng)用的 5 層模型;

對(duì)應(yīng)關(guān)系如下圖(取自 Unix 套接字編程)

不同層次做不同的事情,不斷的封裝,不斷的站在巨人的肩膀上,你將能做的更多。

今天,奇伢剖析的只聚焦在套接字這一層,這是程序猿摸得到的一層,位于所有網(wǎng)絡(luò)協(xié)議之上的一層封裝,網(wǎng)絡(luò)編程又叫套接字編程,這并不是空穴來風(fēng)。

套接字,是內(nèi)核對(duì)賊復(fù)雜的網(wǎng)絡(luò)協(xié)議棧的 API 封裝,使得程序猿能夠用極簡(jiǎn)的姿勢(shì)進(jìn)行網(wǎng)絡(luò)編程。比如寫一個(gè)基于 Tcp 的 C/S 的網(wǎng)絡(luò)程序,需要用到啥?我們大概暢想下:

  1. 客戶端和服務(wù)端都用 socket 調(diào)用創(chuàng)建套接字;
  2. 服務(wù)端用 bind 綁定監(jiān)聽地址,用 listen 把套接字轉(zhuǎn)化為監(jiān)聽套接字,用 accept 撈取一個(gè)客戶端來的連接;
  3. 客戶端用 connect 進(jìn)行建連,用 write/read 進(jìn)行網(wǎng)絡(luò) IO;

程序猿用著好簡(jiǎn)單!因?yàn)閮?nèi)核把事扛了。

socket fd 的類型

上面我們提到了套接字,這是我們網(wǎng)絡(luò)編程的主體,套接字由 socket() 系統(tǒng)調(diào)用創(chuàng)建,但你可知套接字其實(shí)可分為兩種類型,監(jiān)聽套接字和普通套接字。而監(jiān)聽套接字是由 listen() 把 socket fd 轉(zhuǎn)化而成。

1監(jiān)聽套接字

對(duì)于監(jiān)聽套接字,不走數(shù)據(jù)流,只管理連接的建立。accept 將從全連接隊(duì)列獲取一個(gè)創(chuàng)建好的 socket( 3 次握手完成),對(duì)于監(jiān)聽套接字的可讀事件就是全連接隊(duì)列非空。對(duì)于監(jiān)聽套接字,我們只在乎可讀事件。

2普通套接字

普通套接字就是走數(shù)據(jù)流的,也就是網(wǎng)絡(luò) IO,針對(duì)普通套接字我們關(guān)注可讀可寫事件。在說 socket 的可讀可寫事件之前,我們先捋順套接字的讀寫大概是什么樣子吧。

套接字層是內(nèi)核提供給程序員用來網(wǎng)絡(luò)編程的,程序猿讀寫都是針對(duì)套接字而言,那么 write( socketfd, /* 參數(shù) */)read( socketfd, /* 參數(shù) */) 都會(huì)發(fā)生什么呢?

  • write 數(shù)據(jù)到 socketfd,大部分情況下,數(shù)據(jù)寫到 socket 的內(nèi)存 buffer,就結(jié)束了,并沒有發(fā)送到對(duì)端網(wǎng)絡(luò)(異步發(fā)送);
  • read socketfd 的數(shù)據(jù),也只是從 socket 的 內(nèi)存 buffer 里讀數(shù)據(jù)而已,而不是從網(wǎng)卡讀(雖然數(shù)據(jù)是從網(wǎng)卡一層層遞上來的);

也就是說,程序猿而言,是跟 socket 打交道,內(nèi)核屏蔽了底層的細(xì)節(jié)。

那說回來 socket 的可讀可寫事件就很容易理解了。

  • socketfd 可讀:其實(shí)就是 socket buffer 內(nèi)有數(shù)據(jù)(超過閾值 SO_RCLOWAT );
  • socketfd 可寫:就是 socket buffer 還有空間讓你寫(閾值 SO_SNDLOWAT );

sockfs 文件系統(tǒng)

socket fd 為什么能具備“文件”的語義,從而和 eventfd,ext2 fd 這樣的句柄一樣,統(tǒng)一提供對(duì)外 io 的樣子?

核心就是:sockfs ,這也是個(gè)文件系統(tǒng),只不過普通用戶看不見,這是只由內(nèi)核管理的文件系統(tǒng),位于 vfs 之下,為了封裝 socket 對(duì)上的文件語義。

//net/socket.c
staticint__initsock_init(void)
{
//注冊(cè)sockfs文件系統(tǒng)
err=register_filesystem(&sock_fs_type);
//內(nèi)核掛載
sock_mnt=kern_mount(&sock_fs_type);
}

其中最關(guān)鍵的是 sock_mnt 這個(gè)全局變量里面的超級(jí)塊的操作表 sockfs_ops 。

//net/socket.c
staticconststructsuper_operationssockfs_ops={
.alloc_inode=sock_alloc_inode,
.destroy_inode=sock_destroy_inode,
.statfs=simple_statfs,
};

這個(gè)是每個(gè)文件系統(tǒng)的核心函數(shù)表,如上指明了 inode 的分配規(guī)則(這里又將體現(xiàn)依次結(jié)構(gòu)體內(nèi)嵌組合+類型強(qiáng)轉(zhuǎn)的應(yīng)用)。

讀者朋友還記得 inode 和 ext4_inode_info 的關(guān)系嗎?在 Linux fd 究竟是什么?一文中有提到這個(gè):

46ab5d1e-29ad-11ec-82a8-dac502259ad0.png

inode 是 vfs 抽象的適配所有文件系統(tǒng)的結(jié)構(gòu)體,但分配其實(shí)是有下層具體文件系統(tǒng)分配出來的,以 ext4 文件系統(tǒng)來說,使用 ext4_alloc_inode 函數(shù)分配出 ext4_inode_info 這個(gè)大結(jié)構(gòu)體,然后返回的是 inode 的地址而已。

劃重點(diǎn):struct inode 內(nèi)嵌于具體文件系統(tǒng)的 “inode” 里,vfs 層使用的是 inode,ext4 層使用的是 ext4_inode_info ,不同層次通過地址的強(qiáng)制轉(zhuǎn)化類型來切換結(jié)構(gòu)體。

那么類似,sockfs 也是如此,sockfs 作為文件系統(tǒng),也有自己特色的 “inode”,這個(gè)類型就是 struct socket_alloc ,如下:

structsocket_alloc{
structsocketsocket;
structinodevfs_inode;
};

這個(gè)結(jié)構(gòu)體關(guān)聯(lián) socket 和 inode 兩個(gè)角色,是“文件”抽象的核心之一。分配 struct socket 結(jié)構(gòu)體其實(shí)是分配了 struct socket_alloc 結(jié)構(gòu)體,然后返回了 socket_alloc->socket 字段的地址而已。

471bd346-29ad-11ec-82a8-dac502259ad0.png

劃重點(diǎn):vfs 層用的時(shí)候給 inode 字段的地址,socket 層的時(shí)候給 socket 字段的地址。不同抽象層面對(duì)于同一個(gè)內(nèi)存塊的理解不同,強(qiáng)制轉(zhuǎn)化類型,然后各自使用

從文件的角度來看 socket,模塊如下:

477e53c2-29ad-11ec-82a8-dac502259ad0.png ?

回調(diào)喚醒的通用做法?

先鋪墊一個(gè)小知識(shí)點(diǎn):內(nèi)核里面有回調(diào)喚醒的實(shí)現(xiàn),里面有用到一種 wait queue 的做法,其實(shí)很簡(jiǎn)單的原理。

大白話原理:你要走可以,把聯(lián)系方式留下,我搞好之后通知你(調(diào)用你留下的函數(shù),傳入你留下的參數(shù))。

484e2cbe-29ad-11ec-82a8-dac502259ad0.png

拿 socket 來說,struct sock 里面就有個(gè)字段 sk_wq ,這是個(gè)表頭,就是用來掛接等待對(duì)象的。

誰會(huì)掛?

就以 epoll 池來說,epoll_ctl 注冊(cè) socket fd 的時(shí)候,就會(huì)掛一個(gè) wait 對(duì)象到 sk->sk_wq 里?;卣{(diào)參數(shù)為 ep_poll_callback ,參數(shù)為 epitem 。

這樣 epoll 給 socket 留下聯(lián)系方式了( wait 對(duì)象 ),socket 有啥事就可以隨時(shí)通知到 epoll 池了。

能有什么事?

socket 可讀可寫了唄。sk buffer 里面有數(shù)據(jù)可以讀,或者有空間可以寫了唄。對(duì)于監(jiān)聽類型的 socket,有新的連接了唄。epoll 監(jiān)聽的不就是這個(gè)嘛。

socket 編程 ?

服務(wù)端:

  1. socket( ) 創(chuàng)建出 socketfd;
  2. bind( ) 綁定一個(gè)端口(和客戶端約定好的知名端口號(hào));
  3. listen( ) 講套接字轉(zhuǎn)化成監(jiān)聽套接字;
  4. accept( ) 等待客戶端的建連請(qǐng)求;
  5. 建連之后 read/write 處理數(shù)據(jù)即可(一般和監(jiān)聽線程并發(fā));

客戶端:

  1. socket( ) 創(chuàng)建出 socketfd;
  2. connect( ) 向指定機(jī)器、端口發(fā)起建連請(qǐng)求;
  3. 建連之后,read/write 處理數(shù)據(jù);
4924baae-29ad-11ec-82a8-dac502259ad0.png

下面就幾個(gè)關(guān)鍵函數(shù)做個(gè)簡(jiǎn)要實(shí)現(xiàn)。

1socket 函數(shù)

定義原型:

#include
intsocket(intfamily,inttype,intprotocol)

簡(jiǎn)要跟蹤下內(nèi)部實(shí)現(xiàn)

socket 系統(tǒng)調(diào)用對(duì)應(yīng)了 __sys_socket 這個(gè)函數(shù)。這個(gè)函數(shù)主要做兩件事情:

  1. 第一件事:調(diào)用 socket_create 函數(shù)創(chuàng)建好 socket 相關(guān)的結(jié)構(gòu)體,主要是 struct socket ,還有與之關(guān)聯(lián)的 socket sock 結(jié)構(gòu),再往下就是具體網(wǎng)絡(luò)協(xié)議對(duì)應(yīng)的結(jié)構(gòu)體(旁白:這里實(shí)現(xiàn)細(xì)節(jié)過于復(fù)雜,不在文章主干,故略去 10 萬字);
  2. 第二件事:調(diào)用 sock_map_fd 函數(shù)創(chuàng)建好 struct file 這個(gè)結(jié)構(gòu)體,并與第一步創(chuàng)建出的 struct socket 關(guān)聯(lián)起來;

涉及的一些函數(shù)調(diào)用

__sys_socket
//創(chuàng)建structsocket結(jié)構(gòu)體
->sock_create
//創(chuàng)建structsocket結(jié)構(gòu),并且關(guān)聯(lián)特殊inode
->sock_alloc
//pf是根據(jù)family從net_families這個(gè)全局表中取出的操作函數(shù)表,用來創(chuàng)建具體網(wǎng)絡(luò)協(xié)議結(jié)構(gòu)的;
//比如IPv4對(duì)應(yīng)的family就是AF_INET,對(duì)應(yīng)的函數(shù)是inet_create
//在這里面會(huì)賦值sock->ops為對(duì)應(yīng)協(xié)議族的操作函數(shù)表(比如inet_stream_ops)
->pf->create
//structsock結(jié)構(gòu)體的創(chuàng)建(sk->sk_prot的賦值就在這里,比如tcp_prot)
->sk_alloc
//structsock結(jié)構(gòu)體的初始化(比如sk_receive_queue,sk_write_queue,sk_error_queue就是在這里初始化的)
//可讀寫的關(guān)鍵函數(shù)sock_def_readable,sock_def_write_space也是在這里賦值的
->sock_init_data
//創(chuàng)建structfile結(jié)構(gòu)體,并且關(guān)聯(lián)structsocket
->sock_map_fd

先說 socket 函數(shù):

  1. socket( ) 函數(shù)只負(fù)責(zé)創(chuàng)建出適配具體網(wǎng)絡(luò)協(xié)議的資源(內(nèi)存、結(jié)構(gòu)體、隊(duì)列等),并沒有和具體地址綁定;
  2. socket( ) 返回的是非負(fù)整數(shù)的 fd,與 struct file 對(duì)應(yīng),而 struct file 則與具體的 struct socket 關(guān)聯(lián),從而實(shí)現(xiàn)一切皆文件的封裝的一部分(另一部分 inode 的創(chuàng)建處理在 sock_alloc 的函數(shù)里體現(xiàn));

再簡(jiǎn)要說下內(nèi)部細(xì)節(jié):

sock_create 函數(shù)里,會(huì)根據(jù)協(xié)議族查找對(duì)應(yīng)的操作表,以 AF_INET 協(xié)議族舉例,pf->createinet_create ,主要做兩件事:

  1. sock->ops 按照協(xié)議類型賦值成具體的函數(shù)操作表,比如 tcp 的就是 inet_stream_ops ;
  2. 創(chuàng)建了 struct sock 對(duì)象,并且把 struct sock 初始化,并和 struct socket 進(jìn)行關(guān)聯(lián);

著重提一點(diǎn),sock_init_data 函數(shù)( net/core/sock.c )主要是初始化 struct sock 結(jié)構(gòu)體的,提兩點(diǎn)最關(guān)鍵的:

第一點(diǎn):接收隊(duì)列和發(fā)送隊(duì)列在這里初始化

  • sk_receive_queue:套接字接收到的數(shù)據(jù)(sk_buff 里面是純粹的用戶數(shù)據(jù)哦,沒有 header 啥信息);
  • sk_write_queue:套接字要發(fā)送的數(shù)據(jù);
  • sk_error_queue:掛接一些 pengding 的 error 信息;

第二點(diǎn):socket 的喚醒回調(diào)在這個(gè)地方設(shè)置

sk->sk_data_ready=sock_def_readable;
sk->sk_write_space=sock_def_write_space;

為什么這里很重要,因?yàn)檫@個(gè)跟 socket fd 可讀可寫的判斷邏輯,數(shù)據(jù)到了之后的喚醒路徑息息相關(guān)。簡(jiǎn)述下回調(diào)鏈路(以套接字層為主干,其他的流程簡(jiǎn)略描述):

sk->sk_data_ready(數(shù)據(jù)到了,該通知留下過聯(lián)系方式的人了)
tcp_v4_rcv(具體協(xié)議棧處理函數(shù))
軟中斷
硬中斷

再說下結(jié)構(gòu)體:

繼續(xù)說 struct sock ,這個(gè)對(duì)象有意思了,這個(gè)也是以組合的方式往下兼容的,同一個(gè)地址強(qiáng)轉(zhuǎn)類型得到不同層面的結(jié)構(gòu)體。原理就在于:他們是一塊連續(xù)的內(nèi)存空間,起始地址相同。

sock->inet_sock->inet_connection_sock->tcp_sock

示意圖:

49e286e2-29ad-11ec-82a8-dac502259ad0.png

小思考:struct socketstruct sock 是兩個(gè)不同的結(jié)構(gòu)體?

是的。這兩個(gè)是不同的結(jié)構(gòu)體。屬于套接字層的兩個(gè)維度的描述,一個(gè)面向上層,一個(gè)面向下層。

struct socket 在內(nèi)核的注釋為:

struct socket - general BSD socket

struct sock 在內(nèi)核的注釋為:

struct sock_common - minimal network layer representation of sockets

struct socket 是內(nèi)核抽象出的一個(gè)通用結(jié)構(gòu)體,主要作用是放置了一些跟 fs 相關(guān)的字段,而真正跟網(wǎng)絡(luò)通信相關(guān)的字段結(jié)構(gòu)體是 struct sock 。它們內(nèi)部有相互的指針,可以獲取到對(duì)方的地址。

struct socket 這個(gè)字段出生的時(shí)候其實(shí)就和一個(gè) inode 結(jié)構(gòu)體伴生出來的,由 socketfs 的 sock_alloc_inode 函數(shù)分配。

struct sock 這個(gè)結(jié)構(gòu)體是 socket 套階字核心的結(jié)構(gòu)(注意,還有個(gè)結(jié)構(gòu)是 struct socket,這兩個(gè)是不同的結(jié)構(gòu)體哦)。這個(gè)是對(duì)底下具體協(xié)議做的一層抽象封裝,比如在分配 struct sock 的時(shí)候,如果是 tcp 協(xié)議,那么 sk->sk_prot 會(huì)賦值為 tcp_prot ,udp 協(xié)議賦值的是 udp_prot ,之后的一系列協(xié)議解析和處理就是調(diào)用到對(duì)應(yīng)協(xié)議的回調(diào)函數(shù)。

小思考:socket fd 可以和文件一樣用 write(fd, /*xxxx*/ ) 這行的調(diào)用,為什么?

write(fd, /*xxxx*/) 進(jìn)到內(nèi)核首先是到 vfs 層,也就是調(diào)用到 vfs_write ,在這個(gè)里面首先獲取到 file 這個(gè)結(jié)構(gòu)體,然后調(diào)用下層注冊(cè)的回調(diào),比如 file->f_op->write_iterfile->f_op->write ,所以,關(guān)鍵在 file->f_op 這個(gè)字段,對(duì)吧?

現(xiàn)在的問題是,這個(gè)字段是啥呢?

這個(gè)字段在 file 結(jié)構(gòu)體生成的時(shí)候,根據(jù)你的“文件”類型賦值的,這個(gè)在之前文件系統(tǒng)章節(jié)提過這個(gè),比如 ext2 的文件,那么就是 ext2_file_operations ,socketfd 是 socket_file_ops。

vfs_write=>
->socket_file_ops(sockfs)
->ext2_file_operations(ext2)
->ext4_file_operations(ext4)
->eventfd_fops

可以看下 socket_file_ops 的定義:

staticconststructfile_operationssocket_file_ops={
.llseek=no_llseek,
.read_iter=sock_read_iter,
.write_iter=sock_write_iter,
.poll=sock_poll,
//...
}

所以,vfs_write 調(diào)用到的將是 sock_write_iter,而這個(gè)里面就是調(diào)用到 sock_sendmsg ,從而走到網(wǎng)絡(luò)相關(guān)的處理流程。

// sock_sendmsg 實(shí)際調(diào)用;
staticinlineintsock_sendmsg_nosec(structsocket*sock,structmsghdr*msg)
{
intret=sock->ops->sendmsg(sock,msg,msg_data_left(msg));
returnret;
}

還記得上面在 socket 初始化的時(shí)候 socket->opssock->sk_prot 兩個(gè)回調(diào)函數(shù)操作表的賦值嗎( tcp ):

  • socket->ops => inet_stream_ops
  • sock->sk_prot => tcp_prot

這樣從 vfs 進(jìn)來,轉(zhuǎn)接到具體的協(xié)議處理模塊去了。

2bind 函數(shù)

對(duì)應(yīng)內(nèi)核 __sys_bind 函數(shù),做的事情很簡(jiǎn)單:

  1. 先通過 fd 找到對(duì)應(yīng)的 struct socket 結(jié)構(gòu)體;
  2. 然后把 address 和 socket 綁定對(duì)應(yīng)起來(調(diào)用 sock->ops->bind 函數(shù));

tcp 連接的對(duì)應(yīng)的 bind 函數(shù)是 inet_bind,里面做的事情很簡(jiǎn)單,就是簡(jiǎn)單的查一下端口有沒有被占用,沒有被占用的話端口就賦值給 inet_sock->inet_sport 這個(gè)字段。

inet_sock 則是由 sk 強(qiáng)轉(zhuǎn)類型得到。

思考個(gè)小問題:在上面的圖中,bind 這個(gè)函數(shù)只在服務(wù)端用到?

為啥客戶端沒用這個(gè)函數(shù)呢?

其實(shí),客戶端也是可以用 bind 這個(gè)函數(shù),但是沒必要

理解下 bind 函數(shù)的作用:給這個(gè) socketfd 綁定地址(IP:Port)用的??蛻舳瞬恍枰且?yàn)椋喝绻麤]設(shè)置,內(nèi)核在建連的時(shí)候會(huì)自動(dòng)選一個(gè)臨時(shí)的端口號(hào)作為本次 TCP 連接的地址。一般客戶端也不在意端口號(hào),只要能和服務(wù)端正常通信就好,所以客戶端一般沒有 bind 調(diào)用。

服務(wù)端必須要用這個(gè)是因?yàn)榉?wù)端必須提前明確指定監(jiān)聽的 IP 和 Port (不然誰知道向哪里發(fā)起連接呢)。

3listen 函數(shù)

其實(shí) socket( ) 創(chuàng)建出來的套接字并無客戶端和服務(wù)端之分,是 listen 函數(shù)讓 socket 有了不一樣的屬性,成為監(jiān)聽套接字。

listen 系統(tǒng)調(diào)用主要做兩件事:

  1. 通過 fd 找到 struct socket 結(jié)構(gòu)體;
  2. 調(diào)用 sock->ops->listen 函數(shù)(對(duì)應(yīng) inet_listen );

inet_listen 做啥了??jī)?nèi)核注釋:

Move a socket into listening state.

簡(jiǎn)單看下 inet_listen 的實(shí)現(xiàn)功能:

  1. 檢查 socket 狀態(tài),類型,必須為流式套接字才能轉(zhuǎn)化成監(jiān)聽套接字;
  2. 調(diào)用 inet_csk_listen_start ;

inet_csk_listen_start 做啥了?

  1. 初始化請(qǐng)求隊(duì)列 icsk->icsk_accept_queue ;
  2. 套接字狀態(tài)設(shè)置成 TCP_LISTEN;
  3. 獲取到之前 bind 的端口,如果沒有設(shè)置,那么就會(huì)用個(gè)臨時(shí)的端口;
  4. 把監(jiān)聽套接字加入到全局 hash 表中;

劃重點(diǎn):套接字的轉(zhuǎn)變就在于此。

4accept 函數(shù)

inet_accept ( net/ipv4/af_inet.c )注釋:

Accept a pending connection. The TCP layer now gives BSD semantics.

這個(gè)主要是從隊(duì)列 icsk->icsk_accept_queue 中取請(qǐng)求,如果隊(duì)列為空,就看 socket 是否設(shè)置了非阻塞標(biāo)識(shí),非阻塞的就直接報(bào)錯(cuò) EAGAIN,否則阻塞線程等待。

所以,監(jiān)聽套接字的可讀事件是啥?

icsk_accept_queue 隊(duì)列非空。

這個(gè)隊(duì)列什么時(shí)候被填充的?

tcp_child_process
->tcp_rcv_state_process

這個(gè)也是底層網(wǎng)絡(luò)協(xié)議回調(diào)往上調(diào)用的,tcp 三次握手之后,建立好的連接就在一個(gè)隊(duì)列中 accept_queue ,隊(duì)列非空則為只讀。由 tcp 的協(xié)議棧往上調(diào)用,對(duì)應(yīng)到 socket 層,還是會(huì)調(diào)用到 sk->sk_data_ready 。

這里還是以 epoll 管理監(jiān)聽套接字來舉例。這個(gè)跟上面講的數(shù)據(jù)來了一樣,都是把掛接在 socket 本身上的 wait 對(duì)象進(jìn)行喚醒(調(diào)用回調(diào)),這樣就會(huì)到 ep_poll_callback ,ep_poll_callback 就會(huì)把監(jiān)聽套接字對(duì)應(yīng)的 ep_item 掛到 epoll 的 ready 隊(duì)列中,并且喚醒阻塞在 epoll_wait 的線程,從而實(shí)現(xiàn)了監(jiān)聽套接字的讀事件的觸發(fā)的流程。

5connect 函數(shù)

這個(gè)沒啥講的,就是由客戶端向服務(wù)端發(fā)起連接的時(shí)候調(diào)用,一般也和 epoll 配合不起來,略過。

句柄事件

深入剖析 epoll 篇 我們就提到過,epoll 池可以管理 socket fd ,用于監(jiān)聽 socket fd 的可讀,可寫事件。那么問題來了,socket fd 的可讀可寫事件分別是啥?代表了什么含義?

這個(gè)要把服務(wù)端的監(jiān)聽類型的 socket fd 和傳輸數(shù)據(jù)的 socket fd 分開來說。

監(jiān)聽類型的 fd:

  1. 有 client 建連,則觸發(fā)可讀事件;
  2. 句柄被 close ,則觸發(fā)可讀事件;

數(shù)據(jù)類型的 fd:

  1. sk buffer 有可讀的數(shù)據(jù),觸發(fā)可讀事件;
  2. sk buffer 有可寫的空間,觸發(fā)可寫事件;
  3. 句柄杯 close,連接關(guān)閉的時(shí)候,也是可讀的;

還有,如果 socket 之上有 pending 的 error 待處理,那么也會(huì)觸發(fā)可讀事件。

epoll 池怎么配合?

最后,我們?cè)倩貞浺幌?,epoll 池管理的 socket fd 是怎么及時(shí)觸發(fā)喚醒的呢?

換句話說,socket fd 數(shù)據(jù)就緒之后,怎么能及時(shí)的喚醒被阻塞在 epoll_wait 的線程?

還記得套接字 buffer 數(shù)據(jù)來了的時(shí)候的回調(diào)嗎?

調(diào)用的是 sk->sk_data_ready 這個(gè)函數(shù)指針,這個(gè)字段在 socket 初始化的時(shí)候被賦值為 sock_def_readable ,這個(gè)函數(shù)里面會(huì)依次調(diào)用所有掛接到 socket 的 wait 隊(duì)列的對(duì)象( 表頭:sk->sk_wq ),在這個(gè) wait 隊(duì)列中存在和 epoll 關(guān)聯(lián)的秘密。

回憶下,在 深入剖析 epoll 篇 提到,epoll_ctl 的時(shí)候,在把 socket fd 注冊(cè)進(jìn) epoll 池的時(shí)候,會(huì)把一個(gè) wait 對(duì)象掛接到這個(gè) socket 的 sk->sk_wq 中 ,回調(diào)函數(shù)就是 ep_poll_callback 。

這個(gè)wait 對(duì)象就是數(shù)據(jù)就緒時(shí)候的聯(lián)系方式,這樣把 socket 數(shù)據(jù)就緒的流程和 epoll 關(guān)聯(lián)上了。

也就是說,sk->sk_data_ready 會(huì)調(diào)用到 ep_poll_callback ,ep_poll_callback 這個(gè)函數(shù)處理很簡(jiǎn)單,做兩件事情:

  1. 把 socket 對(duì)應(yīng)的 ep_item 掛接到就緒隊(duì)列中;
  2. 把阻塞在 epoll_wait 的線程(Linux 進(jìn)程和線程本質(zhì)無區(qū)別)投遞到就緒隊(duì)列中,等待內(nèi)核調(diào)度(也就是所謂的喚醒,實(shí)現(xiàn)機(jī)制很簡(jiǎn)單,就是 epoll_wait 阻塞切走之前,會(huì)創(chuàng)建出一個(gè) wait 對(duì)象,掛到 epoll 池上,后續(xù)喚醒就能以此為依據(jù));
ep_poll_callback
sk->sk_data_ready
tcp_v4_rcv(具體協(xié)議棧處理函數(shù))
軟中斷
硬中斷

數(shù)據(jù)來了

最后用一張簡(jiǎn)要的圖展示結(jié)構(gòu)體之間的關(guān)系:

總結(jié)

  1. vfs 下有一個(gè) sockfs 的抽象層,是把 socket 抽象成“文件” fd 的關(guān)鍵之一;
  2. socket fd 能夠和文件 IO 一樣,使用 write/read 等系統(tǒng)調(diào)用,就得益于 vfs 幫你做的轉(zhuǎn)接。那 socket() 函數(shù)調(diào)用是不是就和 open 文件 fd 的效果是一樣的呀?是的,都是構(gòu)建并關(guān)聯(lián)各種內(nèi)核結(jié)構(gòu)體;
  3. epoll 池能管理 socketfd,因?yàn)?socket fd 實(shí)現(xiàn) poll 接口;
  4. epoll_ctl 注冊(cè) socket fd 的時(shí)候,掛了個(gè) wait 對(duì)象在 socket 的 sk_wq 里,所以數(shù)據(jù)就緒的時(shí)候,socket 才能通知到 epoll;
  5. epoll_wait 切走的時(shí)候掛了個(gè) wait 對(duì)象在 epoll 上,所以 epoll 就緒的時(shí)候,才能有機(jī)會(huì)喚醒阻塞的線程;
  6. 套接字由 socket() 創(chuàng)建出來,客戶端和服務(wù)端都是,listen() 調(diào)用可以把套接字轉(zhuǎn)化成監(jiān)聽套接字;
  7. 監(jiān)聽套接字一般只監(jiān)聽可讀事件,關(guān)注連接的建立,普通套接字走數(shù)據(jù)流,關(guān)注數(shù)據(jù)的讀寫事件;
編輯:jq
聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 數(shù)據(jù)
    +關(guān)注

    關(guān)注

    8

    文章

    7250

    瀏覽量

    91625
  • Linux
    +關(guān)注

    關(guān)注

    87

    文章

    11499

    瀏覽量

    213346
  • Socket
    +關(guān)注

    關(guān)注

    1

    文章

    212

    瀏覽量

    35753

原文標(biāo)題:深入理解 Linux socket

文章出處:【微信號(hào):LinuxHub,微信公眾號(hào):Linux愛好者】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評(píng)論

    相關(guān)推薦
    熱點(diǎn)推薦

    C語言深度剖析

    C語言深度剖析——一本關(guān)于C語言學(xué)習(xí)的教程,里面包含C語言編寫規(guī)范,各種變量指針用法等。以含金量勇敢挑戰(zhàn)國(guó)內(nèi)外同類書籍
    發(fā)表于 08-14 11:36

    c語言深度剖析

    c語言深度剖析
    發(fā)表于 04-02 09:12

    陳正沖《C語言深度剖析

    陳正沖編寫的《C語言深度剖析》,挺經(jīng)典,剛來論壇,多多指教~~
    發(fā)表于 08-17 12:06

    linux內(nèi)核深度剖析,另附有光盤資料

    linux內(nèi)核深度剖析,對(duì)于想學(xué)linux內(nèi)核的人來說,絕對(duì)值得一看,另附有光盤資料。
    發(fā)表于 01-15 21:25

    【資料分享】C語言深度剖析

    C語言深度剖析
    發(fā)表于 10-16 15:16

    C語言深度剖析

    C語言深度剖析
    發(fā)表于 08-25 09:08

    C語言深度剖析

    C語言深度剖析[完整版].pdfC語言深度剖析[完整版].pdf (919.58 KB )
    發(fā)表于 03-19 05:11

    Linux+Socket編程

    本內(nèi)容詳細(xì)講述了Linux+Socket編程技巧指南,適合所有學(xué)習(xí)編程的廣大用戶使用
    發(fā)表于 06-10 11:19 ?0次下載
    <b class='flag-5'>Linux+Socket</b>編程

    C語言深度剖析

    C語言深度剖析。
    發(fā)表于 05-05 17:40 ?7次下載

    Linux下基于Socket的網(wǎng)絡(luò)編程

    linux開發(fā)編程教程資料——Linux下基于Socket的網(wǎng)絡(luò)編程,感興趣的小伙伴們可以看一看。
    發(fā)表于 08-23 16:23 ?0次下載

    Linux-socket網(wǎng)絡(luò)編程

    linux開發(fā)編程教程資料——Linux-socket網(wǎng)絡(luò)編程,感興趣的小伙伴們可以看一看。
    發(fā)表于 08-23 16:23 ?0次下載

    LinuxSocket網(wǎng)絡(luò)編程

    linux開發(fā)編程教程資料——LinuxSocket網(wǎng)絡(luò)編程,感興趣的小伙伴們可以看一看。
    發(fā)表于 08-23 16:23 ?0次下載

    linux-socket網(wǎng)絡(luò)編程詳解

    linux開發(fā)編程教程資料——linux-socket網(wǎng)絡(luò)編程詳解,感興趣的小伙伴們可以看一看。
    發(fā)表于 08-23 16:23 ?0次下載

    linux-網(wǎng)絡(luò)編程-socket編程

    linux開發(fā)編程教程資料——linux-網(wǎng)絡(luò)編程-socket編程,感興趣的小伙伴們可以看一看。
    發(fā)表于 08-23 16:23 ?0次下載

    C語言深度剖析

    C語言深度剖析
    發(fā)表于 12-20 22:50 ?0次下載