进程间通信(IPC)

1>对于线程间的通信,我们可以使用全局变量实现,因为多个线程共享进程的资源

2>由于进程内存空间是独立的,所以不能通过全局变量来进行进程间的通信

3>虽然可以使用外部文件来实现多个进程间的通信,但是又遇到进程不能同步的问题,例如不能实现必须一个进程写数据,另一个进程读取数据

4>由于每个进程的0-3G是独立的内存,共享3-4G的内核空间,所以可以通过3-4G的内核空间来完成多个进程间的通信

5>系统提供的通信方式一共有三类

1.传统内核提供的通讯机制

        无名管道

        有名管道

        信号

2.System V提供的进程间通信

        消息队列

        共享内存

        信号灯集

3.套接字(socket)

管道

1> 管道原理:

在进程3-4G的内核空间中创建的一个特殊文件,管道可以传输不同进程间的数据,而且,管道中的数据,直接保存在内存上

2>管道的特点:

1.管道是一个特殊文件,其他文件都是存储在磁盘上,而管道中的数据存储在内存上

2.管道遵循先进先出原则,管道文件不能使用lseek

3.由于管道文件在内核空间创建所以但凡使用管道文件只能使用文件IO,不能使用标准IO

4.管道的读取操作是一次性的,也就是管道中的数据被读取后,就从管道中删除了

5.管道通信是一种半双工的通信方式

单工:只支持A->B

半双工:在同一时间内A->B,或者B->A

全双工:A->B,B->A

6.对于管道而言,会提供读和写的两端,如果两端都关闭了,则管道文件也被关闭了

7.管道文件的大小:64KB

一,无名管道

1>没有名字的管道文件

2>由于没有名字,不能使用open打开,使用对应的函数pipe时就打开了

3>又因为没有名字,所以其他进程不能使用该管道,所以该通信方式,只适用于亲缘间的进程通信

4>无名管道文件在系统文件中是不可见的,所以两个无关的进程,是无法拿到同一个处理该文件的文件描述符

1.1无名管道API

 #include <unistd.h>

       int pipe(int pipefd[2]);
功能:创建一个无名管道文件

参数:文件描述符数组,该函数创建好一个管道文件后,会提供读端和写端两个文件描述符分别放在pipefd[0],pipefd[1]

返回值:成功返回0,失败返回-1并置位错误码

举个例子:  int pipefd[2];

                        pipe(pipefd);

                此时:pipefd[0]:读端

                        pipefd[1]:写端

1.2无名管道实例

#include <myhead.h>
  #include <sys/wait.h>
int main(int argc,const char *argv[])
{  
	//定义存放管道文件的数据
	int pipefd[2];

	char buf[128]="";//读取数据用的容器
	//创建管道文件
	if(pipe(pipefd)==-1)
	{
		perror("pipe error");
		return -1;
	}
	printf("pipefd[0]=%d,pipefd[1]=%d\n",pipefd[0],pipefd[1]);
	//定义进程PID
	pid_t pid=fork();
	//判断父子进程
	if(pid<0)
	{
		perror("fork error");
		return -1;
	}else if(pid ==0)
	{
		//子进程
		//关闭写端
		close(pipefd[1]);
		while(1)
		{
			//清空数组
			memset(buf,0,sizeof(buf));
			//从管道中读取数据
			read(pipefd[0],buf,sizeof(buf));
			if(strcmp(buf,"quit")==0)
			{
				break;
			}
			//将读取的数据显示在终端上
			printf("子进程得到的数据:%s\n",buf);
		}
		close(pipefd[0]);

		exit(EXIT_SUCCESS);//退出子进程
	}else
	{
		//父进程
		//关闭读端
		close(pipefd[0]);
		while(1)
		{
			//从终端上获取数据
			fgets(buf,sizeof(buf),stdin);
			//将倒数第二个字符换成'\0'
			buf[strlen(buf)-1]='\0';
         	//将buf的数据写入管道中
			write(pipefd[1],buf,strlen(buf));
			if(strcmp(buf,"quit")==0)
			{
				break;
			}
		
		}
		//关闭写端
		close(pipefd[1]);
		wait(NULL);	//回收子进程
	}
	return 0;
}

