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給它。