Linux環境:C編程之進程傳遞文件描述符

在多進程編程時,會用到進程間傳遞文件描述符的情況,例如進程池編程通信時,在主進程中建立連接,然後把建立後的socket轉給子進程來處理任務。接下來看一下如何在進程間傳遞文件描述符。

需要注意的是:

文件描述符的編號在進程間獨立,每個進程都維護一個進程文件打開表,所以同一文件描述符在不同進程中有不同的含義。因此,傳遞文件描述符,其實是在傳遞文件描述符對應的指向該文件的引用。

具體流程:通過socketpair建立unix域套接字,然後利用這對套接字進行通信,利用sendmsgrecvmsg函數傳遞文件描述符對應文件的輔助信息。

socketpair函數

  • 作用:sockpair函數用來建立一對無名套接字,用於全雙工通信,和無名管道相比,無名套接字可以全雙工通信,並且支持主機和網絡通信,功能更加強大。
  • 函數原型:int socketpair(int d, int type, int protocol, int sv[2]);
  • 參數:前三個參數和socket函數一致,分別是通信域,類型和協議,第四個參數是傳出參數,傳出創建的一對描述符。
  • 注意事項:
  1. 可以用於全雙工通信,每一個套接字既可以讀也可以寫。例如,可以往sv[0]中寫,從sv[1]中讀;或者從sv[1]中寫,從sv[0]中讀;
  2. 如果往一個套接字(如sv[0])中寫入後,再從該套接字讀時會阻塞,只能在另一個套接字中(sv[1])上讀成功;
  3. 讀、寫操作可以位於同一個進程,也可以分別位於不同的進程,如父子進程。如果是父子進程時,一般會功能分離,一個進程用來讀,一個用來寫。因爲文件描述副sv[0]和sv[1]是進程共享的,所以讀的進程要關閉寫描述符, 反之,寫的進程關閉讀描述符。

sendmsg和recvmsg函數

  • 函數原型:
    size_t sendmsg (int s, const struct msghdr *msg, int flags);
    int recvmsg(int s, struct msghdr *msg, unsigned int flags);
  • 作用:
    這一對發送函數可以替代之前的send,recv,sendto和recvfrom,向另一個套接口發送信息。
    參數:第一個參數是要發信息或收信息的套接字描述符;第二個參數是發送的信息結構體頭指針;第三個參數是標誌位,和之前的sendto/recvfrom等函數一致。
  • 返回值:返回實際接受或發送的字節數,失敗返回-1

struct msghdr結構體

要理解sendmsg和recvmsg,需要弄清楚struct msghdr結構體。該結構體定義如下:

//有這麼多成員,所以才能實現多種功能
struct msghdr {

/*下面兩組是套接字的地址指針和長度,用來替代sendto和recvfrom中指明接收方或發送方的地址參數*/
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;
}

sendmsg和recvmsg能實現這麼多功能就是因爲這個結構體很複雜,內容很多,不過我們使用的時候只需要初始化能用到的信息就可以了。
需要注意的是,如果想利用它傳遞輔助信息,比如文件描述符,必須攜帶至少一個字節的真實數據,也就是iov指針指向的緩衝區要有數據,iovlen至少是1。

struct cmsghdr結構體

我們要傳遞的輔助信息需要是一個struct cmsghdr結構體類型保存,然後用msg_control指向它。該結構體的定義如下:

struct cmsghdr {
 socklen_t cmsg_len; /* 該變量的長度,包含結構體頭部 */
 int cmsg_level; /* 原始協議,比如SOL_SOCKET代表套接字API層*/
 int cmsg_type; /* 控制信息類型,比如SCM_RIGHTS代表了文件描述符 */
 /* followed by unsigned char cmsg_data[]; */
 };

該結構體比較複雜所以有一系列的宏來對其進行操作

  • CMSG_FIRSTHDR() :接收一個msghdr指針,返回指向msghdr的控制信息緩衝區的第一個指針,前提是msghdr已經申請了緩衝區空間。
  • CMSG_DATA() :接收一個cmsghdr指針,返回cmsghdr的數據區的第一個字節的指針。
  • CMSG_LEN() :接受我們希望放置在附屬數據緩衝區中的對象尺寸作爲輸入參數。返回cmsghdr頭結構加上所需要的填充字符的字節長度。這個值用來設置cmsghdr對象的cmsg_len成員。

通過把要傳遞的文件描述符放在data區,內核就可以幫我們完成轉換。

recvmsg接收時和上述一致。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章