1.3无名管道特点

二,有名管道

1>创建的管道文件是有名字的,能够在文件系统中进行找到并打开

2>因为管道文件存在,导致能够使用管道文件来完成亲缘和非亲缘

2.1有名管道API

 #include <sys/types.h>
       #include <sys/stat.h>

       int mkfifo(const char *pathname, mode_t mode);

功能:穿件一个有名管道文件

参数1:管道的名字

参数2:创建管道的权限

返回值:成功返回0,失败返回-1并置位错误码

2.2实例

#include <myhead.h>
int main(int argc,const char *argv[])
{  
	//创建一个管道文件
	if(mkfifo("./myfifo",0664))
	{
		perror("mkfifo error");
		return -1;
	}
	//阻塞等待其他进程使用管道
	getchar();
	//用过代码执行终端指令
	system("rm myfifo");
	return 0;
}
#include <myhead.h>
int main(int argc,const char *argv[])
{  
	int fd; //文件描述符
	char buf[128]="";
	//打开有名管道文件
	if((fd=open("./myfifo",O_WRONLY))==-1)
	{
		perror("open file");
		return -1;
	}
	//循环往管道中写数据
	while(1)
	{
		fgets(buf,sizeof(buf),stdin);
		//清掉'\n'
		buf[strlen(buf)-1]='\0';
		write(fd,buf,strlen(buf));
		//如果输入的是quit
		if(strcmp(buf,"quit")==0)
			break;
	}
	//关闭文件
	close(fd);
	
	return 0;
}
#include <myhead.h>
int main(int argc,const char *argv[])
{  //定义文件描述符
	int fd;
	char buf[128]="";
	//打开文件
	if((fd=open("./myfifo",O_RDONLY))==-1)
	{
		perror("open error");
		return -1;
	}
	while(1)
	{
		memset(buf,0,sizeof(buf));
		//从管道中读取数据
		read(fd,buf,sizeof(buf));
		if(strcmp(buf,"quit")==0)
			break;
		printf("%s\n",buf);//将内容打印在终端上
	}
	close(fd);//关闭文件
	return 0;
}

 

2.3有名管道特点

作业:实现两个线程之间的通信

#include <myhead.h>
int main(int argc,const char *argv[])
{  
	//创建一个管道文件
	if(mkfifo("./myfifo",0664))
	{
		perror("mkfifo error");
		return -1;
	}
	if(mkfifo("./myfifo1",0664))
	{
		perror("mkfifo1 error");
		return -1;
	}
	//阻塞等待其他进程使用管道
	getchar();
	//用过代码执行终端指令
	system("rm myfifo");
	return 0;
}
#include <myhead.h>
int main(int argc,const char *argv[])
{  
    pid_t pid;
pid=fork();
if(pid==0)
{
	int fd; //文件描述符
	char buf[128]="";
	//打开有名管道文件
	if((fd=open("./myfifo",O_WRONLY))==-1)
	{
		perror("open file");
		return -1;
	}
	//子线程循环往管道中写数据
	while(1)
	{
		fgets(buf,sizeof(buf),stdin);
		//清掉'\n'
		buf[strlen(buf)-1]='\0';
		write(fd,buf,strlen(buf));
		//如果输入的是quit
		if(strcmp(buf,"quit")==0)
			break;
	}
	//关闭文件
	close(fd);
}else//父线程实现读数据
{
int fd;
	char buf[128]="";
	//打开文件
	if((fd=open("./myfifo1",O_RDONLY))==-1)
	{
		perror("open error");
		return -1;
	}
	while(1)
	{
		memset(buf,0,sizeof(buf));
		//从管道中读取数据
		read(fd,buf,sizeof(buf));
		if(strcmp(buf,"quit")==0)
			break;
		printf("%s\n",buf);//将内容打印在终端上
	}
	close(fd);//关闭文件
}
	
	return 0;
}
#include <myhead.h>
int main(int argc,const char *argv[])
{  
pid_t pid;
pid=fork();
if(pid==0)
{
	int fd; //文件描述符
	char buf[128]="";
	//打开有名管道文件
	if((fd=open("./myfifo1",O_WRONLY))==-1)
	{
		perror("open file");
		return -1;
	}
	//子线程循环往管道中写数据
	while(1)
	{
		fgets(buf,sizeof(buf),stdin);
		//清掉'\n'
		buf[strlen(buf)-1]='\0';
		write(fd,buf,strlen(buf));
		//如果输入的是quit
		if(strcmp(buf,"quit")==0)
			break;
	}
	//关闭文件
	close(fd);
}else//父线程实现读数据

else{//定义文件描述符
	int fd;
	char buf[128]="";
	//打开文件
	if((fd=open("./myfifo",O_RDONLY))==-1)
	{
		perror("open error");
		return -1;
	}
	while(1)
	{
		memset(buf,0,sizeof(buf));
		//从管道中读取数据
		read(fd,buf,sizeof(buf));
		if(strcmp(buf,"quit")==0)
			break;
		printf("%s\n",buf);//将内容打印在终端上
	}
	close(fd);//关闭文件
}
	return 0;
}

