Linux进程控制4-进程间通信(IPC)
推荐链接:进程IPC通信方式7种
- 进程间的全局变量独立,另一个进程无法获得,因此要交换数据必须通过内核.(在内核区建立一块缓冲区,进程1把数据从用户空间写入内核缓冲区,进程2再从内核缓冲区读取数据称为IPC)(比文件的方式更快)
- 常见的进程间的通信方式:
1>管道(使用简单)
2>信号(系统开销最小)
3>共享映射区(无血缘关系)
4>本地套接字(最稳定)
一:管道
- 1.匿名管道
解释:
a:伪文件:文件类型包含:普通文件(-),目录(d),符号链接(l),套接字(s),块设备(b), 字符设备(c),管道(p),其中套接字(s),块设备(b), 字符设备(c),管道(p)不占用内存,所以为伪文件.
b:半双工:数据单向流动(不同时间可换向)(eg:微信(不能收发信息));全双工:数据可以同时双向流动(eg:打电话);单工:数据只能单向流动(eg:BB机)
1>实现原理:内核使用环形队列(先进先出)+内核缓冲区
注意:
2>pipe()函数实现
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include<string.h>
int main(int argc,char* argv[])
{
if(argc<2)
{
printf("请输入父子进程间的通信内容.\n");
exit(1);
}
printf("*****************\n");
/*******************************************************
*函数:pipe()
*头文件:#include <unistd.h>
*格式: int pipe(int pipefd[2]);//当调用该函数时,pipe就为我们创建了一个管道,返回r/w的文件描述符.
* pipefd[0](控制读端),pipefd[1](控制写端),默认都是打开的.创建管道的进程同时掌握读写端.
*作用:有公共祖先的进程间建立通信.
*返回值:成功-0;失败--1;
********************************************************/
int pid;
char buf[8];
int pipefd[2];
int ret=pipe(pipefd);//必须先于fork()函数,父子进程共享文件描述符,pipefd[0],pipefd[1]两端.
if(ret==-1)
{
perror("pipe error:");
exit(1);
}
pid=(int)fork();
if(pid==-1)
{
perror("fork error:");
exit(1);//#include<stdlib.h>
}
else if(pid==0)//子进程写数据
{
close(pipefd[0]);//保证进程间数据单向流动,所以关闭读端口
write(pipefd[1],argv[1],strlen(argv[1]));//写入数据
}
else if(pid>0)
{
//父进程读数据
int status;
int ret1=waitpid(-1,&status,0);//阻塞回收所有子进程,获取子进程死亡原因
if(ret1!=-1)
{
//查看子进程死亡原因
if(WIFEXITED(status))
printf("child %d exit with %d\n",ret1,WEXITSTATUS(status));
if(WIFSIGNALED(status))
printf("child %d exit with %d\n",ret1,WTERMSIG(status));
}
sleep(1);
//读出子进程的数据,并打印到屏幕
close(pipefd[1]);//关闭写端口
int ret2;
while((ret2=read(pipefd[0],buf,sizeof(buf)))>0)
{
write(1,buf,ret2);//1-内核输出到屏幕
}
printf("\n*****************\n");
}
return 0;
}
- 2.有名管道
1>FIFO先进先出,数据读了就没有了,半双工的通信方式(数据单向流动)(实现无血缘关系的进程间通信和有血缘关系的通信)
2>pipe匿名管道(只能实现有血缘关系的进程间通信)
3>有名管道虽然可以提供给任意关系的进程使用.但是由于其长期存在于系统之中,使用不当容易出错.所以普通用户一般不建议使用。
4>
/*******************************************************
*函数:mkfifo()
*头文件:#include <sys/types.h> #include <sys/stat.h>
*格式: int mkfifo(const char *pathname, mode_t mode);//fifo伪文件的名字(路径);mode:权限; //使用open打开该伪文件时,会阻塞,等待另一个进程打开该文件.(必须是同一个路径下的同一个fifo文件)
*作用: mkfifo, mkfifoat - make a FIFO special file (a named pipe) //也可在终端使用mkfifo xx创建.
*返回值:成功-0;失败--1;
********************************************************/
二:共享映射区
- 3.存储映射
1>进程间可通过文件进行通信,同理,存储映射I/O原理是使磁盘文件与内存的缓冲区相映射.使用地址(指针)完成I/O操作,相当于文件的W/R.通过mmap()函数实现.
2>实现
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<stdio.h>
#include <unistd.h>
#include<stdlib.h>
#include<sys/mman.h>
a=100;
/*******************************************************
*使用mmap()函数,创建存储映射区,实现父子进程间的通信;需要open和close以及unlink,比较麻烦,使用fd=open("/dev/zero",O_RDWR);就不用close和unlink和ftruncate.
*匿名映射-不需要文件fd,引入了一个宏定义MAP_ANONYMOUS(linux系统特有的),所有就不需要open和close以及unlink和ftruncate,但只适合linux系统,不适用与类linux系统.
********************************************************/
int main()
{
int *p=NULL;//接收mmap()函数返回的存储映射区的首地址
int fd=open("./test_mmap",O_RDWR|O_CREAT,777);//使用mmap()函数之前需要先打开一个文件,获得文件描述符.(至少要有读的权限,因为mmap()函数创建时会读一次文件)//(匿名映射不需要文件-LINUX系统)
if(fd==-1)
{
perror("open file error");
exit(1);
}
unlink("test_mmap");//删除文件的目录项,当所有进程都关闭该文件后就删除,释放内存.//地址都要加""号; 虚拟文件的手法
ftruncate(fd,4);//扩展该文件容量为4字节lseek()也可以实现;因为open文件为0字节不能创建映射区.
/*******************************************************
*函数:mmap()
*头文件: #include <sys/mman.h>
*格式: void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
*参数:
* void * :返回值类型void*,,(可匹配其他的类型);
* void *addr :建立映射区的首地址,内核指定,用户直接传NULL;
* size_t length :创建映射区的大小;
* int prot :映射区的权限(PORT_READ,PORT_WRITE,PORT_READ|PORT_WRITE);
* int flags :标志位参数(更新物理区域,设置共享,创建匿名映射区)(参数:MAP_SHARED:映射区操作刷新到物理磁盘;MAP_PRIVATE:映射区的修改不刷新到物理设备);
* int fd :用来建立映射区的文件描述符;
* off_t offset :映射文件的偏移量(4k的整数倍(4096\8192)磁盘一个扇区就是4k,mmu映射是4k)//映射区首地址相对于fd文件的偏移量;
*格式: int munmap(void *addr, size_t length);//像文件一样,映射区打开了,也需要关闭(munmap()),释放空间.void *addr:映射区的首地址;size_t length:映射区的大小;
*作用:建立共享映射区;实现:1:对磁盘通过mmap返回的指针进行读写eg:strcpy(映射区地址,"写入的字符串")操作(不使用fd);2:实现父子进程间的通信
*返回值: 成功-返回创建的映射区首地址;失败-返回MAP_FAILED//注意:不是返回的NULL或则-1,判断是否成功使用MAP_FAILED
********************************************************/
p=(int *)mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);//映射区的权限<=open()文件的权限,且open()文件的权限必须有读的权限
//p=(int *)mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0);//匿名映射不需要fd时,mmap()参数fd=-1;size_t 任意大小.
if(p==MAP_FAILED)//必须检查,每一个参数都有可能报错.注意:不是==-1;
{
perror("mmap file error");
exit(1);
}
close(fd);//关闭文件,后面父子进程间通信靠*p指针来读写.
pid_t pid=fork();
if(pid==-1)
{
perror("fork error");
exit(1);
}
if(pid==0)//子进程
{
*p=1000; //通过存储映射区的首地址指针写入数据 //父子进程间fd共享,映射区也共享,所以父进程的*p=1000;
a=2000; //对于用户区,遵循读时共享,写时复制的原则.所以这里输出2000,而不是100;
printf("*p=%d,a=%d\n",*p,a);
}
if(pid>0)
{
sleep(1);
printf("*p=%d,a=%d\n",*p,a); //输出结果 *p=1000;a=100;
wait(NULL);//回收子进程(waitpid())
}
int ret=munmap(p,4);//像文件一样,映射区打开了,也需要关闭(munmap()),释放空间.
if(pid==-1)
{
perror("munmmap error");
exit(1);
}
return 0;
}
1:Y, 2:报错, 3:无影响,4:只能是4k倍(eg:4096),6:不能++\--
- 4.匿名映射
- 5.映射区实现非血缘关系进程的通信
linux内核是多进程共享的,只要不同进程映射时的映射文件是同路径同名的就可实现不同进程间的通信(有点fifo的意思).因此文件也可以在不同进程间通信(原理就mmap)
三:本地套接字
1.选用面向连接的流式协议
2.确保server.c和client.c的下的服务器路径在同一目录下
3.客服端连接前需要构造服务器端的信息.
server.c
#include<stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
#include<unistd.h>
#include<stdlib.h>
#include <errno.h>
#include<string.h>
#include <sys/types.h>
#include <sys/un.h>
#include <stddef.h>
#define SERVER_ADDR "serv.socket"
int main(int argc, char *argv[])
{
int n; //recvfrom()函数返回值
int i;
int connect_fd; //accept返回值
char buffer[1024]; //存储传输数据
//1.创建socket-AF_UNIX(本地套接字),默认报式协议UDP(占位作用)
int fd=socket(AF_UNIX, SOCK_STREAM, 0);//注意:第二个参数不能填报式协议(SOCK_DGRAM)).
if(fd==-1)
{
perror("socke error");
exit(1);
}
//2.绑定端口
struct sockaddr_un server_addr,client_addr; //注意:此处结构体有变化.采用sockaddr_un
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sun_family=AF_UNIX;//也可以传AF_LOCAL,
strcpy(server_addr.sun_path,SERVER_ADDR);
int len=offsetof(struct sockaddr_un,sun_path)+strlen(server_addr.sun_path);
unlink(SERVER_ADDR); //本地套接字的灵魂
int ret1=bind(fd,(struct sockaddr *)&server_addr,len); //注意:不在是sizeof(server_addr)
if(ret1==-1)
{
perror("bind error");
exit(1);
}
int ret2=listen(fd,20);
if(ret2==-1)
{
perror("listen error");
exit(1);
}
printf("Waiting connect....\n");
len=sizeof(client_addr);
connect_fd=accept(fd,(struct sockaddr *)&client_addr,&len);//client_addr传出参数
if(connect_fd<0)
{
again:
if((errno==ECONNABORTED)||(errno==EINTR))//accept为慢速系统调用,在被信号打断的时候,根据实际情况,是否重启
goto again;
else if(connect_fd==-1)
{
perror("accept error");
exit(1);
}
}
//打印客服端信息
len-=offsetof(struct sockaddr_un,sun_path);//得到文件长度
client_addr.sun_path[len]='\0';
printf("client bind filename%s\n",client_addr.sun_path);
while(1)
{
//读取数据
int num=read(connect_fd,buffer,sizeof(buffer));//num读出字节数
if(num==-1)
{
again1:
if(errno==EINTR)//read为慢速系统调用,在被信号打断的时候,根据实际情况,是否重启
goto again1;
else
{
perror("read error");
exit(1);
}
}
for(int i=0;i<num;i++)
buffer[i]=tolower(buffer[i]);
//写数据
int num1=write(connect_fd,buffer,num);
if(num1==-1)
{
again2:
if(errno==EINTR)//write系统调用,在被信号打断的时候,根据实际情况,是否重启
goto again2;
else
{
perror("write error");
exit(1);
}
}
}
close(fd);
close(connect_fd);
return 0;
}
client.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/un.h>
#include <stddef.h>
#define CLIENT_ADDR "client.socket"
#define SERVER_ADDR "../server/serv.socket" //注意:此处的SERVER_ADDR应与server.c的SERVER_ADDR在同一个路径下面
int main(int argc, char *argv[])
{
int n;
char buffer[1024]; //存储传输数据
//1.创建socket-AF_LOCAL(本地套接字),默认报式协议UDP(占位作用)
int fd = socket(AF_LOCAL, SOCK_STREAM, 0); //不能填SOCK_DGRAM(不能连接)
if (fd == -1)
{
perror("socke error");
exit(1);
}
//2.绑定服务器端;不用调用bind()函数,本机会自动隐式绑定
struct sockaddr_un client_addr, server_addr;
memset(&client_addr, 0, sizeof(client_addr));
client_addr.sun_family = AF_LOCAL;
strcpy(client_addr.sun_path, CLIENT_ADDR);
int len = offsetof(struct sockaddr_un, sun_path) + strlen(client_addr.sun_path); //计算地址client_path的有效长度
unlink(CLIENT_ADDR); //bind之前检查该文件是否存在
int ret1 = bind(fd, (struct sockaddr *)&client_addr, len); //注意:不在是sizeof(server_addr)
if (ret1 == -1)
{
perror("bind error");
exit(1);
}
//connect()函数连接服务器需要
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sun_family = AF_LOCAL; //也可以传AF_UNIX,
strcpy(server_addr.sun_path, SERVER_ADDR);
len = offsetof(struct sockaddr_un, sun_path) + strlen(server_addr.sun_path);
int ret2 = connect(fd, (struct sockaddr *)&server_addr, len);
if (ret2 == -1)
{
perror("connect error");
exit(1);
}
else
{
printf("Connect success\n");
}
while (1)
{
n = read(STDIN_FILENO, &buffer, sizeof(buffer)); //阻塞读(错误检查看server.c)
for (int i = 0; i < n; i++)
{
buffer[i] = toupper(buffer[i]);
}
write(fd, &buffer, n);
int num1 = read(fd, &buffer, sizeof(buffer));
write(STDOUT_FILENO, buffer, num1);
}
close(fd);
return 0;
}