揭開網絡編程常見API的面紗【上】
2012-11-23 23:05:35
Linux網絡編程API函數初步剖析
今天我們來分析一下前幾篇博文中提到的網絡編程中幾個核心的API,探究一下當我們調用每個API時,內核中具體做了哪些準備和初始化工作。
1、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套接字,並對其中相關結構體成員進行必要的初始化。至於它創建套接字時的依據和原理等到我們講協議棧時大家就明白了,這裏主要是讓大家對其流程執行流程有個感性的把握。
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{}之間的關聯關係。
2、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()。
3、listen(sockfd, backlog)
這裏我們可以看到面向無連接的套接字和原始套接字是不用listen的,只有流式套接字纔有效。
從這幅圖中我們確實看到,connect()系統調用不但可以面向連接的套接字,也可用於無連接及原始套接字。
5、accept(sockfd, sockaddr, addrlen)
同樣地,我們看到只有面向連接的流式套接字調用accept()纔有意義。最終調用的是tcp_prot對象的accept成員函數。
本篇主要進一步分析了網絡編程中常見的幾個API函數內部的調用流程,一方面可以使大家對這些API的有了更深的認識,不是僅僅停留在形而上的層面;另一方面爲後面分析協議棧的實現原理,奠定堅實的基礎。
未完,待續…