三,信号

3.1概念

所谓信号就是软件模拟的中断功能,信号是软件实现的,中断是硬件实现的。

中断属于异步通信的一种。

同步通信:进程在要执行前一直阻塞等待其他进程结束

异步通信:进程在要执行前不需要一直阻塞等待其他进程结束

关于处理信号有三种情况:默认,忽略,捕获

3.2信号的种类及功能

1>可以通过kill-l 查看所有信号

1) SIGHUP     2) SIGINT     3) SIGQUIT     4) SIGILL     5) SIGTRAP
 6) SIGABRT     7) SIGBUS     8) SIGFPE     9) SIGKILL    10) SIGUSR1
11) SIGSEGV    12) SIGUSR2    13) SIGPIPE    14) SIGALRM    15) SIGTERM
16) SIGSTKFLT    17) SIGCHLD    18) SIGCONT    19) SIGSTOP    20) SIGTSTP
21) SIGTTIN    22) SIGTTOU    23) SIGURG    24) SIGXCPU    25) SIGXFSZ
26) SIGVTALRM    27) SIGPROF    28) SIGWINCH    29) SIGIO    30) SIGPWR
31) SIGSYS    34) SIGRTMIN    35) SIGRTMIN+1    36) SIGRTMIN+2    37) SIGRTMIN+3
38) SIGRTMIN+4    39) SIGRTMIN+5    40) SIGRTMIN+6    41) SIGRTMIN+7    42) SIGRTMIN+8
43) SIGRTMIN+9    44) SIGRTMIN+10    45) SIGRTMIN+11    46) SIGRTMIN+12    47) SIGRTMIN+13
48) SIGRTMIN+14    49) SIGRTMIN+15    50) SIGRTMAX-14    51) SIGRTMAX-13    52) SIGRTMAX-12
53) SIGRTMAX-11    54) SIGRTMAX-10    55) SIGRTMAX-9    56) SIGRTMAX-8    57) SIGRTMAX-7
58) SIGRTMAX-6    59) SIGRTMAX-5    60) SIGRTMAX-4    61) SIGRTMAX-3    62) SIGRTMAX-2
63) SIGRTMAX-1    64) SIGRTMAX    

2>常用信号

SIGHUP  SIGINT  SIGQUIT  SIGILL  

3> 在所有信号中,只有SIGKLL和SIGSTOP信号不能被捕捉也不能被忽略

3.3信号API

 #include <signal.h>

       typedef void (*sighandler_t)(int);

//类型为函数指针 参数为整形的返回值为void 类型的信号处理函数 该参数 就是要处理的信号

       sighandler_t signal(int signum, sighandler_t handler);

功能:将信号与信号处理函数进程绑定

