簡單的說,首先須要在這兩個進程之間創建一個 Unix 域套接字接口做爲消息傳遞的通道( Linux 系統上使用 socketpair 函數能夠很方面便的創建起傳遞通道),而後發送進程調用 sendmsg 向通道發送一個特殊的消息,內核將對這個消息作特殊處理,從而將打開的描述符傳遞到接收進程。linux
而後接收方調用 recvmsg 從通道接收消息,從而獲得打開的描述符。然而實際操做起來並不像看起來那樣單純。web
先來看幾個注意點:windows
1 須要注意的是傳遞描述符並非傳遞一個 int 型的描述符編號,而是在接收進程中建立一個新的描述符,而且在內核的文件表中,它與發送進程發送的描述符指向相同的項。數組
2 在進程之間能夠傳遞任意類型的描述符,好比能夠是 pipe , open , mkfifo 或 socket , accept 等函數返回的描述符,而不限於套接字。socket
3 一個描述符在傳遞過程當中(從調用 sendmsg 發送到調用 recvmsg 接收),內核會將其標記爲「在飛行中」( in flight )。在這段時間內,即便發送方試圖關閉該描述符,內核仍會爲接收進程保持打開狀態。發送描述符會使其引用計數加 1 。函數
4 描述符是經過輔助數據發送的(結構體 msghdr 的 msg_control 成員),在發送和接收描述符時,老是發送至少 1 個字節的數據,即便這個數據沒有任何實際意義。不然當接收返回 0 時,接收方將不能區分這意味着「沒有數據」(但輔助數據可能有套接字)仍是「文件結束符」。ui
5 具體實現時, msghdr 的 msg_control 緩衝區必須與 cmghdr 結構對齊,能夠看到後面代碼的實現使用了一個 union 結構來保證這一點。spa
[cpp]view plaincopy.net
struct msghdr {unix
void *msg_name;
socklen_t msg_namelen;
struct iovec *msg_iov;
size_t msg_iovlen;
void *msg_control;
size_t msg_controllen;
int msg_flags;
};
結構成員能夠分爲下面的四組,這樣看起來就清晰多了:
1 套接口地址成員 msg_name 與 msg_namelen ;
只有當通道是數據報套接口時才須要; msg_name 指向要發送或是接收信息的套接口地址。 msg_namelen 指明瞭這個套接口地址的長度。
msg_name 在調用 recvmsg 時指向接收地址,在調用 sendmsg 時指向目的地址。注意, msg_name 定義爲一個 (void *) 數據類型,所以並不須要將套接口地址顯示轉換爲 (struct sockaddr *) 。
2 I/O 向量引用 msg_iov 與 msg_iovlen
它是實際的數據緩衝區,從下面的代碼能看到,咱們的 1 個字節就交給了它;這個 msg_iovlen 是 msg_iov 的個數,不是什麼長度。
msg_iov 成員指向一個 struct iovec 數組, iovc 結構體在 sys/uio.h 頭文件定義,它沒有什麼特別的。
[cpp]view plaincopy
struct iovec {
ptr_t iov_base; /* Starting address */
size_t iov_len; /* Length in bytes */
};
有了 iovec ,就可使用 readv 和 writev 函數在一次函數調用中讀取或是寫入多個緩衝區,顯然比屢次 read , write 更有效率。 readv 和 writev 的函數原型以下:
[cpp]view plaincopy
#include <sys/uio.h>
int readv( int fd, const struct iovec *vector, int count);
int writev( int fd, const struct iovec *vector, int count);
3 附屬數據緩衝區成員 msg_control 與 msg_controllen ,描述符就是經過它發送的,後面將會看到, msg_control 指向附屬數據緩衝區,而 msg_controllen 指明瞭緩衝區大小。
4 接收信息標記位 msg_flags ;忽略
輪到 cmsghdr 結構了,附屬信息能夠包括若干個單獨的附屬數據對象。在每個對象以前都有一個 struct cmsghdr 結構。頭部以後是填充字節,而後是對象自己。最後,附屬數據對象以後,下一個 cmsghdr 以前也許要有更多的填充字節。
[cpp]view plaincopy
struct cmsghdr {
socklen_t cmsg_len;
int cmsg_level;
int cmsg_type;
/* u_char cmsg_data[]; */
};
cmsg_len 附屬數據的字節數,這包含結構頭的尺寸,這個值是由 CMSG_LEN() 宏計算的;
cmsg_level 代表了原始的協議級別 ( 例如, SOL_SOCKET) ;
cmsg_type 代表了控制信息類型 ( 例如, SCM_RIGHTS ,附屬數據對象是文件描述符; SCM_CREDENTIALS ,附屬數據對象是一個包含證書信息的結構 ) ;
被註釋的 cmsg_data 用來指明實際的附屬數據的位置,幫助理解。
對於 cmsg_level 和 cmsg_type ,當下咱們只關心 SOL_SOCKET 和 SCM_RIGHTS 。
#include <sys/socket.h>
struct cmsghdr *CMSG_FIRSTHDR(struct msghdr *msgh);
struct cmsghdr *CMSG_NXTHDR(struct msghdr *msgh, struct cmsghdr *cmsg);
size_t CMSG_ALIGN(size_t length);
size_t CMSG_SPACE(size_t length);
size_t CMSG_LEN(size_t length);
void *CMSG_DATA(struct cmsghdr *cmsg);
CMSG_LEN() 宏
輸入參數:附屬數據緩衝區中的對象大小;
計算 cmsghdr 頭結構加上附屬數據大小,包括必要的對其字段,這個值用來設置 cmsghdr 對象的 cmsg_len 成員。
CMSG_SPACE() 宏
輸入參數:附屬數據緩衝區中的對象大小;
計算 cmsghdr 頭結構加上附屬數據大小,幷包括對其字段和可能的結尾填充字符,注意 CMSG_LEN() 值並不包括可能的結尾填充字符。 CMSG_SPACE() 宏對於肯定所需的緩衝區尺寸是十分有用的。
注意若是在緩衝區中有多個附屬數據,必定要同時添加多個 CMSG_SPACE() 宏調用來獲得所需的總空間。
下面的例子反映了兩者的區別:
[cpp]view plaincopy
printf( "CMSG_SPACE(sizeof(short))=%d/n" , CMSG_SPACE( sizeof ( short ))); // 返回16
printf( "CMSG_LEN(sizeof(short))=%d/n" , CMSG_LEN( sizeof ( short ))); // 返回14
CMSG_DATA() 宏
輸入參數:指向 cmsghdr 結構的指針 ;
返回跟隨在頭部以及填充字節以後的附屬數據的第一個字節 ( 若是存在 ) 的地址,好比傳遞描述符時,代碼將是以下的形式:
[cpp]view plaincopy
struct cmsgptr *cmptr;
. . .
int fd = *( int )CMSG_DATA(cmptr); // 發送:(int *)CMSG_DATA(cmptr) = fd;
CMSG_FIRSTHDR() 宏
輸入參數:指向 struct msghdr 結構的指針;
返回指向附屬數據緩衝區內的第一個附屬對象的 struct cmsghdr 指針。若是不存在附屬數據對象則返回的指針值爲 NULL 。
CMSG_NXTHDR() 宏
輸入參數:指向 struct msghdr 結構的指針,指向當前 struct cmsghdr 的指針;
這個用於返回下一個附屬數據對象的 struct cmsghdr 指針,若是沒有下一個附屬數據對象,這個宏就會返回 NULL 。
經過這兩個宏能夠很容易遍歷全部的附屬數據,像下面的形式:
[cpp]view plaincopy
struct msghdr msgh;
struct cmsghdr *cmsg;
for (cmsg = CMSG_FIRSTHDR(&msgh); cmsg != NULL;
cmsg = CMSG_NXTHDR(&msgh,cmsg) {
// 獲得了cmmsg,就能經過CMSG_DATA()宏取得輔助數據了
[cpp]view plaincopy
#include <sys/types.h>
#include <sys/socket.h>
int sendmsg( int s, const struct msghdr *msg, unsigned int flags);
int recvmsg( int s, struct msghdr *msg, unsigned int flags);
兩者的參數說明以下:
s, 套接字通道,對於 sendmsg 是發送套接字,對於 recvmsg 則對應於接收套接字;
msg ,信息頭結構指針;
flags , 可選的標記位, 這與 send 或是 sendto 函數調用的標記相同。
函數的返回值爲實際發送 / 接收的字節數。不然返回 -1 代表發生了錯誤。
具體參考 APUE 的高級 I/O 部分,介紹的很詳細。
好了準備工做已經作完了,下面就準備進入正題。