常用的IPC分为两个类别,一是基于文件,而是基于内存
基于文件的分别有匿名管道,有名管道,普通的文件共享,socket文件
基于内存的有普通内存共享(本文章没有介绍),共享内存,共享信号量,消息队列
如果要看基于内存的IPC,请参考:http://blog.csdn.net/xiaoxiaopengbo/article/details/78431042
本文就针对linux基于文件的IPC
一.匿名管道pipe
管道是一种最基本的IPC机制,作用于有血缘关系的进程之间,完成数据传递。调用pipe系统函数即可创建一个管道。有如下特质:
- 其本质是一个伪文件(实为内核缓冲区)
- 由两个文件描述符引用,一个表示读端,一个表示写端。
- 规定数据从管道的写端流入管道,从读端流出。
管道的原理: 管道实为内核使用环形队列机制,借助内核缓冲区(4k)实现。
管道的局限性:
1) 数据一旦被读走,便不在管道中存在,不可反复读取。
2) 由于管道采用半双工通信方式。因此,数据只能在一个方向上流动。
3)只能在有公共祖先的进程间使用管道。
**常见的通信方式有:**单工通信、半双工通信、全双工通信。
函数原形
#include <unistd.h>
int pipe(int pipefd[2]);
返回值
成功:0;失败:-1,设置errno
Pipefd参数是一个入参,函数调用成功返回r/w两个文件描述符。无需open,但需手动close。规定:fd[0]→ r; fd[1] → w,就像0对应标准输入,1对应标准输出一样。向管道文件读写数据其实是在读写内核缓冲区。
有记下几种编程模型:单进程半双工,父子进程半双工
下图是单进程的半双工编程模型,并附带程序源码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
intfd[2];
intr;
charbuf[40];
printf("%d\n",getpid());
r=pipe(fd);
write(fd[1],"hello",5);
write(fd[1],"world",5);
r=read(fd[0],buf,20);
buf[r]=0;
printf("1::%s\n",buf);
write(fd[1],"Iam writing to pipe",strlen("I am writing to pipe"));
r=read(fd[0],buf,20);
buf[r]=0;
printf("2::%s\n",buf);
r=read(fd[0],buf,20);
buf[r]=0;
printf("3::%s\n",buf);
while(1);
}
执行结果
结论:此程序证明了第一点局限数据一旦被读走,便不在管道中存在,不可反复读取。但是这种编程模型基本没有用途
下面来看看另外一种编程模型,父子进程之前的半双工,同样源码如下
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
int n;
int fd[2];
pid_t pid;
char line[20]={0};
if(pipe(fd) < 0)
printf("pipeerr:%m\n");
if((pid = fork()) < 0) {
printf("forkerr:%m\n");
}else if (pid > 0) { /* parent */
close(fd[0]);
write(fd[1],"hello world\n", 12);
}else { /* child */
close(fd[1]);
n= read(fd[0], line, 20);
printf("%s\n",line);
}
exit(0);
}
运行结果
二.有名管道FIFO
上面的匿名管道的缺点其实是很明显的,虽然可以进程间通信,但是只局限于有血缘关系的进程间通信,但是如果我想两个独立的进程用管道来通信呢?有名管道FIFO就有了用武的地方了
创建有名管道函数原形
函数原型:
#include <sys/types.h>
#include <sys/stat.h>
*int mkfifo(const char pathname, mode_tmode);
返回值
返回值 成功:0;失败:-1,设置errno
程序源码:
创建两个文件fifoA.c fifoB.c分别编译为A,B
fifoA.c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <signal.h>
#include <stdlib.h>
int fd;
int i=0;
void end(int s)
{
printf("catchctrl+c signal\n");
//closepipe
close(fd);
//deletepipe
unlink("my.pipe");
exit(-1);
}
int main()
{
signal(SIGINT,end);
//createpipe file
mkfifo("my.pipe",0666);
//openpipe file
fd=open("my.pipe",O_RDWR);
//writedata per 1 sec
while(1)
{
sleep(1);
write(fd,&i,4);
i++;
}
}
fifoB.c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <signal.h>
#include <stdlib.h>
int fd;
int i;
void end(int s)
{
printf("catchctrl+c signal\n");
//closepipe
close(fd);
exit(-1);
}
int main()
{
signal(SIGINT,end);
//openpipe file
fd=open("my.pipe",O_RDWR);
//readdata
while(1)
{
read(fd,&i,4);
printf("%d\n",i);
}
}
先运行fifoA.c的程序,发现就会创建一个
此时运行B程序,发现数据并没有丢失,一下会把之前的都打印出来
另外需要注意的是:FIFO和pipe一样,数据不会重复读出
三.基于文件共享的IPC
函数原型:
#include <sys/mman.h>
#include <fcntl.h>
// 开启映射
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
// 关闭映射
int munmap(void *start, size_t length);
函数形参含义:
*start: 指向欲映射的内存起始地址,通常设为 NULL,表示让系统自动选定地址,映射成功后返回该地址
length: 表示将文件中多大的部分映射到内存,即映射区的长度
prot: 映射区域的保护方式,不能与文件的打开模式冲突。可以为以下一种或几种方式,多种方式使用 or 组合("|")
PROT_EXEC: 映射区域可被执行
PROT_READ: 映射区域可被读取
PROT_WRITE: 映射区域可被写入
PROT_NONE: 映射区域不能存取
flags: 影响映射区域的各种特性,指定映射对象的类型,映射选项和映射页是否可以共享。在调用 mmap() 时必须要指定 MAP_SHARED 或 MAP_PRIVATE,flags 可以是以下一个或者多个值:
MAP_FIXED: 如果参数 start 所指的地址无法成功建立映射时,则放弃映射,不对地址做修正。通常不鼓励这样设置
MAP_SHARED: 对映射区域的写入数据会复制回文件内,而且允许其他映射该文件的进程共享。与其它所有映射这个对象的进程共享映射空间。对共享区的写入,相当于输出到文件。直到 msync() 或者 munmap() 被调用,文件实际上不会被更新
MAP_PRIVATE: 对映射区域的写入操作会产生一个映射文件的复制,即私人的“写入时复制”(copy on write)对此区域作的任何修改都不会写回原来的文件内容,写入不会影响到原文件。这个标志和 MAP_SHARED 是互斥的,只能使用其中之一
MAP_ANONYMOUS: 建立匿名映射。此时会忽略参数fd,不涉及文件,而且映射区域无法和其他进程共享。
MAP_DENYWRITE: 只允许对映射区域的写入操作,其他对文件直接写入的操作将会被拒绝
MAP_LOCKED: 将映射区域锁定住,这表示该区域不会被置换(swap),从而防止页面被交换出内存
MAP_NORESERVE: 不为这个映射保留交换空间。当交换空间被保留,对映射区修改的可能会得到保证。当交换空间不被保留,同时内存不足,对映射区的修改会引起段违例信号
MAP_LOCKED: 锁定映射区的页面,从而防止页面被交换出内存
MAP_GROWSDOWN: 用于堆栈,告诉内核 VM 系统,映射区可以向下扩展
MAP_POPULATE: 为文件映射通过预读的方式准备好页表。随后对映射区的访问不会被页违例阻塞
MAP_NONBLOCK: 仅和 MAP_POPULATE 一起使用时才有意义。不执行预读,只为已存在于内存中的页面建立页表入口
fd: 要映射到内存中的文件描述符
offset: 文件映射的偏移量,通常设置为 0,表示从文件起始位置对应,offset 必须是 page size 的整数倍
返回值:
若映射成功,mmap() 返回映射区的内存起始地址,munmap() 返回 0
若映射失败,mmap() 返回 MAP_FAILED,其值为(void *)-1,munmap() 返回 -1
源码:
分别有procA.c procB.c,分别编译成A,B可以执行程序
ProcA.c
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
main()
{
int*p;
intfd;
inti;
fd=open("tmp",O_RDWR|O_CREAT,0666);
ftruncate(fd,4);
p=mmap(0,4,PROT_READ|PROT_WRITE,
MAP_SHARED,fd,0);
i=0;
while(1)
{
sleep(1);
*p=i;
i++;
}
close(fd);
}
ProcB.c
#include <stdio.h>
#include <fcntl.h>e
#include <sys/mman.h>
main()
{
int*p;
intfd;
fd=open("tmp",O_RDWR);
p=mmap(0,4,PROT_READ|PROT_WRITE,
MAP_SHARED,fd,0);
while(1)
{
sleep(1);
printf("%d\n",*p);
}
close(fd);
}
运行结果
A程序每隔1s写一次数,写的数每次+1
运行B程序如图
发现还是结果会丢失的
四.socket文件的IPC
socket网络编程可能使用得最多,经常用在网络上不同主机之间的通信。其实在同一主机内通信也可以使用socket来完成,socket进程通信与网络通信使用的是统一套接口,只是地址结构与某些参数不同。在使用socket创建套接字时通过指定参数domain是af_inet(ipv4因特网域)或af_inet6(ipv6因特网域)或af_unix(unix域)来实现。
函数原型:
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, intprotocol);
第一个参数demain是协议簇
Name Purpose Man page
AF_UNIX, AF_LOCAL Local communication unix(7)
AF_INET IPv4 Internet protocols ip(7)
AF_INET6 IPv6 Internet protocols ipv6(7)
AF_IPX IPX - Novell protocols
AF_NETLINK Kernel user interface device netlink(7)
AF_X25 ITU-T X.25 / ISO-8208protocol x25(7)
AF_AX25 Amateur radio AX.25 protocol
AF_ATMPVC Access to raw ATM PVCs
AF_APPLETALK Appletalk ddp(7)
AF_PACKET Low level packet interface packet(7)
一般常用的就是前三个
Type参数有以下几个选择
SOCK_STREAM Provides sequenced, reliable, two-way,connection-based
byte streams. An out-of-band data transmission mecha‐
nism may be supported.
SOCK_DGRAM Supports datagrams (connectionless,unreliable messages
of a fixed maximumlength).
SOCK_SEQPACKET Provides a sequenced, reliable, two-way connection-
based data transmissionpath for datagrams of fixed
maximum length; a consumer is required to read anentire packet with each input system call.
SOCK_RAW Provides raw network protocol access.
SOCK_RDM Provides a reliable datagram layer thatdoes not guar‐antee ordering.
SOCK_PACKET Obsolete and should not be used in new programs; see packet(7).
第三个参数很少用,一般通过前两个参数就能确定要传输的协议,我通常都设为0
返回值
成功:返回文件描述符;失败:-1,设置errno
源码:
直接贴两个进程间通讯的
socketA.c socketB.c
socketA.c
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <linux/un.h>
int main()
{
intfd;
intr;
charbuf[200];
//1.建立socket
fd=socket(AF_UNIX,SOCK_DGRAM,0);
if(fd==-1)printf("socketerr:%m\n"),exit(-1);
printf("socket成功\n");
//2.构造本地文件地址
structsockaddr_un addr={0};
addr.sun_family=AF_UNIX;
memcpy(addr.sun_path,"my.sock",strlen("my.sock"));
//3.把socket绑定在地址上
r=bind(fd,(structsockaddr*)&addr,sizeof(addr));
if(r==-1)printf("binderr:%m\n"),exit(-1);
printf("地址绑定成功\n");
//4.接受数据
bzero(buf,sizeof(buf));
r=read(fd,buf,sizeof(buf));
printf("%s\n",buf);
//5.关闭通道
close(fd);
//6.删除socket文件
unlink("my.sock");
}
socketB.c
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <linux/un.h>
int main()
{
intfd;
intr;
charbuf[200];
structsockaddr_un addr={0};
//1.建立socket
fd=socket(AF_UNIX,SOCK_DGRAM,0);
if(fd==-1)printf("socketerr:%m\n"),exit(-1);
printf("socket成功\n");
//2.链接到指定地址
addr.sun_family=AF_UNIX;
memcpy(addr.sun_path,"my.sock",strlen("my.sock"));
r=connect(fd,(structsockaddr*)&addr,sizeof(addr));
if(r==-1)printf("binderr:%m\n"),exit(-1);
printf("地址连接成功\n");
//3.发送数据
write(fd,"hello!",strlen("hello"));
//4.关闭通道
close(fd);
}
运行结果
下面举一个网络通信的socket,编程模型是一致的,ipA.c ipB.c
ipA.c
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <linux/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main()
{
intfd;
intr;
charbuf[200];
//1.建立socket
fd=socket(AF_INET,SOCK_DGRAM,0);
if(fd==-1)printf("socketerr:%m\n"),exit(-1);
printf("socket成功\n");
//2.构造本地文件地址
structsockaddr_in addr={0};
addr.sin_family=AF_INET;
addr.sin_port=htons(9999);
addr.sin_addr.s_addr=inet_addr("192.168.1.102");
//3.把socket绑定在地址上
r=bind(fd,(structsockaddr*)&addr,sizeof(addr));
if(r==-1)printf("binderr:%m\n"),exit(-1);
printf("地址绑定成功\n");
//4.接受数据
bzero(buf,sizeof(buf));
r=read(fd,buf,sizeof(buf));
printf("%s\n",buf);
//5.关闭通道
close(fd);
//6.删除socket文件
}
ipB.c
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <linux/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main()
{
intfd;
intr;
charbuf[200];
//1.建立socket
fd=socket(AF_INET,SOCK_DGRAM,0);
if(fd==-1)printf("socketerr:%m\n"),exit(-1);
printf("socket成功\n");
//2.构造本地文件地址
structsockaddr_in addr={0};
addr.sin_family=AF_INET;
addr.sin_port=htons(9999);
addr.sin_addr.s_addr=inet_addr("192.168.1.102");
//3.把socket绑定在地址上
r=connect(fd,(structsockaddr*)&addr,sizeof(addr));
if(r==-1)printf("binderr:%m\n"),exit(-1);
printf("地址绑定成功\n");
//4.接受数据
write(fd,"hello!",strlen("hello"));
//5.关闭通道
close(fd);
//6.删除socket文件
}
运行结果是和上面的类似的