参数1:要处理的信号号

参数2:处理方式:

该信号被进程忽略

该信号被默认处理,一般都是终止进程

返回值:成功返回信号处理函数的首地址,失败返回SIG_ERR,并置位错误码

#include <myhead.h>
//定义信号处理函数
void handler(int signo)
{
	printf("用户按下了ctrl+C\n");
}
int main(int argc,const char *argv[])
{
	//1.忽略
   /*if(signal(SIGINT,SIG_IGN)==SIG_ERR)
	{
		perror("signal error");
		return -1;
	}*/

	//2.默认
/*		if(signal(SIGINT,SIG_DFL)==SID_ERR)
	{
		perror("signal error");
		return -1;
	}*/

	//3.捕获
	while(1)
		if(signal(SIGINT,handler)==SIG_ERR)
		{
			perror("signal error");
			return -1;
		}
	return 0;
}

3.4 使用信号

#include <myhead.h>
//手动定义处理信号的函数
void handler(int singo)
{
	//处理函数
	waitpid(-1,NULL,WNOHANG);
	printf("父进程以非阻塞的形式手动回收了子进程...\n");

}
int main(int argc,const char *argv[])
{  
	pid_t pid;
	pid =fork(); //创建子进程
	if(pid<0)
	{
		perror("fork error");
		return -1;
	}else if(pid==0)
	{
		//子进程
		printf("子进程开始...\n");
		sleep(3);
		printf("子进程结束...\n");
		exit(EXIT_SUCCESS);//子进程结束
	}else{
		//父进程
		//将子进程发射的SIGCHLD信号绑定到自定义处理函数中
		if(signal(SIGCHLD,handler)==SIG_ERR)
	}
	return 0;
}

3.5发信号相关的API(kill/raise)

  #include <sys/types.h>
       #include <signal.h>

       int kill(pid_t pid, int sig);

功能:给指定的进程发信号

参数1:指定进程的进程号

>0:给指定的这个pid进行发信号

=0:给当前进程所在的组中的所有进程发信号

=-1:给所有进程发信号,但是要注意权限

<-1:给pid的绝对值那个进程所在的组下所有的进程发信号

参数2:要发射的信号

返回值:成功返回0,失败返回-1并置位错误码

#include <signal.h>

       int raise(int sig);
 

#include <myhead.h>
//定义处理信号的函数
void handler(int signo)
{
	printf("有此子,此生无憾...\n");
	kill(getpid(),SIGKILL);//向自己发射一个自杀信号
}
int main(int argc,const char *argv[])
{  
	pid_t pid;
	pid=fork();
	if(pid<0)
	{
		perror("fork error");
		return -1;
	}else if(pid==0)//子进程
	{
		printf("子进程开始...\n");
		sleep(3);
		kill(getppid(),SIGUSR1);//向父进程发射一个自定义信号
		printf("父亲 你可以死了...\n");
		exit(EXIT_SUCCESS);//退出进程

	}else{
		//父进程
		//蒋子进程发射的信号绑定到信号处理函数中
		if(signal(SIGUSR1,handler)==SIG_ERR)
		{
			perror("open signal");
			return -1;
		}
		while(1)
		{
			sleep(1);
			printf("%s,%s,%d\n",__FILE__,__func__,__LINE__);
		}
	}
		return 0;
}

斗地主:

#include <myhead.h>
//定义信号处理函数
void handler(int signo)
{
	printf("系统帮您随机出了一张牌\n");
	alarm(5);
}
int main(int argc,const char *argv[])
{  
	char ch;//打出的牌
	//将信号与信号处理函数 绑定
	if(signal(SIGALRM,handler)==SIG_ERR)
	{
		perror("signal error");
		return -1;
	}
	//启动定时器
	alarm(5);
	while(1)
	{
		ch=getchar();
		getchar();
		printf("您打出的牌:%c\n",ch);
		alarm(5);
	}
	return 0;
}

3.6闹钟函数API

#include <unistd.h>

unsigned int alarm(unsigned int seconds);

