UNIX域套接字可以在同一台主机上各进程之间传递文件描述符。
下面先来看两个函数:
#include
#include
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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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给它。