学习Unix域套接字总结
开门见山,哲学三问!Unix域套接字是什么?为什么会存在Unix域套接字?如何用Unix域套接字?
- Unix域套接字是什么,为什么会有Unix用于套接字?
Linux系统中不同进程进行通信的手段很多,套接字通信就是其中一种,传统我们所说的套接字是网络套接字,是实现不同主机之间的进程间通信的,需要有五元组,打包拆包、计算校验和、维护序号和应答等数据及操作保证数据的可靠传输。但是我们想用套接字实现同一主机不同进程之间的通信,操作系统下进程间通信本来就很可靠,不需要以上一系列数据及操作也可以实现数据的可靠传输,况且太多的校验会影响传输效率。所以Unix域套接字实现了这一功能!在数据交互上只负责copy数据,不需要执行协议处理,没有网络报头,校验和,不发送确认等,而且提供数据报和数据流两种接口。数据报服务是可靠的,既不会丢失报文也不会传输错误。Unix域套接字有两种,匿名Unix域套接字和命名的Unix域套接字
- 匿名Unix域套接字
匿名Unix域套接字,和管道想似以套接字对的形式创建,不同于管道的是,这对套接字都对读写开放,是全双工,可以使用他们面向网络的域套接字接口或者使用socketpair函数创建一对无命名,相互连接的Unix域套接字。需要注意的是,匿名Unix套接字对,虽然都对读写开放,但是,通过fd[0]写入数据,再通过fd[0]读数据,会阻塞,只能通过fd[1]来读,当fd[1]中没写入数据时,通过fd[0]进行读的话,也会阻塞。
创建匿名Unix域套接字
#include <sys/types.h>
#include <sys/socket.h>
int socketpair(int d, int type, int protocol, int sv[2]);
d:套接口的域,一般为AF_UNIX和网络套接字中我们熟悉的AF_INET是都是socket域。
type:套接口类型,Unix域套接字也支持流协议(SOCK_STREAM)和数据报协议SOCK_DGRAM。
protocol:使用的协议,对于socketpair函数,protocol参数必须为0。(目前网上没找到原因,可能是规定吧!)
sv[2]:指向存储文件描述符的指针:就是进行通信的套接字对。
匿名Unix域套接字搭配其他进程间通信方式使用
以消息队列为例,将消息队列的ID与套接字队中任意一个绑定在一块,将其中一个套接字注册到IO复用中进行可读事件检测,当消息队列中有来自其他进程的数据时,将消息队列中的数据读出,写入套接字缓冲区,这时就会触发IO复用的可读事件。然后就可以接收到其他进程的数据了。这样和单纯使用消息队列的区别就在于可以通过IO复用机制监测普通进程件的通信事件!下面是想法的一个实现,可以参考。
Main.h
#pragma once
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/epoll.h>
#include<iostream>
#include<signal.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<unistd.h>
#include<fcntl.h>
#include<pthread.h>
using namespace std ;
#define BUF_SIZE (1024*4)
#define EPOLL_MAX 1024
#define NUM 4
#define KEY 0x123//将消息队列的id和unix域套接字关联起来
struct node {//unix 域套接字int fd ;//消息队列的id号int qid ;
} ;//消息数据
struct Msg{long type ;char text[BUF_SIZE] ;
} ;namespace MsgId {static int msgid[NUM] ;
}
namespace EpFd {static int epfd ;
}
//初始化epoll,返回epoll句柄
int initEpollFd() ;
//添加fd到epoll树上
int addFd(int epfd, int fd) ;
//接收到消息队列的消息后写到套接字缓冲区中
void* recvData(void* data) ;
int runEpoll(int epfd) ;
void sig_handle(int signo) ;
Main.cpp
#include"Main.h"int main() {signal(SIGINT, sig_handle) ;pthread_t tid[NUM] ;struct node data[NUM] ;int qid[NUM] ;initEpollFd() ;int fd[2] ;int epfd = initEpollFd() ;EpFd:: epfd = epfd ;if(epfd < 0) {cout << __FILE__ << __LINE__ << endl ;exit(1) ;}for(int i = 0; i < NUM; i++) {//创建消息队列if((qid[i] = msgget(KEY+i, IPC_CREAT|IPC_EXCL|0666))<0) {cout << __FILE__ << __LINE__ << endl ;exit(1) ;}//创建unix域套接字if(socketpair(AF_UNIX, SOCK_DGRAM, 0, fd) < 0) {cout << __FILE__ << __LINE__ << endl ;exit(1) ;}MsgId::msgid[i] = qid[i] ;//将套接字和消息队列的id进行关联data[i].fd = fd[1];data[i].qid = qid[i] ;int ret = addFd(epfd, fd[0]) ;if(ret < 0) {return 0;}//将套接字加入epoll中pthread_create(&tid[i], NULL, recvData, &data[i]) ;}runEpoll(epfd) ;return 0;
}//启动epoll进行监听
int runEpoll(int epfd) {char buf[BUF_SIZE] ; struct epoll_event es[EPOLL_MAX] ;while(1) {int ret = epoll_wait(epfd, es, NUM, -1) ;if(ret < 0) {cout << __FILE__ << __LINE__ <<endl;exit(1) ;}cout << ret << endl ;for(int i=0; i< NUM; i++) {int fds = es[i].data.fd ; //若为可读事件,就将读取的数据打印到屏幕上if(es[i].events&EPOLLIN) {if(read(fds, buf, sizeof(buf)) < 0) {cout << __FILE__ <<__LINE__ <<endl ;exit(1) ;}cout << "get id from :"<< MsgId::msgid[i] << " data" << buf <<endl ;}}}
}//线程等待数据的到来
void* recvData(void* args) {node data = *(node*)args ;Msg msg ;for( ; ;) {int ret = msgrcv(data.qid, (void*)&msg, sizeof(msg)-sizeof(long), 0, MSG_NOERROR) ;if(ret < 0) {printf("%s %d\n", __FILE__, __LINE__) ;return 0 ;}if(write(data.fd, msg.text, sizeof(msg.text)) < 0) {printf("%s %d\n", __FILE__, __LINE__) ;return 0 ;}cout << msg.text <<endl ;}
}//向epoll树上添加fd
int addFd(int epfd, int fd) {struct epoll_event ev ;ev.events = EPOLLIN|EPOLLET ;ev.data.fd = fd ;int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev) ;if(ret < 0) {cout << __FILE__ << __LINE__ << endl ;exit(1) ;}return 1 ;
}//初始化epoll句柄
int initEpollFd() {int epfd = epoll_create(EPOLL_MAX) ;if(epfd < 0) {cout << __FILE__ << __LINE__ <<endl ;return -1 ;}struct epoll_event ev ;//检测可读事件ev.events = EPOLLIN ;return epfd ;
}//释放掉所有资源
void sig_handle(int signo) {//接收到中断信号,将所有资源释放掉if(signo == SIGINT) {for(int i= 0; i<NUM ;i++) {msgctl(MsgId::msgid[i], IPC_RMID, 0) ;}close(EpFd::epfd) ;}
}
test.h
#pragma once
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/epoll.h>
#include<thread>
#include<iostream>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<unistd.h>
#include<stdio.h>
#include<fcntl.h>
#include<unistd.h>
#include<sys/stat.h>
#include<signal.h>using namespace std ;
#define BUF_SIZE (1024*4)//消息数据
struct Msg{long type ;char text[BUF_SIZE] ;
} ;namespace MsgId {static int msgid ;
}
void sig_handle(int signo) ;
#include"test.h"
#include<stdlib.h>
#define KEY 0x123
int main(int argc, char** argv) {signal(SIGINT, sig_handle);if(argc != 2) {printf("%s %d\n", __FILE__, __LINE__) ;return 1 ;}key_t key = strtol(argv[1],NULL, 0) ;//获取消息队列的句柄int msgid = msgget(key, 0) ;if(msgid < 0) {printf("%s %d\n", __FILE__, __LINE__) ;exit(1) ;}Msg msg ;while(1) {cout <<"请输入要发送的消息:" ;cin >> msg.text ; //获取pid,在接收消息时可以用到int ret = msgsnd(msgid, (void*)&msg, sizeof(msg)-sizeof(long), 0) ;if(ret < 0) {printf("%s %d\n", __FILE__, __LINE__) ;exit(1) ;}}return 0;
}void sig_handle(int signo) {if(signo == SIGINT) {msgctl(MsgId::msgid, IPC_RMID, 0) ;}printf("已中断\n") ;
}
先执行Main,然后执行test并指定消息队列key值
运行结果 :
以上是一个简单应用,当我们想要使用IO复用机制检测各种进程间通信的事件时,可以将它们的ID与Unix域套接字绑定起来。然后将套接字注册到IO事件检测表(epoll、poll,或者select中)中就行!
以上是一个简单的应用,好玩的还在后面呢!
- 命名Unix域套接字
具体用法详述
- 命名Unix套接字
socketpair可以创建一对相互连接的套接字,但是每个套接字没有名字,在无关进程中也是无法使用它们。所以要想和其他进程进行通信,就需要定一个众所周知的名字。即类似于消息队列那样的操作,创建一个特殊的文件作为地址,让其他进程通过连接地址找到服务进程,Linux中通过以下结构维护地址:
struct sockaddr_un {sa_family_t sun_family ;char sun_path[108] ;
};
sun_path包括一个路径名,一般为绝对路径,当我们将一个地址绑定到一个Unix域套接字时,系统会用该路径名创建一个S_IFSOCK类型的文件。该文件向客户进程告示套接字的名称。该文件不能被打开,也不能有应用程序用于通信。如果我们试图绑定同一地址,该文件已经存在,那么绑定请求就会失败,当关闭套接字时并不会自动删除该文件,所以必须确保在程序退出前,对文件执行连接解除操作。
绑定简单例子
#include <stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<stdlib.h>
#include<sys/un.h>
#include<stddef.h>
int main()
{int fd ,size ;struct sockaddr_un un ;un.sun_family = AF_UNIX ;//设置一个路径名strncpy(un.sun_path, "test",sizeof(un.sun_path)-1) ;if((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {printf("socket failed") ;return 1;} if(bind(fd, (struct sockaddr*)&un, sizeof(struct sockaddr_un)) < 0) {printf("bind failed!");return 1 ; } printf("unix demain bound!\n");//推出解除链接unlink("test.socket") ;return 0 ;
}
创建唯一连接
确定了地址的创建方式,和网络套接字一样,服务进程可以通过bind、listen、accept和客户进程建立唯一连接。客户进程通过使用connect向服务进程发送连接请求。
下面是一个使用Unix域中的流socket的客户端和服务器程序:
server
#include <stdio.h>
#include<iostream>
#include<stdlib.h>
#include<unistd.h>
#include<sys/un.h>
#include<sys/socket.h>
#define BUFSIZE 1024
#define SV_SOCK_PATH "/tmp/uu"int main()
{struct sockaddr_un addr ;int sfd ,cfd ;char buf[BUFSIZE] ;bzero(buf, 1024) ;sfd = socket(AF_UNIX, SOCK_STREAM, 0) ;if(sfd == -1) {std::cout <<"socket err" <<std::endl ;exit(1) ;}//防止文件已存在导致绑定失败if(remove(SV_SOCK_PATH) == -1&& errno != ENOENT) { std::cout<< "remove" <<std::endl ;exit(1) ;}memset(&addr, 0, sizeof(addr)) ;addr.sun_family = AF_UNIX ;//设置成员sun_pathstrncpy(addr.sun_path, SV_SOCK_PATH, sizeof(addr.sun_path)-1) ;//绑定地址if(bind(sfd, (struct sockaddr*)&addr,size) == -1) {std::cout <<"bind error!"<<std::endl ;exit(1) ;}if(listen(sfd, 10) == -1) {std::cout <<"listen error!"<<std::endl ;exit(1);}ssize_t num ;for(;;){cfd = accept(sfd, NULL, NULL) ;if(cfd == -1){std::cout << "accept error!"<<std::endl ;exit(1) ;} while((num = read(cfd, buf, BUFSIZE)) > 0)write(STDOUT_FILENO, buf, BUFSIZE); }close(cfd) ;return 0;
}
client
#include <iostream>
#include<sys/un.h>
#include<string.h>
#include<sys/socket.h>
#include<unistd.h>
#define SIZE 1024
#define SV_SOCK_PATH "/tmp/uu"int main()
{struct sockaddr_un addr ;int sfd ;ssize_t num ;char buf[SIZE] ;bzero(buf, SIZE) ;sfd = socket(AF_UNIX, SOCK_STREAM, 0) ;memset(buf, 0, sizeof(buf)) ;memset(&addr, 0, sizeof(addr)) ;addr.sun_family = AF_UNIX ;strncpy(addr.sun_path, SV_SOCK_PATH, sizeof(addr.sun_path)-1) ;int size = offsetof(struct sockaddr_un, sun_path)+strlen(addr.sun_path) ;if(connect(sfd, (struct sockaddr*)&addr, size)) {std::cout <<"connnect error!" << std::endl ;exit(1) ;}while((num = read(STDIN_FILENO, buf, SIZE)) >0) {write(sfd, buf, num) ;}if(num == -1) {std::cout <<"write error!" << std ::endl ;exit(1) ;}return 0;
}
发送打开的描述符
发送文件描述符有什么意义呢?
对于这一个问题,我只能说,根据个人见解存在即合理,这一操作可以实现使得发送文件描述符的进程被接收描述符进程监视起来,发送进程对文件的操作对于接收进程来说都是透明的!
思考一个问题 ,在A进程中,打开一个文件,通过消息队列发给进程B(假定能接收成功),那这个文件描述符在B进程中是否会有效呢?!对,会失效!我们都知道在进程空间的所有信息由内核控制的结构体task_struct来维护,每个进程都有相应的task_struct,task_struct里面有一个成员 struct files_struct *files,称为用户打开文件表,记录当前进程文件描述符的打开情况,打开文件的记录也在这个结构中记录,进程调用open返回的文件描述符由struct file类型的成员决定, 每个进程的struct files_struct如图所示:
struct files_struct {atomic_t count; /* 共享该表的进程数 */
rwlock_t file_lock; /* 保护以下的所有域,以免在tsk->alloc_lock中的嵌套*/
int max_fds; /*当前文件对象的最大数*/
int max_fdset; /*当前文件描述符的最大数*/
int next_fd; /*已分配的文件描述符加1*/
struct file ** fd; /* 指向文件对象指针数组的指针 */
fd_set *close_on_exec; /*指向执行exec( )时需要关闭的文件描述符*/
fd_set *open_fds; /*指向打开文件描述符的指针*/
fd_set close_on_exec_init;/* 执行exec( )时需要关闭的文件描述符的初 值集合*/
fd_set open_fds_init; /*文件描述符的初值集合*/
struct file * fd_array[32];/* 文件对象指针的初始化数组*/
};struct file
{struct list_head f_list; /*所有打开的文件形成一个链表*/
struct dentry *f_dentry; /*指向相关目录项的指针*/
struct vfsmount *f_vfsmnt; /*指向VFS安装点的指针*/
struct file_operations *f_op; /*指向文件操作表的指针*/
mode_t f_mode; /*文件的打开模式*/
loff_t f_pos; /*文件的当前位置*/
unsigned short f_flags; /打开文件时所指定的标志fd///
unsigned short f_count; /*使用该结构的进程数*/
unsigned long f_reada, f_ramax, f_raend, f_ralen, f_rawin;
/*预读标志、要预读的最多页面数、上次预读后的文件指针、预读的字节数以及
预读的页面数*/
int f_owner; /* 通过信号进行异步I/O数据的传送*/
unsigned int f_uid, f_gid; /*用户的UID和GID*/
int f_error; /*网络写操作的错误码*/
unsigned long f_version; /*版本号*/
void *private_data; /* tty驱动程序所需 */
};
所以说,tast_struct中的file_struct不一样,A进程打开的文件描述符发给B进程后怎么能指向同一个文件表呢?普通的IPC实现进程之间传递文件fd会导致fd失效!
但是Unix域套接字却可以实现不同进程间描述符的传送!如何实现?就和父进程fork出子进程,父子进程共享文件表项的原理基本一致!如图:
下面的例子是子进程继承了父进程的文件描述符,父进程和子进程通过fd共享文件表读取同一文件:
#include <stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<fcntl.h>
int main()
{char buf[10] ;int fd = open("hello",O_RDWR) ;//创建新进程if(fork() == 0) {while(read(fd, buf, sizeof(buf))>0)printf("\n子进程%d: %s\n", getpid(), buf);}while(read(fd, buf, sizeof(buf))>0)printf("\n父进程%d:%s\n",getpid(), buf);return 0;
}
文件内容为:
bcdefghjklpourwbnmz
运行结果:
子进程先被系统调度读取文件移动文件指针,父进程被调度后,继续移动子进程移动了的文件指针读取数据,说明父子进程的文件fd共享同一文件表!
同样,在非亲缘关系进程之间传送文件描述符也是同样的道理,发送fd就相当于发送进程向接收进程发送了一个与其进程中打开的fd所指向的文件表项相同的指针,这个指针被接收进程接收到后,存于接收进程第一个可用的描述符表项中。当发送进程将描述符发送给接收进程后,通常关闭该文件描述符,发送进程关闭该描述符并不会真的关闭该文件或者设备,原因是描述符将视为由接收进程打开(即使接收进程尚未接收到该描述符),即描述符的发送导致他的访问统计数加1。
为了用Unix域套接字交换文件描述符,调用sendmsg和recvmsg,这两个函数中都有一个指向msghdr的结构的指针,该结构包含了所有关于要发送或者要接收的消息的信息,定义如下:
#include<sys/socket.h>
struct msghdr { void * msg_name ; / * 消息的协议地址 * / 协议地址和套接口信息,在非连接的UDP中,发送者要指定对方地址端口,接受方用于的到数据来源,如果不需要的话可以设置为NULL(在TCP或者连接的UDP中,一般设置为NULL)socklen_t msg_namelen ; / * 地址的长度 * / //上面两种一般和用于在网络连接上发送数据报,其中目的地址可以有数据报来指定struct iovec * msg_iov ; / * 多io缓冲区的地址 * //定义套接字要发送的数据 int msg_iovlen ; / * 缓冲区的个数 * / //上面缓冲区的个数void * msg_control ; 辅助数据, cmsghdr结构体的地址socklen_t msg_controllen ; //cmsghdr字段包含的字节数int msg_flags ; / * 接收消息的标识 * /
} ;
科普一下关于struct iovec结构的用法:
#include <iostream>
#include<sys/uio.h>
#include<string.h>
int main()
{struct iovec iov[3] ;const char* p1 = "I'" ;const char* p2 = "m " ;const char* p3 = "programing!\n";iov[0].iov_base =(void*) p1 ;iov[0].iov_len = strlen(p1) ;iov[1].iov_base =(void*) p2 ;iov[1].iov_len = strlen(p2) ;iov[2].iov_base =(void*) p3 ;iov[2].iov_len = strlen(p3) ;writev(1, iov, 3) ;return 0;
}
前两者主要用于网络连接上发送数据报。
结构中msg_control指向msg_controllen结构。
struct cmsghdr{socetlen_t cmsg_len ;//大小有CMSG_LEN宏决定,一般传一个int然后sizeof(int)+sizoeof(struct cmsghdr) ;然后根据结构体对其要求向上取整int cmsg_type ;//一般设置为SCM_RIGHTS说明访问权int cmsg_level ;//一般设置成SOL_SOCKET,说明被设置选项是在socket级别上的
}
为了传送文件描述符,将cmsg_len设置为cmsghdr结构的长度加一个整型的长度(描述符的长度),cmsg_level字段设置为SOL_SOCKET,cmsg_type字段设置为SCM_RIGHTS(SCM为套接字级控制消息),用以表明传送访问权。访问权仅能通过Unix域套接字传送,描述符紧随cmsg_type字段之后存储,用CMSG_DATA宏获得该整型量的指针。
参考(可以说是照抄)APUE写的send_fd
#include<sys/un.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<string.h>#define CONTROLLEN CMSG_LEN(sizeof(int)) static struct cmsghdr* cmptr = NULL ;//send_fd 先发送2字节0,然后发送实际描述符
//发送文件描述符
//通过Unix域套接字传递文件描述符,sendmsg调用被
//用来传送协议数据以及描述符
int send_fd(int fd, int fd_to_send) {struct iovec iov[1] ;struct msghdr msg ;char buf[2] ;//向往缓冲区填上两字节数据,描述符随其后发送iov[0].iov_base = buf ;iov[0].iov_len = 2 ;msg.msg_iov = iov ;msg.msg_iovlen = 1 ;msg.msg_name = NULL ;msg.msg_namelen = 0 ;if(fd_to_send < 0) {msg.msg_control = NULL ;msg.msg_controllen = 0 ;buf[1] = -fd_to_send ;if(buf[1] == 0) {buf[1] = 1 ;}}else {if(cmptr == NULL && (cmptr = (struct cmsghdr*)malloc(CONTROLLEN)) == NULL) {return -1 ;}cmptr->cmsg_level = SOL_SOCKET ;cmptr->cmsg_type = SCM_RIGHTS ;cmptr->cmsg_len = CONTROLLEN ;msg.msg_control = cmptr ;msg.msg_controllen = CONTROLLEN ;//返回一个指针指向与cmsghdr相关联的数据//这里将要发送的描述符和这个地址绑定*(int*)CMSG_DATA(cmptr) = fd_to_send ;buf[1] = 0 ;}buf[0] = 0; if(sendmsg(fd, &msg, 0) != 2) {return -1 ;}return 0 ;
}
recv_fd
#include<sys/socket.h>
#include<stdio.h>
#include<sys/un.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<string.h>
#include<stdlib.h>
//recv_fd 读取套接字中所有字节直至遇到NULL字符
//null字符之前所有的字符都传送给调用者的userfunc
//该程序总是准备接收一个描述符
//在每次调用之前设置msg_control和msg_controllen
//在msg_controllen返回的是非0时才确实接收到描述符
#define CONTROLLEN CMSG_LEN(sizeof(int))
static struct cmsghdr *cmptr = NULL ;
int recv_fd(int fd, ssize_t(*userfunc)(int, const void *t, size_t)) {int newFd , nr ,status ;char* ptr ;char buf[1024] ;struct iovec iov[1] ;struct msghdr msg ;status = 1 ;for(;;) {iov[0].iov_base = buf ;iov[0].iov_len =sizeof(buf) ; msg.msg_iov = iov ;msg.msg_iovlen = 1 ;msg.msg_name = NULL ;msg.msg_namelen = 0 ;if(cmptr == NULL && (cmptr = (struct cmsghdr*)malloc(CONTROLLEN)) == NULL) {return -1 ;}msg.msg_control = cmptr ;msg.msg_controllen = CONTROLLEN ;if((nr = recvmsg(fd, &msg, 0)) < 0) {return -1 ;}else if(nr == 0){return -1 ;}for(ptr = buf; ptr < &buf[nr]; ) {if(*ptr++ == 0) {if(ptr != &buf[nr-1]) {printf("出现错误\n") ;return -1 ;}status = * ptr&0xFF ;if(status == 0) {if(msg.msg_controllen < CONTROLLEN) {printf("出现错误!\n") ;}newFd = *(int*)CMSG_DATA(cmptr) ;}else {newFd = -status ;}nr = -2 ;}}if(nr > 0 && (*userfunc)(2, buf, nr) != nr) {return -1 ;}if(status >= 0) {return newFd ;}}
}
当发送完描述符后,那么问题来了,接收到描述符的那一个进程如何知道是哪个进程那个用户给他发送的文件描述符呢?
要想确认进程身份,则需要发送文件描述符的进程来主动告诉目标进程。Linux中维护了一个结构体,用来表述发送进程的身份。定义如下:
struct ucred{pid_t pid ;uid_t uid ;gid_t gid ;
}
见名知意,这里各个字段不做解释。
在发送进程发送时,包含上自己的证书才能让接收进程确定各个进程的身份。
更新过后的
send_fd
#if defined(SCM_CREDS)
#define CREDSTRUCT cmsgcred
#define SCM_CREDTYPE SCM_CREDS
#elif defined(SCM_CREDENTIALS)
#define CREDSTRUCT ucred
#define SCM_CREDTYPE SCM_CREDENTIALS
#else
#error passing credentials is unsupported!
#endif#define RIGHTSLEN CMSG_LEN(sizeof(int))
#define CREDSLEN CMSG_LEN(sizeof(struct CREDSTRUCT))
#define CONTROLLEN (RIGHTSLEN + CREDSLEN)static struct cmsghdr *cmptr = NULL; int
send_fd(int fd, int fd_to_send)
{struct CREDSTRUCT *credp;struct cmsghdr *cmp;struct iovec iov[1];struct msghdr msg;char buf[2]; /* send_fd/recv_ufd 2-byte protocol */iov[0].iov_base = buf;iov[0].iov_len = 2;msg.msg_iov = iov;msg.msg_iovlen = 1;msg.msg_name = NULL;msg.msg_namelen = 0;msg.msg_flags = 0;if (fd_to_send < 0) {msg.msg_control = NULL;msg.msg_controllen = 0;buf[1] = -fd_to_send; if (buf[1] == 0)buf[1] = 1; } else {if (cmptr == NULL && (cmptr = malloc(CONTROLLEN)) == NULL)return(-1);msg.msg_control = cmptr;msg.msg_controllen = CONTROLLEN;cmp = cmptr;cmp->cmsg_level = SOL_SOCKET;cmp->cmsg_type = SCM_RIGHTS;cmp->cmsg_len = RIGHTSLEN;*(int *)CMSG_DATA(cmp) = fd_to_send; cmp = CMSG_NXTHDR(&msg, cmp);cmp->cmsg_level = SOL_SOCKET;cmp->cmsg_type = SCM_CREDTYPE;cmp->cmsg_len = CREDSLEN;credp = (struct CREDSTRUCT *)CMSG_DATA(cmp);
#if defined(SCM_CREDENTIALS)credp->uid = geteuid();credp->gid = getegid();credp->pid = getpid();
#endifbuf[1] = 0; }buf[0] = 0; if (sendmsg(fd, &msg, 0) != 2)return(-1);return(0);
}
recv_fd
.......apue头文件省略
#if defined(SCM_CREDS)
#define CREDSTRUCT cmsgcred
#define CR_UID cmcred_uid
#define SCM_CREDTYPE SCM_CREDS
#elif defined(SCM_CREDENTIALS)
#define CREDSTRUCT ucred
#define CR_UID uid
#define CREDOPT SO_PASSCRED
#define SCM_CREDTYPE SCM_CREDENTIALS
#else
#error passing credentials is unsupported!
#endif#define RIGHTSLEN CMSG_LEN(sizeof(int))
#define CREDSLEN CMSG_LEN(sizeof(struct CREDSTRUCT))
#define CONTROLLEN (RIGHTSLEN + CREDSLEN)static struct cmsghdr *cmptr = NULL;
int
recv_ufd(int fd, uid_t *uidptr,ssize_t (*userfunc)(int, const void *, size_t))
{struct cmsghdr *cmp;struct CREDSTRUCT *credp;char *ptr;char buf[MAXLINE];struct iovec iov[1];struct msghdr msg;int nr;int newfd = -1;int status = -1;
#if defined(CREDOPT)const int on = 1;if (setsockopt(fd, SOL_SOCKET, CREDOPT, &on, sizeof(int)) < 0) {err_ret("setsockopt error");return(-1);}
#endiffor ( ; ; ) {iov[0].iov_base = buf;iov[0].iov_len = sizeof(buf);msg.msg_iov = iov;msg.msg_iovlen = 1;msg.msg_name = NULL;msg.msg_namelen = 0;if (cmptr == NULL && (cmptr = malloc(CONTROLLEN)) == NULL)return(-1);msg.msg_control = cmptr;msg.msg_controllen = CONTROLLEN;if ((nr = recvmsg(fd, &msg, 0)) < 0) {err_ret("recvmsg error");return(-1);} else if (nr == 0) {err_ret("connection closed by server");return(-1);}for (ptr = buf; ptr < &buf[nr]; ) {if (*ptr++ == 0) {if (ptr != &buf[nr-1])err_dump("message format error");status = *ptr & 0xFF; if (status == 0) {if (msg.msg_controllen != CONTROLLEN)err_dump("status = 0 but no fd");// CMSG_FIRSTHDR返回一个指针,指向与msghdr相关联的第一个结构体//CMSG_NXTHDR返回一个指针指向与msghdr相关联的下一个结构体for (cmp = CMSG_FIRSTHDR(&msg); cmp != NULL; cmp = CMSG_NXTHDR(&msg, cmp)) {if (cmp->cmsg_level != SOL_SOCKET)continue;switch (cmp->cmsg_type) {case SCM_RIGHTS:newfd = *(int *)CMSG_DATA(cmp);break;case SCM_CREDTYPE://返回一个指针指向域cmghdr相关联的数据credp = (struct CREDSTRUCT *)CMSG_DATA(cmp);*uidptr = credp->CR_UID;}}} else {newfd = -status;}nr -= 2;}}if (nr > 0 && (*userfunc)(STDERR_FILENO, buf, nr) != nr)return(-1);if (status >= 0) return(newfd);}
}
下面是进程间的通信,传送普通数据和套接字的一个简单例子:
recvProcess
#include <unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/un.h>int recvfd(int);void client_socketun(){int sockfd, newfd, len, nr;sockaddr_un un;char buf[20]={0};un.sun_family = AF_UNIX;strcpy(un.sun_path, "/tmp/s");len = sizeof(un.sun_family) + strlen(un.sun_path);sockfd = socket(AF_UNIX, SOCK_STREAM, 0);if(connect(sockfd, (sockaddr *)&un, len) == -1){//连接socketprintf("connect error");return ;}// read(sockfd, buf, 20);recv(sockfd, buf, 20, 0);//接收普通数据printf("接收到普通数据:%s\n", buf) ; if((newfd = recvfd(sockfd)) == -1){//接收msg数据printf("rec error");}else{printf("通过新的文件描述符读取文件数据:\n") ;while(1){if((nr = read(newfd, buf, 10)) == -1){perror("read error");}if(nr == 0) {break ;}else{printf("%s", buf);fflush(stdout);}}}close(sockfd);
}int recvfd(int sockfd){int newfd, nr;struct cmsghdr *cmptr = nullptr;//OUTint cmsghdrlen = CMSG_LEN(sizeof(int));char buf[2];struct iovec iov[1];struct msghdr msg;iov[0].iov_base = buf;iov[0].iov_len = 2;msg.msg_iov = iov;msg.msg_iovlen = 1;msg.msg_name = NULL;msg.msg_namelen = 0;cmptr = (cmsghdr *)malloc(cmsghdrlen);msg.msg_control = cmptr;msg.msg_controllen = cmsghdrlen;if((nr = recvmsg(sockfd, &msg, 0)) < 0){perror("recvmsg error\n");}else{newfd = *(int *)CMSG_DATA(cmptr);fflush(stdout);return newfd;}return -1;
}int main(){client_socketun();return 0;
}
sendProcess
#include <unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/un.h>void socketSendfd(int, int);void namedSocket() {int sockfd, size;sockaddr_un un;char buf[20] = {"123"};un.sun_family = AF_UNIX;strcpy(un.sun_path, "/tmp/s");if ((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)printf("socket error");size = sizeof(un.sun_family) + strlen(un.sun_path);
// print(size);if (bind(sockfd, (sockaddr *) &un, size) < 0) {//绑定printf("bind error");return;}
// print("bound OK");if (listen(sockfd, 5) == -1) {//监听printf("listen error");return;}sockaddr_un client_addr;//OUT,接收客户端un信息socklen_t len;int clifd;if ((clifd = accept(sockfd, (sockaddr *) &client_addr, &len)) == -1) {printf("accept error");return;}send(clifd, buf, 10, 0);//传送普通数据int fd = open("hello", O_CREAT | O_RDWR, 0666);char data[10] ;read(fd, data, sizeof(data)) ;printf("读取文件的数据:%s\n",data) ;socketSendfd(clifd, fd);//传送文件描述符close(fd);close(sockfd);unlink(un.sun_path);//解除链接操作
}void socketSendfd(int clifd, int fdToSend){struct iovec iov[1];struct msghdr msg;//②msghdr结构体struct cmsghdr *cmptr = nullptr;//③cmsghdr结构体int cmsghdrlen = CMSG_LEN(sizeof(int));//CMSG_LEN()返回cmsghdr结构的cmsg_len成员的值,考虑到任何必要的对齐。它取出参数的长度。这是一个常量表达式。char buf[2] = {0};iov[0].iov_base = buf;iov[0].iov_len = 2;msg.msg_iov = iov;msg.msg_iovlen = 1;msg.msg_name = NULL;msg.msg_namelen = 0;cmptr = (cmsghdr *)malloc(cmsghdrlen);cmptr->cmsg_level = SOL_SOCKET;cmptr->cmsg_type = SCM_RIGHTS;//SCM_RIGHTS表明在传送访问权,访问权仅能通过UNIX域套接字传送。cmptr->cmsg_len = cmsghdrlen;msg.msg_control = cmptr;msg.msg_controllen = cmsghdrlen;*(int *)CMSG_DATA(cmptr) = fdToSend;//CMSG_DATA()返回cmsghdr的数据部分指针。if(sendmsg(clifd, &msg, 0) == -1){perror("send error");}
}int main() {namedSocket();return 0;
}
hello源文件内容:
bdfsfcdefghjklpourfsfsfsfwbnmz
运行结果:
发送端发送时指针做了移动,在接收端文件指针继续接着发送端进程移动的位置移动!证明两个进程中的描述符共同指向同意文件表项。成功实现无关进程之间传送文件描述符的目的!
其实可以讲匿名Unix套接字和有名套接字结合起来使用通过使用有名Unix域套接字发送匿名套接字!
学习Unix域套接字总结相关推荐
- UNIX域套接字编程和socketpair 函数
一.UNIX Domain Socket IPC socket API原本是为网络通讯设计的,但后来在socket的框架上发展出一种IPC机制,就是UNIX Domain Socket.虽然网络soc ...
- 域服务器广播消息,广播,组播和UNIX域套接字
1.广播 1.特点 一对多 仅能使用UDP 2.概念 发送方只有一个接收方则称单播 如果同时发给局域网中的所有主机,成为广播 只有用户数据包(使用UDP协议)套接字才能广播 广播地址 1.以192.1 ...
- linux 套接字 文件 路径,linux – 识别unix域套接字连接的另一端
我正在试图找出一个持有unix域套接字另一端的进程.在某些strace输出中,我已经确定了一个给定的文件描述符,这个文件描述符涉及到我目前正在调试的问题,我想知道哪一个进程在另一端.由于存在与该套接字 ...
- Beats:将 Unix 域套接字中的数据索引到 Elastic Stack
这篇博文将解释什么是 UNIX 域套接字,以及如何将发送到 UNIX 域套接字的索引编入 Elastic Stack - 以及为此存在哪些不同的用例. UNIX 域套接字 - 简短的历史 如果你想让进 ...
- 【技术应用】java基于UNIX域套接字(unix domain socket)连接mysql数据库
前言 Unix domain socket 又叫 IPC(inter-process communication 进程间通信)socket,用于实现同一主机上的进程间通信. socket 原本是为网络 ...
- unix 域套接字实现进程间通信
目录 1.认识域套接字 2.unix域套接字相关API及地址结构介绍 (1) 创建unix域套接字 (2) 填充地址结构 sockaddr_un 3.unix域套接字实现进程间通信( ...
- 【socket】 unix域套接字(socketpair )通信|socketpair和pipe的区别|进程间通信-Unix domain socket
目录 unix域套接字(socketpair )通信|socketpair和pipe的区别 socketpair机制 描述 原理 socketpair和pipe的区别 进程间通信-Unix domai ...
- 网络编程_5(超时检测+UNIX域套接字+抓包工具+包头分析)
一二章请点击:网络编程_1(网络基础+跨主机传输) 三四章请点击:网络编程_2(网络属性+UDP(UDP模型+广播组播)) 第五章请点击:网络编程_3(TCP) 第六章请点击:网络编程_4(IO模型) ...
- 经由unix域套接字传送文件描述符
sendmsg 和recvmsg 该两个函数都指向msghdr指针:该结构包含了所有有关收发内容的信息 两个元素处理控制信息的传送与接收
最新文章
- androidx和android的区别,【译】使用AndroidX代替Android支持库
- 发送邮件_使用 Python 发送电子邮件
- moxy json介绍_MOXy是GlassFish 4中新的默认JSON绑定提供程序
- LeetCode 963. 最小面积矩形 II
- H5实现俄罗斯方块(一)
- 5G 芯片的“春秋五霸”
- Proj.4库的编译及使用
- 西南科技大学oj题逆置顺序表
- 蘑菇租房java,租房经历总结-----我是如何2天找到合适租房的(房东直租)简单粗暴...
- LabVIEW色彩匹配实现颜色识别、颜色检验(基础篇—13)
- chrome浏览器上传文件延迟_谷歌Chrome上传文件未响应的解决办法
- HiveSQL一天一个小技巧:如何借助于str_to_map进行行转列
- elementUI table表格合并相同的内容
- wp文件转shp_ArcGIS教程:MapGIS转换shp攻略
- 制作立体图像(上):红蓝眼镜原理
- Oracle知识点总结(三)
- 春日里有skycc营销软件相陪
- 2的负x次幂图像_函数y=2的x次方与y=x的2次方的图象的 – 手机爱问
- 计算机组装与维修专业术语,计算机组装与维护大纲.pdf
- 【技术探索】专注连接的LinkFabric技术详解!