UNIX环境高级编程-第17章- 高级进程间通信 - 一
来源:互联网 发布:淘宝砍价师靠谱吗 编辑:程序博客网 时间:2024/06/08 09:40
17.1 引言
前面两章介绍了UNIX系统提供的各种IPC,包括管道和套接字。本章介绍两种高级IPC:基于STREAM的管道和UNIX域套接字,并说明它们的应用方法。前面介绍的管道是半双工管道,本章基于 STREAMS 的管道是属于全双工的管道,半双工和全双工管道的区别如下:半双工只能在一端进行读或写;全双工可以在某一端同时进行读写;
17.2 基于 STREAMS 的管道
基于 STREAMS 的管道是一个全双工的管道,单个 STREAMS 管道就能实现父、子进程之间的双向的数据流操作。下面是基于 STREAMS 的管道两种方式:
基于 STREAMS 管道的内部结构,它包含两个流首,每个流首的写队列(WQ)指向另一个流首的读队列(RQ),写入管道一端的数据被放入另一端的读队列的消息中。 STREAMS 管道是一个流,可将一个 STREAMS 模块压入到该管道的任一端,但是,如果在一端压入了一个模块,那么并不能在另一端弹出模块,若要删除,只能从原压入端删除。下图是基于 STREAMS 管道的内部结构和带模块的 STREAM 管道的内部结构:
创建基于 STREAMS 管道只是对普通管道进行接口实现,其实现如下:
#include "apue.h" /* * Return a STREAMS-based pipe, with the two file descirptors * returned in fd[0] and fd[1]. */ int s_pipe(int fd[2]) { return(pipe(fd)); }
17.2.1 命名的 STREAMS 管道
命名的 STREAMS 管道和 FIFO 管道一样克服了管道的局限性,使其可以在没有亲缘关系的进程间通信,命名的 STREAMS 管道机制通过一种途径,使得进程可以给予管道一个文件系统中的名字,使其能够实现双向通信,避免了 FIFO 管道的单向通信。为了使进程给予管道一个文件系统中的名字,可以调用函数 fattach 使进程给予 STREAMS 管道一个文件系统中的名字,其定义如下:
/* 命名STREAM 管道 */ /* * 函数功能:使进程给予STREAM管道一个文件系统中的名字; * 返回值:若成功则返回0,若出错则返回-1; * 函数原型: */ #include <stropts.h> int fattach(int filedes, const char *path); /* * 说明: * path必须是引用一个现有的文件,且对该文件具有写权限; */ /* * 函数功能:撤销STREAM管道与一个文件系统中的名字的关联; * 返回值:若成功则返回0,若出错则返回-1; * 函数原型: */ #include <stropts.h> int fdetach(const char *path);
一旦 STREAMS 管道连接到文件系统名字空间,那么原来使用该名字的底层文件就不再是可访问的。打开该名字的任一进程将能访问相应管道,而不是访问原先的文件。在调用 fattach 之前打开底层文件的任一进程可以继续访问该文件。确实,一般而言,这些进程并不知道该名字现在引用了另外一个文件。
在调用 fdetach 函数之后,先前依靠打开path 而能访问 STREAMS 管道的进程仍可继续访问该管道,但是在此之后打开 path 的进程将访问驻留在文件系统中的底层文件。
17.2.2 唯一连接
将 STREAMS 管道的一端连接到文件系统的名字空间后,如果多个进程都使用命名 STREAMS 管道与服务器进程通信时,会出现通信混乱。为了解决多进程访问出现的问题,在 STREAMS 管道压入一个模块,即服务器进程将模块压入到要被连接管道的一端。其实现如下图所示:
/* * 函数功能:创建在无关进程之间的唯一连接; * 函数原型: */ #include "apue.h" int serv_listen(const char *name); /* 返回值:若成功则返回要侦听的文件描述符,出错则返回负值;*/ /* * 说明: * 服务器进程调用serv_listen函数声明要在文件系统中的某个路径侦听客户进程的连接请求; 当客户端想要连接至服务器进程,就将使用该文件系统中的名字,该函数返回值是STREAMS管道的服务器进程端; * int serv_accept(int listenfd, uid_t *uidptr); /* 返回值:若成功则返回新文件描述符,出错则返回负值 */ * 服务器进程调用serv_accept函数等待客户进程连接请求的到达,当一个请求到达时,系统自动创建一个新的STREAMS管道, * 该函数向服务器进程返回该STREAMS管道的一端,客户进程的有效用户ID存放在uidptr所指向的存储区中; * int cli_conn(const char *name); /* 返回值:若成功则返回文件描述符,出错则返回负值 */ * 客户端进程调用cli_conn函数连接至服务器进程,客户端进程指定的参数name必须和服务器进程调用serv_listen函数时所用的参数name相同; * 该函数返回时,客户端进程得到连接至服务器进程的文件描述符; * */
使用这些函数可以创建在无关进程之间的唯一连接。这些函数模仿了在16.4 节中讨论过的面向连接的套接字函数。上面三个函数的具体实现如下:
#include "apue.h" #include <fcntl.h> #include <stropts.h> /* pipe permissions: user rw, group rw, others rw */ #define FILE_MODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH) /* * Establish an endpoint to listen for connect requests. * Returns fd if all ok, <0 on error */ int serv_listen(const char *name) { int tempfd; int fd[2]; /* * Create a file: mount point for fattach(). */ unlink(name); if((tempfd = creat(name, FIFO_MODE)) < 0) return(-1); if(close(tempfd) < 0) return(-2); if(pipe(fd) < 0) return(-3); /* * Push connld & fattach() on fd[1]. */ if(ioctl(fd[1], I_PUSH, "connld") < 0) { close(fd[0]); close(fd[1]); return(-4); } if(fattach(fd[1], name) < 0) { close(fd[0]); close(fd[1]); return(-5); } close(fd[1]); /* fattach holds this end open */ return(fd[0]); /* fd[0] is where client connections arrive */ }
#include "apue.h" #include <stropts.h> /* * Wait for a client connection to arrive, and accept it. * We also obtain the client's user ID. * Return new fd if all ok, <0 on error. */ int serv_accept(int listenfd, uid_t *uidptr) { struct strrecvfd recvfd; if(ioctl(listenfd, I_RECVFD, &recvfd) < 0) return(-1); /* could be EINTR if signal caught */ if(uidptr != NULL) *uidptr = recvfd.uid; /* effective uid of caller */ return(recvfd.fd); /* return the new descriptor */ }
#include "apue.h" #include <fcntl.h> #include <stropts.h> /* * Create a client endpoint and connect to a server. * Return fd if all ok, <0 on error. */ int cli_conn(const char *name) { int fd; /* open the mounted stream */ if((fd = open(name, O_RDWR)) < 0) return(-1); if(isastream(fd) == 0) { close(fd); return(-2); } return(fd); }
17.3 UNIX 域套接字
UNIX 域套接字可在用一台机器上实现进程间通信。虽然因特网域套接字可用于同一目的,但UNIX域套接字的效率更高。因为 UNIX 域套接字仅仅复制数据,不执行协议处理,不需要添加或删除网络报头,无需验证和,不产生顺序号,无需发送确认报文。
UNIX域套接字提供流和数据报两种接口,UNIX域数据报服务是可靠的,既不会丢失消息也不会传递出错。UNIX域套接字是套接字和管道之间的混合物。为了创建一对非命名的,相互连接的UNXI域套接字,用户可以使用socketpair函数。其实现如下:
#include <sys/socket.h> int socketpair(int domain, int type, int protocol, int sockfd[2]); /* 返回值:若成功则返回0,出错则返回-1 */ #include "apue.h" #include <sys/socket.h> /* * Return a full-duplex "stream" pipe (a UNIX domain socket) * with the two file descriptors returned in fd[0] and fd[1]. */ int s_pipe(int fd[2]) { return(socketpair(AF_UNIX, SOCK_STREAM, 0, fd)); }
17.3.1 命名 UNIX 域套接字
虽然socketpair函数创建相互连接的一对套接字,但是每一个套接字都没有名字。这意味着无关进程不能使用它们。在16.3.4节,我们学习了如何将一个地址绑定一因特网域套接字。恰如因特网域套接字一样,我们也可以命名UNIX域套接字,并可将其用于告示服务。
命名 UNIX 域套接字也是针对没有亲缘关系进程之间的通信,它的地址结构和因特网的地址结构不同,其地址结构如下:
struct sockaddr_un{ sa_family_t sun_family; /* AF_UNIX */ char sun_path[108]; /* pathname */ };
sockaddr_un 结构的 sun_path 成员包含一路径名。当我们将以地址绑定至UNIX域套接字时,系统用该路径名创建一类型为S_IFSOCK的文件。该文件仅用于向客户进程告知套接字名字。该文件不能打开,也不能由应用程序用于通信。如果当我们试图绑定地址时,该文件已经存在,那么bind请求失败。当关闭套接字时,并不自动删除该文件,所以我们必须确保在应用程序终止前,对该文件执行解除链接操作。下面例子是实现地址绑定到 UNIX 域套接字:
#include "apue.h" #include <sys/socket.h> #include <sys/un.h> int main(void) { int fd, size; struct sockaddr_un un; un.sun_family = AF_UNIX; strcpy(un.sun_path, "foo.socket"); if((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) err_sys("socket failed"); size = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path); //offsetof宏计算sun_path成员在sockaddr_un结构中的偏移量 if(bind(fd, (struct sockaddr *)&un, size) < 0) err_sys("bind failed"); printf("UNIX domain socket bound\n"); exit(0); }
输出结果:
[root@chenchen 17]# ./a.outUNIX domain socket bound[root@chenchen 17]# ll总计 12-rw-r--r-- 1 root root 553 01-14 14:42 17-1.c-rwxr-xr-x 1 root root 6991 01-14 14:42a.outsrwxr-xr-x 1 root root 0 01-14 14:44 foo.socket[root@chenchen 17]# ./a.outbind failed: Address already in use[root@chenchen 17]# rm -f foo.socket[root@chenchen 17]# ./a.outUNIX domain socket bound[root@chenchen 17]#
当运行程序时,bind请求成功执行,但是如若第二次运行该程序,则出错返回,其原因是该文件已经存在。
17.3.2 唯一连接
服务器进程可以使用标准bind、listen 和 accept 函数,为客户进程安排一个唯一的 UNIX 域连接。客户进程使用 connect 与服务器进程关联;服务器进程接受了 connect 请求后,在服务器进程和客户进程之间就存在了唯一连接。以下是 UNIX 域套接字唯一连接的实现(与因特网域套接字的操作类似):
UNIX域套接字的serv_listen函数
#include "apue.h" #include <sys/socket.h> #include <sys/un.h> #include <errno.h> #define QLEN 10 /* * Create a server endpoint of a connection. * Return fd if all ok, <0 on error. */ int serv_listen(const char *name) { int fd, len, err, rval; struct sockaddr_un un; /* create a UNIX domain stream socket */ if((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) return(-1); unlink(name); /* in case it already exists */ /* fill in socket address structure */ memset(&un, 0, sizeof(un)); un.sun_family = AF_UNIX; strcpy(un.sun_path, name); len = offsetof(struct sockaddr_un, sun_path) + strlen(name); /* bind the name to the descriptor */ if(bind(fd, (struct sockaddr *)&un, len) < 0) { rval = -2; goto errout; } if(listen(fd, QLEN) < 0) /* tell kernel we're a server */ { rval = -3; goto errout; } return(fd); errout: err = errno; close(fd); errno = err; return(rval); }
上面的程序先调用socket创建一个UNIX域套接字,然后将路径名填入sockaddr_un结构,再调用bind绑定,最后调用listen函数以通知内核该进程将作为服务器进程等待客户进程的连接请求,当收到一个客户进程的连接请求后,服务器进程调用serv_accept函数。
UNIX域套接字的serv_accept函数
#include "apue.h" #include <sys/socket.h> #include <sys/un.h> #include <time.h> #include <errno.h> #define STALE 30 /* client's name can't be older than this (sec) */ /* * Wait for a client connection to arrive, and accept it. * We also obtain the client's usr ID from the pathname * that it must bind before calling us. * Returns new fd if all ok, <0 on error */ int serv_accept(int listenfd, uid_t *uidptr) { int clifd, len, err, rval; time_t staletime; struct sockaddr_un un; struct stat statbuf; len = sizeof(un); if((clifd = accept(listenfd, (struct sockaddr *)&un, &len)) < 0) return(-1); /* often errno=EINTR, if signal caught */ /* obtain the client's uid from its calling address */ len -= offsetof(struct sockaddr_un, sun_path); /* len of pathname */ un.sun_path[len] = 0; /* null terminate */ if(stat(un.sun_path, &statbuf) < 0) { rval = -2; goto errout; } #ifdef S_ISSOCK /* not defined fro SVR4 */ if(S_ISSOCK(statbuf.st_mode) == 0) { rval = -3; /* not a socket */ goto errout; } #endif if((statbuf.st_mode & (S_IRWXG | S_IRWXO)) || (statbuf.st_mode & S_IRWXU) != S_IRWXU) { rval = -4; /* is not rwx------ */ goto errout; } staletime = time(NULL) - STALE; if(statbuf.st_atime < staletime || statbuf.st_ctime < staletime || statbuf.st_mtime < staletime) { rval = -5; /* i-node is too old */ goto errout; } if(uidptr != NULL) *uidptr = statbuf.st_uid; /* return uid of caller */ unlink(un.sun_path); /* we're done with pathname now */ return(clifd); errout: err = errno; close(clifd); errno = err; return(rval); }
客户进程调用cli_conn函数对服务器进程的连接进行初始化
#include "apue.h" #include <sys/socket.h> #include <sys/un.h> #include <errno.h> #define CLI_PATH "/var/tmp/" /* +5 for pid = 14 chars */ #define CLI_PERM S_IRWXU /* rwx for user only */ /* * Create a client endpoint and connect to a server. * Returns fd if all ok, <0 on error. */ int cli_conn(const char *name) { int fd, len, err, rval; struct sockaddr_un un; /* create a UNIX domain stream socket */ if((fd = socket(AF_UNIX, SOCK_STREM, 0)) < 0) return(-1); /* fill socket address structure with our address */ memset(&un, 0, sizeof(un)); un.sun_family = AF_UNIX; sprintf(un.sun_path, "%s%05d", CLI_PATH, getpid()); len = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path); unlink(un.sun_path); /* in case it already exits */ if(bind(fd, (struct sockaddr *)&un, len) < 0) { rval = -2; goto errout; } if(chmod(un.sun_path, CLI_PERM) < 0) { rval = -3; goto errout; } /* fill socket address structure with server's address */ memset(&un, 0, sizeof(un)); un.sun_family = AF_UNIX; strcpy(un.sun_path, name); len = offsetof(struct sockaddr_un, sun_path) + strlen(name); if(connect(fd, (struct sockaddr *)&un, len) < 0) { rval = -4; goto errout; } return(fd); errout: err = errno; close(fd); errno = err; return(rval); }
调用socket创建UNIX域套接字的客户进程端,然后用客户进程专有的名字填入sockaddr_un结构。
我们不让系统为我们选择默认地址,而是绑定自己的地址。绑定的路径名的最后5个字符来自客户进程ID,调用unlink,以防路径名已经存在。然后调用bind将名字赋予客户进程套接字。在文件系统中创建了一个套接字文件。然后,调用chmod关闭用户读,用户写以及用户执行以外的其他权限。
最后用服务器进程的路径名填充另一个sockaddr_un结构,然后调用connect函数初始化与服务器进程的连接。
- UNIX环境高级编程-第17章- 高级进程间通信 - 一
- 《UNIX环境高级编程》第15章 进程间通信
- UNIX环境高级编程(第17章 高级进程间通信)
- 《UNIX环境高级编程》第17章 高级进程间通信
- UNIX环境高级编程-第15章- 进程间通信 - 一
- unix环境高级编程-进程间通信
- UNIX环境高级编程-第8章- 进程控制 - 一
- 《UNIX环境高级编程》十七高级进程间通信读书笔记
- UNIX环境高级编程第十五章 进程间通信 总结
- UNIX环境高级编程-第15章- 进程间通信 - 二
- UNIX环境高级编程(第15章 进程间通信)
- 《Unix环境高级编程》之 进程通信
- UNIX环境高级编程第十七章 高级进程间通信 总结
- 进程环境 - UNIX环境高级编程-第7章
- UNIX环境高级编程(第7章 进程环境)
- UNIX环境高级编程-第7章- 进程环境
- UNIX环境高级编程之第7章:进程环境
- 《UNIX环境高级编程》第7章 进程环境
- CComboBox控件的用法
- 【JNI】JNI编程模型结构
- 整形数int、浮点型数据float,在内存中存储的表示
- 灰度图像--频域滤波 傅里叶变换之采样定理
- Maven安装配置
- UNIX环境高级编程-第17章- 高级进程间通信 - 一
- 计算UNDO恢复完成时间
- 如何让一个IFRAME调用页面的背景为透明
- Java程序员到架构师的推荐阅读书籍
- itext 生成pdf文档
- android异步下载网络图片(二)
- LeetCode88——Merge Sorted Array
- tomkeeper 的DEP/ASLR bypass without ROP/JIT
- C++ 文件操作