UNIX域套接字可以在同一台主機上各進程之間傳遞文件描述符。
下面先來看兩個函數:
#include <sys/types.h> #include <sys/socket.h> ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags); ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);它們與sendto 和 recvfrom 函數相似,只不過可以傳輸更復雜的數據結構,不僅可以傳輸一般數據,還可以傳輸額外的數據,即文件描述符。下面來看結構體msghdr :
struct msghdr { void *msg_name; /* optional address */ socklen_t msg_namelen; /* size of address */ struct iovec *msg_iov; /* scatter/gather array */ size_t msg_iovlen; /* # elements in msg_iov */ void *msg_control; /* ancillary data, see below */ size_t msg_controllen; /* ancillary data buffer len */ int msg_flags; /* flags on received message */ };1、msg_name :即對等方的地址指針,不關心時設為NULL即可;
2、msg_namelen:地址長度,不關心時設置為0即可;
3、msg_iov:是結構體iovec 的指針。
struct iovec { void *iov_base; /* Starting address */ size_t iov_len; /* Number of bytes to transfer */ };成員iov_base 可以認為是傳輸正常數據時的buf,iov_len 是buf 的大小。
4、msg_iovlen:當有n個iovec 結構體時,此值為n;
5、msg_control:是一個指向cmsghdr 結構體的指針
struct cmsghdr { socklen_t cmsg_len; /* data byte count, including header */ int cmsg_level; /* originating protocol */ int cmsg_type; /* protocol-specific type */ /* followed by unsigned char cmsg_data[]; */ };6、msg_controllen :cmsghdr 結構體可能不止一個;
7、flags : 一般設置為0即可;
直接上代碼:
client.c
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <stddef.h> #include <string.h> #include <sys/types.h> #include <errno.h> #include <sys/un.h> #include <sys/socket.h> #include <netinet/in.h> #include <sys/epoll.h> #include <fcntl.h> #define UNIXSTR_PATH "foo.socket" #define OPEN_FILE "test" int main(int argc, char *argv[]) { int clifd; struct sockaddr_un servaddr; //IPC int ret; struct msghdr msg; struct iovec iov[1]; char buf[100]; union { //保證cmsghdr和msg_control對齊 struct cmsghdr cm; char control[CMSG_SPACE(sizeof(int))]; } control_un; struct cmsghdr *pcmsg; int fd; clifd = socket(AF_UNIX, SOCK_STREAM, 0) ; if ( clifd < 0 ) { printf ( "socket failed.\n" ) ; return - 1 ; } fd = open(OPEN_FILE ,O_CREAT | O_RDWR, 0777); if( fd < 0 ) { printf("open test failed.\n"); return -1; } bzero (&servaddr, sizeof(servaddr)); servaddr.sun_family = AF_UNIX; strcpy ( servaddr.sun_path, UNIXSTR_PATH); ret = connect(clifd, (struct sockaddr*)&servaddr, sizeof(servaddr)); if(ret < 0) { printf ( "connect failed.\n" ) ; return 0; } //udp需要,tcp無視 msg.msg_name = NULL; msg.msg_namelen = 0; iov[0].iov_base = buf; iov[0].iov_len = 100; msg.msg_iov = iov; msg.msg_iovlen = 1; //設置緩沖區和長度 msg.msg_control = control_un.control; msg.msg_controllen = sizeof(control_un.control); //直接通過CMSG_FIRSTHDR取得附屬數據 pcmsg = CMSG_FIRSTHDR(&msg); pcmsg->cmsg_len = CMSG_LEN(sizeof(int)); pcmsg->cmsg_level = SOL_SOCKET; pcmsg->cmsg_type = SCM_RIGHTS; //指明發送的是描述符 *((int*)CMSG_DATA(pcmsg)) == fd; //把描述符寫入輔助數據 ret = sendmsg(clifd, &msg, 0); //send filedescriptor printf ("ret = %d, filedescriptor = %d\n", ret, fd); return 0 ; }server.c
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <stddef.h> #include <string.h> #include <sys/types.h> #include <errno.h> #include <sys/un.h> #include <sys/socket.h> #include <netinet/in.h> #include <sys/epoll.h> #include <fcntl.h> #define UNIXSTR_PATH "foo.socket" int main(int argc, char *argv[]) { int clifd, listenfd; struct sockaddr_un servaddr, cliaddr; int ret; socklen_t clilen; struct msghdr msg; struct iovec iov[1]; char buf [100]; char *testmsg = "test msg.\n"; union { //對齊 struct cmsghdr cm; char control[CMSG_SPACE(sizeof(int))]; } control_un; struct cmsghdr * pcmsg; int recvfd; listenfd = socket ( AF_UNIX , SOCK_STREAM , 0 ) ; if(listenfd < 0) { printf ( "socket failed.\n" ) ; return -1; } unlink(UNIXSTR_PATH) ; bzero (&servaddr, sizeof(servaddr)); servaddr.sun_family = AF_UNIX; strcpy ( servaddr.sun_path , UNIXSTR_PATH ) ; ret = bind ( listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)); if(ret < 0) { printf ( "bind failed. errno = %d.\n" , errno ) ; close(listenfd); return - 1 ; } listen(listenfd, 5); while(1) { clilen = sizeof( cliaddr ); clifd = accept( listenfd, (struct sockaddr*)&cliaddr , &clilen); if ( clifd < 0 ) { printf ( "accept failed.\n" ) ; continue ; } msg.msg_name = NULL; msg.msg_namelen = 0; //設置數據緩沖區 iov[0].iov_base = buf; iov[0].iov_len = 100; msg.msg_iov = iov; msg.msg_iovlen = 1; //設置輔助數據緩沖區和長度 msg.msg_control = control_un.control; msg.msg_controllen = sizeof(control_un.control) ; //接收 ret = recvmsg(clifd , &msg, 0); if( ret <= 0 ) { return ret; } //檢查是否收到了輔助數據,以及長度 if((pcmsg = CMSG_FIRSTHDR(&msg) ) != NULL && ( pcmsg->cmsg_len == CMSG_LEN(sizeof(int)))) { if ( pcmsg->cmsg_level != SOL_SOCKET ) { printf("cmsg_leval is not SOL_SOCKET\n"); continue; } if ( pcmsg->cmsg_type != SCM_RIGHTS ) { printf ( "cmsg_type is not SCM_RIGHTS" ); continue; } //這就是我們接收的描述符 recvfd = *((int*)CMSG_DATA(pcmsg)); printf ( "recv fd = %d\n", recvfd ); write ( recvfd, testmsg, strlen(testmsg) + 1); } } return 0 ; }
進程間傳輸文件描述符的fd是不一定相同的,在client裡面是4,到達server的時候4已經被占用了,所以分配了5給它。