Linux高效能伺服器程式設計——程序間 傳遞檔案描述符
阿新 • • 發佈:2018-12-15
問題的提出
一:fork()之後,父程序中開啟的檔案描述符,在子程序中仍然處於開啟狀態。所以從父程序——》子程序(傳遞檔案描述符)很方便。值得注意的是傳遞一個檔案描述符並不是傳遞一個檔案描述符的值。
二:那麼怎樣吧子程序中開啟的檔案描述符傳遞給父程序呢?通俗的說:也就是,如何在兩個不相干的程序之間傳遞檔案描述符呢?
解決方案
在Linux底下,可以利用UNIX域底下的socket在程序間傳遞特殊的輔助資料,以實現檔案描述符的傳遞。
程式碼實現
#include<sys/socket.h> #include<sys/param.h> #include<fcntl.h> #include<stdio.h> #include<unistd.h> #include<stdlib.h> #include<assert.h> #include<string.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); unsigned char* CMSG_DATA(struct cmsghdr *cmsg); struct cmsghdr{ socklen_t cmsg_len; int cmsg_level; int cmsg_type; }; */ static const int CONTROL_LEN = CMSG_LEN(sizeof(int)); /*傳送檔案描述符,fd引數是用來傳遞資訊的UNIX域的socket,fd_to_send引數是待發送的檔案描述符*/ void send_fd(int fd,int fd_to_send) { struct iovec iov[1]; struct msghdr msg; char buf[0]; iov[0].iov_base = buf; iov[1].iov_len = 1; msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_iov = iov; msg.msg_iovlen = 1; cmsghdr cm; cm.cmsg_len = CONTROL_LEN; cm.cmsg_level = SOL_SOCKET; cm.cmsg_type = SCM_RIGHTS; *(int *)CMSG_DATA(& cm) = fd_to_send; msg.msg_control = &cm;//設定輔助資料 msg.msg_controllen = CONTROL_LEN; sendmsg(fd,&msg,0); } int recv_fd(int fd) { struct iovec iov[1]; struct msghdr msg; char buf[0]; iov[0].iov_base = buf; iov[1].iov_len = 1; msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_iov = iov; msg.msg_iovlen = 1; cmsghdr cm; msg.msg_control = &cm; msg.msg_controllen = CONTROL_LEN; recvmsg( fd,&msg,0); int fd_to_read = *(int *)CMSG_DATA(&cm); return fd_to_read; } int main() { int pipefd[2]; int fd_to_pass = 0;//檔案描述符的傳遞值 /*建立父,子程序間的管道,檔案描述符 pipefd[0]和pipefd[1]*/ int ret = socketpair( PF_UNIX,SOCK_DGRAM,0,pipefd);//管道函式 assert(ret != -1); pid_t pid = fork(); assert(pid >= 0); if(pid == 0)//子程序 { close(pipefd[0]);//????? fd_to_pass = open("text.txt",O_RDWR,0666); /*子程序通過管道將檔案描述符傳送到父程序。如果檔案text開啟失敗則子程序將標準輸入檔案描述符傳送到父程序*/ send_fd( pipefd[1],(fd_to_pass > 0)?fd_to_pass:0); close(fd_to_pass); exit(0); } close(pipefd[1]); fd_to_pass = recv_fd(pipefd[0]);//父程序從管道接收目標檔案描述符 char buff[128]; memset(buff,0,sizeof(buff)); read(fd_to_pass,buff,127);//讀目標檔案描述符,以驗證其有效性 printf("I Got fd: %d and Data: %s\n",fd_to_pass,buff); close(fd_to_pass); }
結果呈現
函式分析
一:通用I/O函式 recvmsg 和 sendmsg
①函式標頭檔案及函式原型
標頭檔案 | #include<sys/socket.h> |
接受函式 | ssize_t recvmsg( int sockfd , struct msghdr * msg , int flags); |
傳送資料 | ssize_t sendmsg( int sockfd , struct msghdr * msg , int flags); |
②引數介紹
sockfd | 接受或傳送端的套接字(檔案描述符) |
msg | 用來指定 接收來源 或者 傳送目的的地址。 |
flags |
|
③struct msghdr結構分析
struct msghdr{ void *msg_name; socklen_t msg_namelen; struct iovec *msg_iov; int msg_iovlen; void *msg_control; socklen_t msg_controllen; int msg_flags; }
msg_name和msg_namelen | 這兩個成員主要用於 套接字未連線的時間(主要是未連線的UDP套接字),用來指定接受來源 或者 傳送目的的地址。套接字地址及其地址大小,類似recvfrom和sendto的第二個和第三個引數。 對於已經連線的套接字,則可以直接將msg_name設定為NULL 和 msg_namelen在recv中為0,在send中為一個值-結果引數。 |
msg_iov | 用於指定資料緩衝區陣列,即iovec陣列。 iovc結構如下: 這樣兩者組合成了一個二維陣列,並且每個一個數組的長度不是固定的 |
msg_iovlen | 和msg_iov一起用於指定資料緩衝區陣列 |
msg_control 和 msg_controllen | 設定輔助資料的位置和大小,輔助資料(ancillary data)也叫控制資訊(control information) |
flags和msg_flags | 在這兩個函式中有不同的傳遞方式。這裡不做詳細介紹 |