功能:在seconds过后发射一个SIGALRM信号

参数:毫秒数

返回值:该函数返回上次调用alarm函数剩余的毫秒数,如果之前没有调用过alarm函数或者,上一次alarm函数闹钟刚已经处理完毕

注意:如果第二次调用alarm时,如果更新了秒数,则SIGALRM信号的发射,根据新更改的秒数确定

四,system V IPC进程间通信

1>系统5又提供了三种进程间通信

        消息队列

        共享内存

        信号灯集

2> 查看相关属性的命令

ipcs  :可以查看所有信息(消息队列,共享内存,信号量)

ipcs_q:查看消息队列(queue)

ipcs-m:查看共享内存(memory)

ipcs-s:查看信号量(semphore)

删除:ipcrm -q/-m/-s ID

五,消息队列

5.1原理

5.2消息队列的API

#include <myhead.h>
//定义一个要放到队列中数据的结构体
typedef struct
{
	long msg_type;//消息类型
	char mtext[1024];//数据域

}msg_t;
//定义数据正文的大小
#define MSG_SIZE(sizeof(msg_t)-sizeof(long))
int main(int argc,const char *argv[])
{  
	key_t key;//钥匙
	int msg_id; //消息队列的id号
	msg_t msg={.msg_type=100};
	//1.获取key值
	if((key=ftok("./",'t'))==-1)
	{
		perror("ftok error");
		return -1;
	}
	//2.创建消息队列
	if((msg_id=msgget(key,IPC_CREAT|0664))==-1)
	{
		perror("msgget error");
		return -1;
	}
	//3,向消息队列中存放数据
	while(1)
	{
		//提示输入消息的正文
		printf("请输入:");
		fgets(msg.mtext,sizeof(msg.mtext),stdin);//获取数据
		//消除回车
		msg.mtext[strlen(msg.mtext)-1]='\0';
		//4.将消息放入消息队列中
		//msg_id:消息队列的队列号
		//&msg:存放数据的数据起始地址
		//MSG_SIZE:正文大小
		//0:阻塞等待
		if(msgsnd(msg_id,&msg,MSG_SIZE,0)==-1)
		{
			perror("msgsnd error");
			return -1;
		}
	}
	return 0;
}
#include <myhead.h>
//定义一个要放到队列中数据的结构体
typedef struct
{
	long msg_type;//消息类型
	char mtext[1024];//数据域

}msg_t;
//定义数据正文的大小
#define MSG_SIZE(sizeof(msg_t)-sizeof(long))
int main(int argc,const char *argv[])
{  
	key_t key;//钥匙
	int msg_id; //消息队列的id号
	msg_t msg=;
	//1.获取key值
	if((key=ftok("./",'t'))==-1)
	{
		perror("ftok error");
		return -1;
	}
	//2.创建消息队列
	if((msg_id=msgget(key,IPC_CREAT|0664))==-1)
	{
		perror("msgget error");
		return -1;
	}
	//3,向消息队列中读取数据
	while(1)
	{

		//4.从消息队列中读取数据
		//msg_id:消息队列的队列号
		//&msg:存放数据的数据起始地址
		//MSG_SIZE:正文大小
		//0:第一个0表示每次都是从第一个位置读取数据
		//0:阻塞等待
		if(msgrcv(msg_id,&msg,MSG_SIZE,0,0)==-1)
		{
			perror("msgsnd error");
			return -1;
		}
		printf("读取消息类型为:%d,内容为:%s\n",msg_type,msg.mtext);
	}
	return 0;
}

 亲缘进程互相通信

