管道
匿名管道
匿名管道通过一个文件描述符沟通,这个pipe是单工的。因为使用文件描述符,所以只能在同一个进程或者子进程之间通信。
在父进程中一般有这样的形式:
close(pipefd[0]); /* 关闭 read end */ write(pipefd[1], argv[1], strlen(argv[1])); close(pipefd[1]); /* Reader will see EOF */ wait(NULL); /* Wait for child(同步) */ exit(EXIT_SUCCESS);
这里我们把父进程当作write进程,子进程当作read进程,其实反过来也一样。pipefd是一个二元数组,用来管理管道的read end 和 write end。
创建管道的方式是使用pipe()
if (pipe(pipefd) == -1) { perror("pipe"); exit(EXIT_FAILURE); }
子进程中的形式与父进程类似
close(pipefd[1]); /* Close unused write end */ while (read(pipefd[0], &buf, 1) > 0) write(STDOUT_FILENO, &buf, 1); write(STDOUT_FILENO, " ", 1); close(pipefd[0]); _exit(EXIT_SUCCESS); /* _exit是一个子进程版的exit,可以避免执行atexit */
具名管道FIFO
在shell中使用mkfifo fifo就可以创建一个具名管道,在文件夹下可以看到一个fifo,用ls -l fifo可以看到以p开头,说明这是一个管道。
mknod fifo c/b/p 可以决定创建的fifo的文件类型,通过https://en.wikipedia.org/wiki/Device_file了解block special和character special文件的区别。
在unix函数中使用mkfifo(path, umask),path必须是绝对路径,umask推荐使用定义于#include <sys/stat.h>中的常量(跨平台),当然使用8进制的umask也可以。
POSIX线程
pthread_create可以创建一个线程,第一个参数是表示类型为pthread_t的线程的ID,第二个参数是在新线程中执行的例程,该例程应该被定义为void *类型,第四个参数是可以被传入的参数。
pthread_join传入线程的ID等待指定的线程ID结束。
#include<unistd.h> #include<stdlib.h> #include<stdio.h> #include<pthread.h> #include<fcntl.h> #include <sys/stat.h> #define FIFO "test_fifo" void *read_fifo(void* dummy) { int fd = open(FIFO, O_RDONLY); char read_buf[1024]; read(fd, read_buf, 1024); write(STDOUT_FILENO, read_buf, 1024); close(fd); return NULL; } void *write_fifo(void* dummy) { int fd = open(FIFO, O_WRONLY); write(fd, "test1 running now. ",19); close(fd); return NULL; } void test1() { pthread_t tid1; pthread_t tid2; mkfifo(FIFO, S_IRUSR | S_IWUSR| S_IRGRP|S_IWGRP); pthread_create(&tid1, NULL, read_fifo, NULL); pthread_create(&tid2, NULL, write_fifo, NULL); pthread_join(tid1,NULL); pthread_join(tid2,NULL); } int main() { test1(); }
三兄弟
message queue、semaphere、shared memory,三者相似,但有不同实现,比如有POSIX,有XSI,有Sysv,意思相近,部分API不同。
早期的Unix通信方式有诸多缺点,如上的管道只能传递无格式的字节流,而且缓存区大小有限;另外如信号要求接受信号的进程在一个时间范围内反应,有时效性。这里将要介绍的消息队列随内核存在(持久),而且使用链表作为数据结构,可以指定特定的数据格式和优先级。
IPC可以根据持续时间的不同分为三种类型:
- 随进程持续:IPC一直存在到打开IPC对象的最后一个进程关闭该对象为止。如管道和有名管道;
- 随内核持续:IPC一直持续到内核重新自举或者显示删除该对象为止。如消息队列、信号灯以及共享内存等;
- 随文件系统持续:IPC一直持续到显示删除该对象为止。
消息队列
消息队列的API如下
#include <sys/types.h> #include <sys/ipc.h> key_t ftok (char*pathname, char proj); //key=ftok(path_ptr, 'a'); // ipc_id=ipc(MSGGET, (int)key, flags,0,NULL,0); //linux系统提供的原生API只有一个ipc函数,这里介绍SysV的进一层包装 #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgget(key_t key, int msgflg); int msgrcv(int msqid, struct msgbuf *msgp, int msgsz, long msgtyp, int msgflg); int msgsnd(int msqid, struct msgbuf *msgp, int msgsz, int msgflg);
1)int msgget(key_t key, int msgflg)
参数key是一个键值,由ftok获得;msgflg参数是一些标志位。该调用返回与健值key相对应的消息队列描述字。
在以下两种情况下,该调用将创建一个新的消息队列:
- 如果没有消息队列与健值key相对应,并且msgflg中包含了IPC_CREAT标志位;
- key参数为IPC_PRIVATE;
参数msgflg可以为以下:IPC_CREAT、IPC_EXCL、IPC_NOWAIT或三者的或结果。
调用返回:成功返回消息队列描述字,否则返回-1。
注:参数key设置成常数IPC_PRIVATE并不意味着其他进程不能访问该消息队列,只意味着即将创建新的消息队列。
2)int msgrcv(int msqid, struct msgbuf *msgp, int msgsz, long msgtyp, int msgflg);
该系统调用从msgid代表的消息队列中读取一个消息,并把消息存储在msgp指向的msgbuf结构中。
msqid为消息队列描述字;消息返回后存储在msgp指向的地址,msgsz指定msgbuf的mtext成员的长度(即消息内容的长度),msgtyp为请求读取的消息类型;读消息标志msgflg可以为以下几个常值的或:
- IPC_NOWAIT 如果没有满足条件的消息,调用立即返回,此时,errno=ENOMSG
- IPC_EXCEPT 与msgtyp>0配合使用,返回队列中第一个类型不为msgtyp的消息
- IPC_NOERROR 如果队列中满足条件的消息内容大于所请求的msgsz字节,则把该消息截断,截断部分将丢失。
msgrcv手册中详细给出了消息类型取不同值时(>0; <0; =0),调用将返回消息队列中的哪个消息。
msgrcv()解除阻塞的条件有三个:
- 消息队列中有了满足条件的消息;
- msqid代表的消息队列被删除;
- 调用msgrcv()的进程被信号中断;
调用返回:成功返回读出消息的实际字节数,否则返回-1。
3)int msgsnd(int msqid, struct msgbuf *msgp, int msgsz, int msgflg);
向msgid代表的消息队列发送一个消息,即将发送的消息存储在msgp指向的msgbuf结构中,消息的大小由msgze指定。
对发送消息来说,有意义的msgflg标志为IPC_NOWAIT,指明在消息队列没有足够空间容纳要发送的消息时,msgsnd是否等待。造成msgsnd()等待的条件有两种:
- 当前消息的大小与当前消息队列中的字节数之和超过了消息队列的总容量;
- 当前消息队列的消息数(单位"个")不小于消息队列的总容量(单位"字节数"),此时,虽然消息队列中的消息数目很多,但基本上都只有一个字节。
msgsnd()解除阻塞的条件有三个:
- 不满足上述两个条件,即消息队列中有容纳该消息的空间;
- msqid代表的消息队列被删除;
- 调用msgsnd()的进程被信号中断;
调用返回:成功返回0,否则返回-1。
4)int msgctl(int msqid, int cmd, struct msqid_ds *buf);
该系统调用对由msqid标识的消息队列执行cmd操作,共有三种cmd操作:IPC_STAT、IPC_SET 、IPC_RMID。
- IPC_STAT:该命令用来获取消息队列信息,返回的信息存贮在buf指向的msqid结构中;
- IPC_SET:该命令用来设置消息队列的属性,要设置的属性存储在buf指向的msqid结构中;可设置属性包括:msg_perm.uid、msg_perm.gid、msg_perm.mode以及msg_qbytes,同时,也影响msg_ctime成员。
- IPC_RMID:删除msqid标识的消息队列;
调用返回:成功返回0,否则返回-1。
信号量
DOMAN SOCKET
与internet socket类似,domain socket使用的是sockaddr_un的结构,为方便说明,假设声明了sockaddr_un un。
我们同样使用bind来连接
API: int bind(int sockfd, struct sockaddr *my_addr, int addrlen);
Usage: bind(fd, (struct sockaddr*)&un, size)
--fd是调用socket得到的套接字描述符,我们使用AF_UNIX或AF_LOCAL表示这是一个domain socket
API:int socket(int domain, int type, int protocol);
Usage: socket(AF_UNIX, SOCK_STREAM, 0);
--size 是un的长度,()