1、半双工管道
简单实现
半双工管道可以实现父进程和子进程之间或者子进程之间(前提是有共同的祖先)的通信
因为是半双工,所以两端不可能同时读取,而是一端读一端取,而且当一端分配到读任务后,那么他就固定了,不能再担当写的角色了,相反亦然。
测试程序如下:
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <unistd.h>
- #include <sys/types.h>
- int main(void)
- {
- int fd[2],nbytes;
- pid_t childpid;
- char string[] = "Hello, World!\n";
- char readbuffer[80];
- pipe(fd);
- int w_fd = fd[1];//写
- int r_fd = fd[0];//读
- if((childpid = fork()) == -1)
- {
- perror("fork");
- exit(1);
- }
- if(childpid == 0)
- {
- close(r_fd);//关闭读
- write(w_fd,string,strlen(string));
- exit(0);
- }
- else
- {
- close(w_fd);关闭写
- nbytes = read(r_fd, readbuffer,sizeof(readbuffer));
- printf("Received string:%s\n",readbuffer);
- }
- return 0;
- }
不能看出,为什么半双工管道两端是单一角色了,因为开始读或者写之前必须关闭写或读的fd
半双工管道的阻塞性
写端对读端具有依赖性:假如读端被关闭了,那么再写入管道就没有意义的,此时写入管道会返回-1。
阻塞性:
上例程序的这句代码 bytes = read(r_fd, readbuffer,sizeof(readbuffer));
指明了管道的大小,即为sizeof(readbuffer),写入比该数子大的数据时,先只会写sizeof(readbuffer)个数据到管道,然后写端阻塞,等待读端取走数据,然后按同样的规则写入剩余的部分,这个也体现了写入操作的非原子性。写请求字节数还有一个最大阀值,在/usr/include/linux有文件 limits.h中宏定义
#define PATH_MAX 4096
此时,假如指明的管道大小大于PATH_MAX ,系统会按这个PATH_MAX 作为写请求最大字节数。
2、命名管道
先说说命名管道相比半双工管道的优势,不再需要进程之间有亲属关系了,因为是以一种文件的形式存在,所以对文件的大部分操作都支持。建立命名管道的方法为int makefifo(const char *pathname, mode_t mode);
先解释下这个函数,pathname为管道名称,mode为建立管道的选项,返回值0为成功,-1为失败。
命名管道的阻塞性:
阻塞性可通过mode参数指定:
1、当以阻塞(未指定O_NONBLOCK)方式只读打开FIFO的时候,则将会被阻塞,知道有其他进程以写方式打开该FIFO。
2、类似的,当以阻塞(未指定O_NONBLOCK)方式只写打开FIFO的时候,则将会被阻塞,知道有其他进程以读方式打开该FIFO。
3、当以非阻塞方式(指定O_NONBLOCK)方式只读打开FIFO的时候,则立即返回-1,其errno是ENXIO。
测试程序
server.c
- #include<stdio.h>
- #include<sys/types.h>
- #include<sys/stat.h>
- #include<fcntl.h>
- #include<unistd.h>
- #define FIFO_CHANNEL "my_fifo" /* 宏定义,fifo路径 */
- int main()
- {
- int fd;
- char buf[80];
- if(mkfifo(FIFO_CHANNEL,0777)==-1) /* 创建命名管道,返回-1表示失败 */
- {
- perror("Can't create FIFO channel");
- return 1;
- }
- if((fd=open(FIFO_CHANNEL,O_RDONLY))==-1) /* 以只读方式打开命名管道 */
- {
- perror("Can't open the FIFO");
- return 1;
- }
- while(1) /* 不断从管道中读取信息 */
- {
- read( fd, buf, sizeof(buf) );
- printf("Message from Client: %s\n",buf );
- sleep(3); /* sleep 3s */
- }
- close(fd); /* 关闭管道 */
- return 0;
- }
client.c
- #include<stdio.h>
- #include<sys/types.h>
- #include<sys/stat.h>
- #include<fcntl.h>
- #include<unistd.h>
- #define FIFO_CHANNEL "my_fifo" /* 宏定义,fifo路径 */
- int main()
- {
- int fd;
- char s[]="Hello!";
- if((fd=open(FIFO_CHANNEL,O_WRONLY))==-1) /* 以读写方式打开命名管道,返回-1代表失败 */
- {
- perror("Can't open the FIFO");
- return 1;
- }
- while(1) /* 不断向管道中写信息 */
- {
- write( fd, s, sizeof(s) );
- printf("Write: %s\n",s);
- sleep(3); /* sleep 3s */
- }
- close(fd); /* 关闭管道 */
- return 0;
- }
结果截图:
此时文件系统中会多一个my_fifo的特殊文件。
3、消息队列
先介绍消息队列常用函数
- #include <sys/types.h>
- #include <sys/ipc.h>
- key_t ftok(const char* pathname,int proj_id);
注意: pathname必须是已经存在的目录
系统建立IPC通讯(如消息队列、共享内存时)必须指定一个ID值。通常情况下,该id值通过该ftok函数得到。
需要用 mkdir -p /ipc/msg
- #include <sys/types.h>
- #include <sys/ipc.h>
- #include <sys/msg.h>
- int msgget(key_t key,int msgflag);
获取消息的msgget()函数,第一个参数为键值,msgflag可以指定参数
IPC_CREATE:如果在内存中不存在该队列,则创建它
IPC_EXCL:当与IPC_CREATE一起使用时,如果队列早已存在则将出错
- #include <sys/types.h>
- #include <sys/ipc.h>
- #include <sys/msg.h>
- int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
发送消息msgsnd()函数,第一个参数为msgget返回值,第二个参数为消息缓冲区,第三个参数为消息长度,msgflg可以设置为0(表示忽略),也可只是为IPC_NOWAIT,如果消息队列已满,则消息将不会被写入队列中,未设IPC_NOWAIT,将会阻塞,直到可以写消息为止。
- #include <sys/types.h>
- #include <sys/ipc.h>
- #include <sys/msg.h>
- ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
接受消息msgrcv,第一个参数由msgget指定,第二个参数指定缓冲区,第三个参数代表缓冲区大小,不包括mtype成员的长度,第四个参数指定要从队列中获取的消息类型。msgflg可设置为IPC_NOWAIT功能同上。
- #include <sys/types.h>
- #include <sys/ipc.h>
- #include <sys/msg.h>
- int msgctl(int msqid, int cmd, struct msqid_ds *buf);
消息控制msgctl函数,该函数向内核发送一个cmd命令,内核根据此来判断进行何种操作。
IPC_STAT 获取队列的msqid _ds结构
IPC_SET 设置队列的msqid_ds结构的ipc_perm成员值
IPC_RMID内核删除队列
以下为消息队列的一个例子:
- <p>#include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <sys/types.h>
- #include <sys/msg.h>
- #include <unistd.h>
- #include <sys/ipc.h></p><p>void msg_show_attr(int msg_id, struct msqid_ds msg_info)
- {
- int ret = -1;
- sleep(1);
- ret = msgctl(msg_id, IPC_STAT, &msg_info);
- if( -1 == ret)
- {
- printf("获得消息信息失败\n");
- return ;
- }
- printf("\n");
- printf("现在队列中的字节数:%d\n",msg_info.msg_cbytes);
- printf("队列中消息数:%d\n",msg_info.msg_qnum);
- printf("队列中最大字节数:%d\n",msg_info.msg_qbytes);
- printf("最后发送消息的进程pid:%d\n",msg_info.msg_lspid);
- printf("最后接收消息的进程pid:%d\n",msg_info.msg_lrpid);
- printf("最后发送消息的时间:%s",ctime(&(msg_info.msg_stime)));
- printf("最后接收消息的时间:%s",ctime(&(msg_info.msg_rtime)));
- printf("最后变化时间:%s",ctime(&(msg_info.msg_ctime)));
- printf("消息UID是:%d\n",msg_info.msg_perm.uid);
- printf("消息GID是:%d\n",msg_info.msg_perm.gid);
- }</p><p>
- int main(void)
- {
- int ret = -1;
- int msg_flags, msg_id;
- key_t key;
- struct msgmbuf{
- int mtype;
- char mtext[10];
- };
- struct msqid_ds msg_info;
- struct msgmbuf msg_mbuf;
- int msg_sflags,msg_rflags;
- char *msgpath = "/ipc/msg/";
- key = ftok(msgpath,'b');
- if(key != -1)
- {
- printf("成功建立KEY\n");
- }
- else
- {
- printf("建立KEY失败\n");
- }
- msg_flags = IPC_CREAT|IPC_EXCL;
- msg_id = msgget(key, msg_flags|0x0666);
- if( -1 == msg_id)
- {
- printf("消息建立失败\n");
- return 0;
- }
- msg_show_attr(msg_id, msg_info);
- msg_sflags = IPC_NOWAIT;
- msg_mbuf.mtype = 10;
- memcpy(msg_mbuf.mtext,"测试消息",sizeof("测试消息"));
- ret = msgsnd(msg_id, &msg_mbuf, sizeof("测试消息"), msg_sflags);
- if( -1 == ret)
- {
- printf("发送消息失败\n");
- }
- msg_show_attr(msg_id, msg_info);
- msg_rflags = IPC_NOWAIT|MSG_NOERROR;
- ret = msgrcv(msg_id, &msg_mbuf, 10,10,msg_rflags);
- if( -1 == ret)
- {
- printf("接收消息失败\n");
- }
- else
- {
- printf("接收消息成功,长度:%d\n",ret);
- }
- msg_show_attr(msg_id, msg_info);
- msg_info.msg_perm.uid = 8;
- msg_info.msg_perm.gid = 8;
- msg_info.msg_qbytes = 12345;
- ret = msgctl(msg_id, IPC_SET, &msg_info);
- if( -1 == ret)
- {
- printf("设置消息属性失败\n");
- return 0;
- }
- msg_show_attr(msg_id, msg_info);
- ret = msgctl(msg_id, IPC_RMID,NULL);
- if(-1 == ret)
- {
- printf("删除消息失败\n");
- return 0;
- }
- return 0;
- }</p>