Linux網絡編程API函數初步剖析linux
今天咱們來分析一下前幾篇博文中提到的網絡編程中幾個核心的API,探究一下當咱們調用每一個API時,內核中具體作了哪些準備和初始化工做。golang
一、socket(family,type,protocol)編程
當咱們在開發網絡應用程序時,使用該系統調用來建立一個套接字。該API所作的工做以下所示:數組
該系統調用主要完成兩個任務:「建立套接字」和「爲套接字綁定文件句柄」。服務器
socket{}<include/linux/net.h>結構定義以下:網絡
struct socket { socket_state state; //socket狀態 unsigned long flags; //標識,如SOCK_ASYNC_NOSAPCE const struct proto_ops *ops; //協議特定的socket操做集 struct fasync_struct *fasync_list; //異步喚醒隊列 struct file *file; //指向文件的指針 struct sock *sk; //指向下一層中的sock結構 wait_queue_head_t wait; //等待在這個socket上的任務列表 short type; //數據包的類型 };
在建立socket套接字時,就是要完成ops、file和sk等這些成員的初始化。數據結構
1). 建立套接字:sock_create()架構
根據family參數值在全局數組struct net_proto_family net_families[]裏找到咱們所指定的地址簇。不一樣類型的地址簇都有一個struct net_proto_family{}類型的對象,例如咱們常見的IPv4的inet_family_ops,IPv6的inet6_family_ops,X25協議的ax25_family_ops等。在內核是初始化時,這些模塊會在本身的初始化函數內部調用sock_register()接口將各自的地址簇對象註冊到net_families[]數組裏。異步
咱們分析的焦點集中在IPv4協議簇,即inet_family_ops對象上。重點是inet_create函數,該函數的主要任務就是建立一個socket套接字,並對其中相關結構體成員進行必要的初始化。至於它建立套接字時的依據和原理等到咱們講協議棧時你們就明白了,這裏主要是讓你們對其流程執行流程有個感性的把握。socket
sock_alloc()函數中咱們建立一個struct socket{}類型的對象,假如叫作A,將socket()系統調用的第二參數type字段賦值給A->type。
在inet_create()函數中,咱們根據type的值,在全局數組 struct inet_protosw inetsw[]裏找到咱們對應的協議轉換開關。而inetsw[]數組是在inet_init()函數裏被初始化的:
其中inetsw_array[]是一個比較重要的數據結構,定義在af_inet.c文件中:
根據type的值,就能夠肯定struct socket{}->ops,究竟是inet_stream_ops、inet_dgram_ops或者inet_sockraw_ops。而後,對應地,就以tcp_prot、udp_prot或raw_prot爲輸入參數,實例化一個struct sock{}對象sk=sk_alloc()。緊接着創建socket{}和sock{}的關聯,最後將socket()系統調用的第三個參數protocol付給sock{}對象中的屬性sk_protocol。
看不懂彆着急,我說過,這裏只是給你們梳理總體流程,等到咱們講了協議棧章節,而後再回頭看本篇,就感受這些東西就過小兒科了。
2). 爲套接字綁定文件句柄:sock_map_fd()
咱們都知道網絡套接字也是一種系統IO,因此不可避免的要與文件系統打交道。每一個套接字都對應一個已打開的文件標識符,因此在套接字初始化完成後,就要將其和本地一個惟一的文件標識符關聯起來,即創建socket{}和file{}之間的關聯關係。
二、bind (sockfd, sockaddr, addrlen)
該系統調用在內核中的執行過程以下:
重點是socket->ops->bind()回調接口。咱們如今已經知道了,針對IPv4而言,這裏的ops無非就是inet_stream_ops、inet_dgram_ops或inet_sockraw_ops對象。碰巧的是,這三個對象中的bind函數指針均指向inet_bind()函數。只有原始套接字的狀況,這裏會去調用raw_prot對象的bind回調函數,即raw_bind()。
三、listen(sockfd, backlog)
這裏咱們能夠看到面向無鏈接的套接字和原始套接字是不用listen的,只有流式套接字纔有效。
四、connect(sockfd, sockaddr, addrlen)
從這幅圖中咱們確實看到,connect()系統調用不但能夠面向鏈接的套接字,也可用於無鏈接及原始套接字。
五、accept(sockfd, sockaddr, addrlen)
一樣地,咱們看到只有面向鏈接的流式套接字調用accept()纔有意義。最終調用的是tcp_prot對象的accept成員函數。
須要C/C++ Linux高級服務器架構師學習資料加羣812855908(包括C/C++,Linux,golang技術,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協程,DPDK,ffmpeg等)
Linux網絡編程數據收發的API流程分析
只要把數據在協議棧中的流動線路和脈絡弄清楚了,關於協議棧的實現部分,理解起來就輕鬆多了。在網絡編程章節的數據接收過程當中,咱們主要介紹過read()、recv()、recvfrom()還有一個recvmsg()沒介紹到,今天咱們就來看一下這幾個API函數到底有什麼差異。
數據接收
在接收數據的過程,主要分兩個階段:BOTTOM-HALF和TOP-HALF。 BOTTOM-HALF:
當從網卡驅動收到數據包後即進入BOTTOM-HALF階段,在這裏要根據以太幀頭部中的類型字段來肯定上層承載的具體協議類型,如IP,或ARP、RARP等。IP報文的處理函數一般交付給ip_recv()函數來處理,而後數據進入網絡層,具體流程: 若是該數據包是發給本機的通常調用ip_local_deliver()函數,若是是須要本機轉發給出去的,而且本機也開啓了轉發功能,那麼就會調用ip_forward()函數。 在這裏咱們看到了Netfilter的身影,很久沒看到它了,仍是有些親切。你們能夠結合這幅圖回頭再理解一下Netfilter和協議棧的關係。 BOTTOM-HALF最後將收到的skb填充到socket套接字的接收隊列裏,參見下圖。
TOP-HALF:
緊承BOTTOM-HALF階段,該階段的主要任務就是從接收隊列裏拿出一個skb而後將其傳遞到用戶空間去,以下:
能夠看出,這幾個函數的內部最終都統一到了一塊兒:__sock_recvmsg()。
數據發送
一樣的,數據發送也分兩個階段,對照接收的狀況,發送數據時確定也存在一個發送隊列,這樣想就對了。前面關於發送數據包時咱們介紹過的API有write()、send()、sendto()還有一個sendmsg()沒介紹到。 TOP-HALF以下:
BOTTOM-HALF以下所示:
通過這麼一份探索,咱們對這幾個數據收發的API至少理解的要比別人深入些了吧。