#include <myhead.h>
//定义一个要放到队列中数据的结构体
typedef struct 
{
	long msg_type;//消息类型
	char mtext[1024]; //数据域
}msg_t
//定义数据正文的大小
#define MSG_SIZE(sizeof(msg_t)-sizeof(long))
int main(int argc,const char *argv[])
{  
	int msg_id;//消息队列的id号
	msg_t msg={.msg_type=100};//定义消息结构体变量,并指定类型
	//2.创建消息队列
	if((msg_id=msgget(IPC_PRIVATE,IPC_CREAT|0664))==-1)
	{
		perror("msgget error");
		return -1;
	}
	pid_t pid=fork();
	if(pid<0)
	{
		perror("fork error");
		return -1;
	}else if(pid==0)
	{
		//3.向消息队列中存放数据
		while(1)
		{
			//提示输入消息的正文
			printf("请输入:");
			fgets(msg.mtext,sizeof(msg.mtext),stdin);
			//消除回车
			msg.mtext[strlen(msg.mtext)-1]='\0';

			//4.将消息放入消息队列中
			//msg_id:消息队列的队列号
			//&msg:存放数据的数据起始地址
			//MSG_SIZE:正文大小
			//0:阻塞等待
	       if(msgsnd(msg_id,&msg,MSG_SIZE,0,0)==-1)
		   {
			   perror("msgsnd error");
			   return -1;
		   }
		}
	}else
	{
	    //4.读取消息队列中
			//msg_id:消息队列的队列号
			//&msg:存放数据的数据起始地址
			//MSG_SIZE:正文大小
			//0:阻塞等待

         if(msgrcv(msg_id,&msg,MSG_SIZE,0,0)==-1)
		   {
			   perror("msgsnd error");
			   return -1;
			printf("读取的消息类型为:%ld,内容为:%s\n",msg.msg_type,msg.mtext);

	}
	//使用控制函数,将消息队列删除
	msgctl(msg_id,IPC_RMID,NULL);
	return 0;
}

  六,共享内存

6.1原理

6.2共享内存的实例

#include <myhead.h>
#define SHMSIZE 4096
int main(int argc,const char *argv[])
{  
	//定义KEY值
	key_t key;
	//共享内存的id
	int shmid;
	//物理内存映射的地址
	char *addr=NULL;
	//1.获取key值
	if((key=ftok("/",'k'))==-1)
	{
		perror("ftok error");
		return -1;
	}
	//2.创建共享内存
	if((shmid=shmget(key,SHMSIZE,IPC_CREAT|0664))==-1)
	{
		perror("shmget error");
		return -1;
	}
	//3.将共享内存的空间映射到该进程中
	addr=shmat(shmid,NULL,0);
	//判断是否申请成功
	if(addr==(void *)-1)
	{
		perror("shmat error");
		return -1;
	}
	//打印共享内存地址
	printf("addr=%p\n",addr);
	//向共享内存中写数据
	printf("请输入:");
	fgets(addr,SHMSIZE,stdin);  //从终端输入数据到共享内存中
	addr[strlen(addr)-1]='\0';
	getchar();//阻塞
	//撤销映射
	shmdt(addr);
	//删除共享内存
	shmctl(shmid,IPC_RMID,NULL);
	return 0;
}
#include <myhead.h>
#define SHMSIZE 4096
int main(int argc,const char *argv[])
{  
	//定义KEY值
	key_t key;
	//共享内存的id
	int shmid;
	//物理内存映射的地址
	char *addr=NULL;
	//1.获取key值
	if((key=ftok("/",'k'))==-1)
	{
		perror("ftok error");
		return -1;
	}
	//2.创建共享内存
	if((shmid=shmget(key,SHMSIZE,IPC_CREAT|0664))==-1)
	{
		perror("shmget error");
		return -1;
	}
	//3.将共享内存的空间映射到该进程中
	addr=shmat(shmid,NULL,0);
	//判断是否申请成功
	if(addr==(void *)-1)
	{
		perror("shmat error");
		return -1;
	}
	//打印共享内存地址
	printf("addr=%p\n",addr);
	//读取出共享内存中的数据
	printf("共享内存中的数据是:%s\n",addr);
	//阻塞
	getchar();

	return 0;
}

⑦信号量

7.1信号量概念

信号量是实现进程间同步的方式,这个信号量,又叫信号灯集,在该信号量中,有多个信号灯,每个信号灯控制一个进程,他们之间不会互相产生影响

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值