進程間傳遞描述符一 - sparkliang的專欄 - 博客頻道 - CSDN.NET

進程間傳遞描述符一 每一個進程都擁有本身獨立的進程空間,這使得描述符在進程之間的傳遞變得有點複雜,這個屬於高級進程間通訊的內容,下面就來講說。順便把 Linux 和 Windows 平臺都講講。

Linux 下的描述符傳遞 Linux 系統系下,子進程會自動繼承父進程已打開的描述符,實際應用中,可能父進程須要向子進程傳遞「後打開的描述符」,或者子進程須要向父進程傳遞;或者兩個進程多是無關的,顯然這須要一套傳遞機制。

簡單的說,首先須要在這兩個進程之間創建一個 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

msghdr 和 cmsghdr 結構體 上面說過,描述符是經過結構體 msghdr 的 msg_control 成員送的,所以在繼續向下進行以前,有必要了解一下 msghdr 和 cmsghdr 結構體,先來看看 msghdr 。

[cpp]view plaincopy.net

  1. struct msghdr {unix

  2. void *msg_name;

  3. socklen_t msg_namelen;

  4. struct iovec *msg_iov;

  5. size_t msg_iovlen;

  6. void *msg_control;

  7. size_t msg_controllen;

  8. int msg_flags;

  9. };

結構成員能夠分爲下面的四組,這樣看起來就清晰多了:

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

  1. struct iovec {

  2. ptr_t iov_base; /* Starting address */

  3. size_t iov_len; /* Length in bytes */

  4. };

有了 iovec ,就可使用 readv 和 writev 函數在一次函數調用中讀取或是寫入多個緩衝區,顯然比屢次 read , write 更有效率。 readv 和 writev 的函數原型以下:

[cpp]view plaincopy

  1. #include <sys/uio.h>

  2. int readv( int fd, const struct iovec *vector, int count);

  3. 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

  1. struct cmsghdr {

  2. socklen_t cmsg_len;

  3. int cmsg_level;

  4. int cmsg_type;

  5. /* u_char cmsg_data[]; */

  6. };

cmsg_len 附屬數據的字節數,這包含結構頭的尺寸,這個值是由 CMSG_LEN() 宏計算的;

cmsg_level 代表了原始的協議級別 ( 例如, SOL_SOCKET) ;

cmsg_type 代表了控制信息類型 ( 例如, SCM_RIGHTS ,附屬數據對象是文件描述符; SCM_CREDENTIALS ,附屬數據對象是一個包含證書信息的結構 ) ;

被註釋的 cmsg_data 用來指明實際的附屬數據的位置,幫助理解。

對於 cmsg_level 和 cmsg_type ,當下咱們只關心 SOL_SOCKET 和 SCM_RIGHTS 。

msghdr 和 cmsghdr 輔助宏 這些結構仍是挺複雜的, Linux 系統提供了一系列的宏來簡化咱們的工做,這些宏能夠在不一樣的 UNIX 平臺之間進行移植。這些宏是由 cmsg(3) 的 man 手冊頁描述的,先來認識一下:

#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

  1. printf( "CMSG_SPACE(sizeof(short))=%d/n" , CMSG_SPACE( sizeof ( short ))); // 返回16

  2. printf( "CMSG_LEN(sizeof(short))=%d/n" , CMSG_LEN( sizeof ( short ))); // 返回14

CMSG_DATA() 宏

輸入參數:指向 cmsghdr 結構的指針 ;

返回跟隨在頭部以及填充字節以後的附屬數據的第一個字節 ( 若是存在 ) 的地址,好比傳遞描述符時,代碼將是以下的形式:

[cpp]view plaincopy

  1. struct cmsgptr *cmptr;

  2. . . .

  3. 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

  1. struct msghdr msgh;

  2. struct cmsghdr *cmsg;

  3. for (cmsg = CMSG_FIRSTHDR(&msgh); cmsg != NULL;

  4. cmsg = CMSG_NXTHDR(&msgh,cmsg) {

  5. // 獲得了cmmsg,就能經過CMSG_DATA()宏取得輔助數據了

函數 sendmsg 和 recvmsg 函數原型以下:

[cpp]view plaincopy

  1. #include <sys/types.h>

  2. #include <sys/socket.h>

  3. int sendmsg( int s, const struct msghdr *msg, unsigned int flags);

  4. int recvmsg( int s, struct msghdr *msg, unsigned int flags);

兩者的參數說明以下:

s, 套接字通道,對於 sendmsg 是發送套接字,對於 recvmsg 則對應於接收套接字;

msg ,信息頭結構指針;

flags , 可選的標記位, 這與 send 或是 sendto 函數調用的標記相同。

函數的返回值爲實際發送 / 接收的字節數。不然返回 -1 代表發生了錯誤。

具體參考 APUE 的高級 I/O 部分,介紹的很詳細。

好了準備工做已經作完了,下面就準備進入正題。

相關文章
相關標籤/搜索