进程间通信概述:
1、数据传输
一个进程需要将他的数据发送给另一个进程。
2、资源共享
多个进程之间共享同样的资源。
3、通知事件
一个进程需要向另一个或一组进程发送消息,通知他们发生了某种时间。
4、进程控制
有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有操作,bin能够及时知道他的状态改变 。
进程间通信方式:
1、管道(pipe)和有名管道(FIFO)
2、信号(signal)
3、消息队列
4、共享内存
5、信号量
6、套接字(socket)
一、管道通信:
管道通信是单向的、先进先出的,把一个进程的输出和另一个进程的输入连接在一起。一个进程(写进程)在管道的尾部写入数据,另一个进程(读进程)从管道的头部读出数据
管道包括无名管道和有名管道两种,前者用于父进程和子进程间的通信,后者课用于运行于同一系统中任意两个子进程间的通信
无名管道pipe():
#include
int pipe(int filedis[2]);
当一个管道建立时,他会创建两个文件描述符filedis[0]用于读管道,filedis[2]用于写管道必须在fork之前调用pipe,否则子进程将不会继承文件描述符
pipe_rw.c
#include
#include
#include
#include
#include
int main()
{
int pipe_fd[2];
pid_t pid;
char buf_r[100];
char *p_wbuf;
int r_num;
memset(buf_r,0,sizeof(buf_r));
/*创建管道*/
if(pipe(pipe_fd)<0)
{
printf("pipe creat error\n");
return -1;
}
/*创建子进程*/
if((pid=fork())==0) //在子进程中
{
printf("\n");
close(pipe_fd[1]); //关闭写数据
sleep(2); //让子进程停下来,父进程运行向管道中写数据
if((r_num=read(pipe_fd[0],buf_r,100))>0)
{
printf("%d numbers read from the pipe is %s\n",r_num,buf_r);
}
close(pipe_fd[0]);
exit(0);
}
else if(pid>0)
{
close(pipe_fd[0]); //关闭读数据
if(write(pipe_fd[1],"Hello",5)!=-1)
printf("parent writel Hello!\n");
if(write(pipe_fd[1]," Pipe",5)!=-1)
printf("parent write2 Pipe!\n");
close(pipe_fd[1]); //数据写入完毕
sleep(3); //让父进程停下来,子进程运行从管道中读数据
waitpid(pid,NULL,0);
exit(0);
}
}
命名管道FIFO:
命名管道和无名管道基本相同,但也有不同点:
无名管道只能有父子进程使用;
但是通过命名管道,不相关的进程也能交换数据
#include
#include
int mkfifo(const char * pathname,mode_t mode)
pathname :FIFO文件名
mode: 属性
一旦创建了一个FIFO,就可以用open打开他,一般的文件访问函数(close,read,write等)都可以用于FIFO
打开FIFO时,非阻塞标志(O_NONBLOCK)
1、没使用O_NONBLOCK:访问要求无法满足时进程将阻塞,如试图读取空的FIFO,将导致进程阻塞
2、使用O_NONBLOCK:访问要求无法满足时不阻塞,立刻出错返回,errno是ENXIO
fifo_read.c
#include
#include
#include
#include
#include
#include
#include
#include
#define FIFO "/tmp/myfifo"
void main(int argc,char** argv)
{
char buf_r[100];
int fd;
int nread;
if(mkfifo(FIFO,O_CREAT|O_EXCL)<0)
printf("cannot creat fifosever\n");
printf("Preparing for reading bytes...\n");
memset(buf_r,0,sizeof(buf_r));
//打开管道,只读且不阻塞
fd=open(FIFO,O_RDONLY|O_NONBLOCK,0);
if(fd == -1)
{
perror("open");
exit(1);
}
while(1)
{
memset(buf_r,0,sizeof(buf_r));
if((nread=read(fd,buf_r,100))==-1)
{
if(errno == EAGAIN)
printf("no data yet\n");
}
printf("read %s from FIFO\n",buf_r);
sleep(1);
}
pause();//暂停,等待信号
}
fifo_write.c
#include
#include
#include
#include
#include
#include
#include
#include
#define FIFO_SERVER "/tmp/myfifo"
void main(int argc,char** argv)
{
int fd;
char w_buf[100];
int nwrite;
//打开管道,只写且不阻塞
fd=open(FIFO_SERVER,O_WRONLY|O_NONBLOCK,0);
if(argc == 1)
{
printf("Please send something\n");
exit(-1);
}
strcpy(w_buf,argv[1]);
//向管道写入数据
if( (nwrite=write(fd,w_buf,100)) == -1)
{
if(errno==EAGAIN)
{
printf("The FIFO has not been read yet.Please try later\n");
}
}
else
printf("write %s to the FIFO\n",w_buf);
}
二、信号通信:
kill、raise
发送信号函数kill和raise:
kill既可以想自身发送信号,也可以向其他进程发送信号
raise是向进程自身发送信号
kill -s 信号 pid
#include
#include
int kill(pid_t pid,int signo)
int raise(int signo)
alarm
alarm可以设置一个时间值(闹钟时间),当所设置的时间到了时,产生SIGAKRM信号。如果不捕获此信号,则默认动作是终止该进程。
#include
unsigned int alarm(unsigned int seconds)
seconds:经过了指定的seconds秒后会产生信号SIGALRM
pause
pause函数使用调用进程挂起直至捕捉到一个信号
#include
int pause (void)
signal
#include
void (*signal(int signo,void (*func)(int)))(int)
若成功,返回以前的信号处理配置
若出错,返回SIG_ERR
将函数拆分:
typedef void (*sighandler_t)(int)
sighandler_t signal(int signum,sighandler_t handler)
使用ps aux命令可以查看进程
mysignal.c
#include
#include
#include
#include
void my_func(int sign_no)
{
if(sign_no == SIGINT)
printf("I have get SIGINT\n");
else if(sign_no==SIGQUIT)
printf("I have get SIGQUIT\n");
}
int main()
{
printf("Waiting foe signal SIGINT or SIGQUIT \n ");
//注册信号处理函数
signal(SIGINT,my_func);
signal(SIGQUIT,my_func);
pause();
exit(0);
}
三、共享内存:
是被多个进程共享的一部分的物理内存。共享内存是进程间共享数据的一种最快的方法,一个进程向共享内存写入了数据,共享这个内存区域的所有进程就可以立刻看到其中的内容。
共享内存实现方法:
一、创建共享内存,使用shmget函数
二、映射共享内存,将这段创建的共享内存映射到具体的进程空间去,使用shmat函数
创建:
#include
#include
#include
int shmget(key_t key,int size,int shmflg)
返回值:如果成功,返回共享内存标识符;如果失败,返回-1
映射:
#include
#include
#include
int shmat(int shmid,char *shmaddr,int flag)
shmid:shmget函数返回的共享内存存储标识符
flag:觉得以什么方式来确定映射的地址(通常为0)
返回值:如果成功,则返回共享内存映射到进程中的地址;如果失败,则返回-1
解除:
#include
#include
#include
int shmdt(char *shmaddr)
返回值:成功返回0,出错返回-1
shmem.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define PERM S_IRUSR|S_IWUSR
/*共享内存*/
int main(int argc,char **argv)
{
int shmid;
char *p_addr,*c_addr;
if(argc!=2)
{
fprintf(stderr,"Usage:%s\n\a",argv[0]);
exit(1);
}
//创建共享内存
if((shmid=shmget(IPC_PRIVATE,1024,PERM))==-1)
{
fprintf(stderr,"Create Share Memory ERROR:%s\n\a",strerror(errno));
exit(1);
}
//创建子进程
if(fork())
{
p_addr=shmat(shmid,0,0);//穿0表示有系统自动寻找一个地址
memset(p_addr,'\0',1024);//对某一块内存区“清楚”,本历程全部格式化为‘\0’
strncpy(p_addr,argv[1],1024);
wait(NULL);
exit(0);
}
else
{
sleep(1);//防止子进程先运行
c_addr=shmat(shmid,0,0);
printf("Client get %s\n",c_addr);
exit(0);
}
}
四、消息队列:
POSIX消息队列、系统V消息队列
POSIX:可移植操作系统接口
系统V消息队列是随内核持续的,只有在内核重启或者人工删除时,该消息队列才会被删除 。
消息队列就是一个消息的链表,可以吧消息看做一个记录,有特定的格式,进程可以向其中按一定规则添加新的消息,另一些进程可以从消息队列中读出消息。
消息队列的内核持续性要求每个消息队列都在系统范围内对应唯一的键值,所以,要获得一个消息队列的描述字,必须提供该消息队列的键值 。
#include
#include
key_t ftok(char *pathname,char proj)
功能:返回文件名对应的键值
pathname:文件名
proj:项目名(不为0即可)
打开/创建
#include
#include
#include
int msgget(key_t key,int msgflg)
key:键值,由ftok获得
msgflg:标志位
返回值:与键值key相对应的消息队列描述字
标志位:
IPC_CREAT:创建新的消息队列
IPC_EXCL:与IPC_CREAT一同使用,表示如果要创建的消息队列已经存在,则返回错误
IPC_NOWAIT:读写消息队列要求无法的到满足时,不阻塞
在以下两种情况下,将创建一个新的消息队列:
1、如果没有与键值key相对应的消息队列,并且msgflg中包含了IPC_CREAT标志位。
2、key参数为IPC_PRIVATE
发送消息:
#include
#include
#include
int msgsnd(int msqid,struct msgbuf* msgp,int msgsz,int msgflg)
功能:向消息队列发送一条消息
struct msgbuf
{
long mtype; //消息类型>0
char mtext[1];//消息数据的首地址
}
接收消息:
#include
#include
#include
int msgrcv(int msqid,struct msgbuf* msgp,int msgsz,long msgtyp,int msgflg)
功能:从msqid代表的消息队列中读取一个msgtyp类型的消息,并吧消息存储在msgp指向msgbuf结构中。在成功读取一条消息后,队列中的这条消息将被删除。
msg.c
#include
#include
#include
#include
#include
#include
#include
struct msg_buf
{
long mtype; //消息类型>0
char data[255];//消息数据的首地址
};
int main()
{
key_t key;
int msgid;
int ret;
struct msg_buf msgbuf;
/*
key_t ftok(char *pathname,char proj)
功能:返回文件名对应的键值
pathname:文件名
proj:项目名(不为0即可)
消息队列也是一个文件
*/
key=ftok("/tmp/2",'a');
printf("key=[%x]\n",key);
/*
打开/创建
#include
#include
#include
int msgget(key_t key,int msgflg)
key:键值,由ftok获得
msgflg:标志位
返回值:与键值key相对应的消息队列描述字
*/
msgid=msgget(key,IPC_CREAT|0666);
if(msgid==-1)
{
printf("creat error\n");
return -1;
}
msgbuf.mtype=getpid();//发送消息类型
strcpy(msgbuf.data,"test haha");
//发送数据
//int msgsnd(int msqid,struct msgbuf* msgp,int msgsz,int msgflg)
//将msgbuf.data中的数据发送到消息队列中
ret=msgsnd(msgid,&msgbuf,sizeof(msgbuf.data),IPC_NOWAIT);
if(ret==-1)
{
printf("send message error\n");
return -1;
}
memset(&msgbuf,0,sizeof(msgbuf));//将msgbuf全部格式化为0
//接收数据
/*
int msgrcv(int msqid,struct msgbuf* msgp,int msgsz,long msgtyp,int msgflg)
功能:从msqid代表的消息队列中读取一个msgtyp类型的消息,并吧消息存储在msgp指向msgbuf结构中。
在成功读取一条消息后,队列中的这条消息将被删除
*/
ret=msgrcv(msgid,&msgbuf,sizeof(msgbuf.data),getpid(),IPC_NOWAIT);
if(ret == -1)
{
printf("recv message err\n");
}
printf("recv msg = [%s]\n",msgbuf.data);
return 0;
}
五、信号量
信号量(信号灯):与其他进程间通信方式不大相同,主要用途是保护临界资源。进程可以根据他判定是否能够方位某些共享资源,除了用于访问控制外,还可以用于进程同步。
二值信号量:信号灯的值只能去0或1。最多一个进程访问
计数信号量:信号灯的值可以去任意非负值。课多个进程同时访问
#include
#include
#include
int semget(key_t key,int nsems,int semflg);
key:键值,由ftok获得
nsems:指定打开或者新创建的 信号灯集 中将包含信号灯的数目
semflg: 标识,同消息队列
标志位:
IPC_CREAT:创建新的消息队列
IPC_EXCL:与IPC_CREAT一同使用,表示如果要创建的消息队列已经存在,则返回错误
IPC_NOWAIT:读写消息队列要求无法的到满足时,不阻塞
int semop(int semid,struct sembuf *sops,unsigned nsops)
功能:对信号量进行控制
semid:信号量集的ID
sops:是一个操作数组,表明要进行什么操作
nsops:sops所指向数组的元素个数
struct sembuf
{
unsigned short sem_num;
short sem_op;
short sem_flg;
}
sem_flg:信号操作标志,可能的选择有两种:
IPC_NOWAIT:对信号的操作不能满足时,semop()不会阻塞,并立即返回,同时设定错误信息。
IPC_UNDO:程序结束时(不论正常或不正常)释放信号量,这样做的目的在于避免程序在宜昌情况下结束时未将锁定的资源解锁,造成该资源永远锁定。