UNP学习 Unix域协议
Unix域协议并不是一个实际的协议族,它只是在同一台主机上进行客户-服务器通信时,使用与在不同主机上的客户和服务器间通信时相同的API的一种方法。
当客户和服务器在同一台主机上时,Unix域协议是这套系列书的第二卷将介绍的IPC通信方式的一种替代品。
Unix域提供了两种类型的套接口:字节流套接口(与TCP类似)、数据报套接口(与UDP类似)
使用Unix域套接口有三个原因:
- 当通信双方位于同一台主机上时,Unix域套接口的速度通常是TCP套接口的两倍。
- Unix域套接口可以用来在同一台主机上的各进程之间传递描述字。
- Unix域套接口的较新实现中可以向服务器提供客户的凭证,这能提供附加的安全检查。
二、Unix域套接口地址结构
struct sockaddr_un{ uint8_t sun_len; sa_family_t sun_family; /* AF_LOCAL */ char sun_path[104]; /* null-terminated pathname */ };
三、socketpair函数
socketpair函数建立一对相互连接的套接口,这个函数只对Unix域套接口适用
#include <sys/socket.h> int socketpair(int family, int type, int protocol, int sockfd[2]); 返回:成功0,出错-1
family:必须为AF_LOCAL
protocol:必须为0
type:可以是SOCK_STREAM或SOCK_DGRAM
新创建的两个套接口描述字作为sockfd[0]和sockfd[1]返回
socketpair的多进程例子
1 #include <stdio.h> 2 #include <sys/types.h> 3 #include <sys/socket.h> 4 #include <pthread.h> 5 6 #define SOCKET_BUFFER_SIZE (32768U) 7 8 void *thread_function(void *arg) 9 { 10 int len = 0; 11 int fd = *((int *)(arg)); 12 char buf[500]; 13 int cnt = 0; 14 15 while(1) { 16 len = sprintf(buf, "Hi, main process, cnt = %d", cnt++); 17 write(fd, buf, len); 18 19 len = read(fd, buf, 500); 20 buf[len] = '\0'; 21 printf("%s\n", buf); 22 23 sleep(5); 24 } 25 26 return NULL; 27 } 28 29 int main() 30 { 31 int ret; 32 int sockets[2]; 33 int bufferSize = SOCKET_BUFFER_SIZE; 34 pthread_t thread; 35 36 ret = socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets); 37 if(ret == -1) { 38 printf("socketpair create error!\n"); 39 return -1; 40 } 41 42 setsockopt(sockets[0], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize)); 43 setsockopt(sockets[0], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize)); 44 setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize)); 45 setsockopt(sockets[1], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize)); 46 47 pthread_create(&thread, NULL, thread_function, (void *)(&sockets[1])); 48 49 int len = 0; 50 int fd = sockets[0]; 51 char buf[500]; 52 int cnt = 0; 53 54 while(1) { 55 len = read(fd, buf, 500); 56 buf[len] = '\0'; 57 printf("%s\n", buf); 58 59 len = sprintf(buf, "Hi, thread process, cnt = %d", cnt++); 60 write(fd, buf, len); 61 } 62 return 0; 63 }
四、套接口函数
当用于Unix套接口时,套接口函数有一些差别和限制。需注意的是并不是所有的实现中都做到了这一级。
- bind建立的路径名的缺省访问权限应为0777,并被当前的umask值修改。
- 与Unix域套接口相关联的路径名应为一个绝对路径名,而不是相对路径名。避免使用后者的原因是它依赖于调用者的当前工作目录。
- connect使用的路径名必须是一个绑定的某个已打开的Unix域套接口上的路径名,而且套接口的类型也必须一致。
- 用connect连接Unix域套接口时的权限检查和用open以只写方式访问路径名时完全相同。
- Unix域字节流套接口和TCP套接口类似:它们都为进程提供一个没有记录边界的字节流接口
- 如果Unix域字节流套接口的connect调用发现监听套接口的队列已满,会立刻返回一个ECONNREFUSED错误。如果监听套接口的队列已满,它将忽略到来的SYN,TCP连接的发起方会接着发送几次SYN重试。
- Unix域数据报套接口和UDP套接口类似,他们都提供一个保留记录边界的不可靠的数据报服务。
- 与UDP套接口不同的是,在未绑定的Unix域套接口上发送数据报不会给它绑定一个路径名。
五、描述字传递
从考虑从一个进程向另一个进程传递打开的描述字时,我们通常会想到:
- 在fork调用后,子进程共享父进程的所有打开的描述字
- 在调用exec时所有描述字仍保持打开
第一个例子中进程打开一个描述字,调用fork,然后父进程关闭描述字,让子进程处理这个描述字。
这样将一个打开的描述字从父进程传递到子进程。但我们也想让子进程打开一个描述字并捡起传递给父进程。
- 创建一个字节流的或数据报的Unix域套接口。
- 进程可以用任何返回描述字的Unix函数打开一个描述字。
- 发送进程建立一个msghdr接口,其中包含要传递的描述字
- 接收进程调用recvmsg在Unix域套接口上接收描述字。
两个进程用socketpair通信文件描述符,并对打开的文件写入的例子:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <iostream> using namespace std; #include <sys/socket.h> #include <unistd.h> #include <sys/wait.h> #include <fcntl.h> #include <sys/uio.h> void send_fd(int sock, int sendfd) { struct msghdr msg; struct iovec iov[1]; char buf[32]; int cmsgsize = CMSG_LEN(sizeof(int)); struct cmsghdr *cmptr; cmptr = (cmsghdr *)malloc(cmsgsize); if(cmptr == NULL) { cout<<"[send_fd] init cmptr error"<<endl; exit(1); } cmptr->cmsg_level = SOL_SOCKET; cmptr->cmsg_type = SCM_RIGHTS; /* send file description */ cmptr->cmsg_len = cmsgsize; *((int *)CMSG_DATA(cmptr)) = sendfd; iov[0].iov_base = buf; iov[0].iov_len = sizeof(buf); msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_iov = iov; msg.msg_iovlen = 1; msg.msg_control = cmptr; msg.msg_controllen = cmsgsize; int ret = sendmsg(sock, &msg, 0); free(cmptr); if(ret == -1) { cout<<"[send_fd] sendmsg error"<<endl; exit(1); } } int recv_fd(int sock) { struct msghdr msg; struct iovec iov[1]; char buf[32]; int cmsgsize = CMSG_LEN(sizeof(int)); struct cmsghdr *cmptr; iov[0].iov_base = buf; iov[0].iov_len = sizeof(buf); // cmptr = CMSG_FIRSTHDR(&msg);搞不明白为什么用这个函数不行 cmptr = (cmsghdr *)malloc(cmsgsize); if(cmptr == NULL) { cout<<"[send_fd] init cmptr error"<<endl; exit(1); } msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_iov = iov; msg.msg_iovlen = 1; msg.msg_control = cmptr; msg.msg_controllen = cmsgsize; int ret = recvmsg(sock, &msg, 0); free(cmptr); if(ret == -1) { cout<<"[recv_fd] recvmsg error"<<endl; exit(1); } int fd = *((int *)CMSG_DATA(cmptr)); return fd; } void worker_process_cycle(int sockets[2]) { cout<<"worker process #"<<getpid()<<endl; int fd = sockets[1]; int file = recv_fd(fd); if(file < 0) { cout<<"[worker] invalid fd!"<<endl; exit(1); } char msg[] = "child process"; cout<<"[worker] write file #"<<file<<" ret = " <<write(file, msg, sizeof(msg))<<endl; close(file); exit(0); } void master_process_cycle(int sockets[2]) { cout<<"master process #"<<getpid()<<endl; int fd = sockets[0]; system("rm -f ./newfile"); int file = open("./newfile", O_CREAT|O_TRUNC|O_RDWR); cout<<"[master] dispath fd to worker process, file=#"<<file<<endl; send_fd(fd, file); int status; waitpid(-1, &status, 0); exit(0); } int main(int argc, char *argv[]) { cout<<"current pid: "<<getpid()<<endl; int sockets[2]; if(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) == -1) { cout<<"failed to create domain socket by socketpair"<<endl; exit(1); } cout<<"create domain socket by sockpair success"<<endl; cout<<"create process to communicate over domain socket"<<endl; pid_t pid = fork(); if(pid == 0) { worker_process_cycle(sockets); } else { master_process_cycle(sockets); } for( ; ; ) { pause(); } }
无欲速,无见小利。欲速,则不达;见小利,则大事不成。