******************day05进程间通信**********************
进程间的7种通信方式
传统的通信方式:
有名管道
无名管道
信号
IPC通信:
消息队列
共享内存
信号灯
BSD:
socket
*****************************************************************
【1】无名管道
定义:
无名管道是一种特殊类型的文件,在内核空间中对应的资源即是一段内存空间,内核在这段空间以循环对列的方式临时存入一个进程发送给另一个进程的信息,这段内核空间完全由操作系统管理和维护,应用程序只需要,也只能通过系统调用来访它。
无名管道和普通的文件有很大的差异:无名管道的内核资源在通信两进程退出后会自动释放。跟普通文件不同。
存储大量常规信息,但是编程方式,具有和普通文件一样的特点,可以使用read/write等函数进行读写操作,只是
注意:特殊的文件只能用文件IO操作。
读写的特点有一定的差异,另外,不能用lseek函数来修改当前的读写位置,因为FIFO需要满足FIFO的原则。
特点:
1、使用条件:只能用于具有亲缘关系(父子进程,兄弟进程等)的进程之间的通信
2、通信模式:半双工模式,fd[0]作为读端,fd[1]作为写端
3、读写方式:对于它的读写采用文件IO(不支持lseek函数)
4、读操作会阻塞(等待):在管道中无数据情况下。
写操作会阻塞(等待):当管道被写满时,无名管道的大小为64K
5、管道破裂:管道读端关闭,再向管道中写数据时。
即向管道中写入数据的进程将收到内核传来的SIGPIPE信号。
函数接口:
pipe---
#include <unistd.h>
int pipe(int pipefd[2]);
函数的功能:在内核空间创建一个无名管道
参数:pipefd 固定的读写端 pipefd[0]---无名管道读端,
pipefd[1]---写端
返回值:成功返回0,失败-1
无名管道的4种特殊形式:
<1>以阻塞的方式读无名管道:
①如果当前没有进程(包括当前进程)访问写端,读操作立即返回,管道中无数据:立即返回0
注意:如果写端关闭,并且管道中实现没有数据的话,读端没有任何意义。
<2>以阻塞的方式读无名管道,如果某个进程(包括当前进程)可以访问写端,管道端阻塞(等待)
注意:读写端打开,当前管道中没有数据,阻塞等待。
<3>如果以阻塞的方式写无名管道,如果没有某个进程(包括当前进程)可以访问读端,写操作会收到SIGPIPE信号,write函数返回-1,如果当前有某进程可以访问读端,且管道中有空间,则写入成功。
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
功能:依参数signum 指定的信号编号来设置该信号的处理函数
参数:signum:指定所要处理的类型,除了SIGKILL和SIGSTOP外的任一种信号
返回值:返回先前的信号处理函数指针,错误返回SIG_ERR(-1)。
<4>如果以阻塞的方式写无名管道,如果当前管道已经满,则阻塞等待当前进程,如果有多个进程试图写,当进程读管道唤醒写操作时 ,唤醒哪个进程未知,需要多个进程同时写入时,需要相应的避免竞争机制。
注意:无名管道只有所有进程都结束的时候,管道内的资源才完全释放,否则一直存在
【2】有名管道
定义:
有名管道:有自己的名字,但是有名管道名称保存在磁盘上,但是内容保存在内核中
有名管道和普通的文件一样具有磁盘存放路径,文件的权限和其他的属性信息,但是有名管道和普通文件又有区别,有名管道没有在磁盘上存真正的信息,而是在内存中存放,2个进程结束后自动丢失,通信结束后有名管道的文件路径本身存在,这是和无名管道区别的地方。
特点:
1、有名管道可以使互不相关的两个进程互相通信。
2、有名管道可以通过路径名来指出,并且在文件系统中可见。
3、读写方式:对于它的读写采用文件IO(不支持lseek函数)
4、其它与无名管道一样
5.有名管道读端写端不固定。一段读,另一端写。当然某一端既可以读也可以写,要用父子进程实现。
函数接口:
mkfifo
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
函数的功能:创建一个有名管道
参数:pathname 有名管道的路径加名称
mode 打开权限 0664
mode & ~umask
返回值:成功返回0,失败-1
EACCESS 参数pathname所指定的目录路径无可执行的权限
EEXIST 参数pathname所指定的文件已存在。
ENAMETOOLONG 参数pathname的路径名称太长。
ENOENT 参数pathname包含的目录不存在
ENOSPC 文件系统的剩余空间不足
ENOTDIR 参数pathname路径中的目录存在但却非真正的目录。
EROFS 参数pathname指定的文件存在于只读文件系统内。
有名管道的4种特殊形式:
<1>如果希望以写的方式打开管道,则需要另一进程以读的方式打开管道。即如果以某种方式打开有名管道,则系统将阻塞进程,直到另一个进程(包括自己)以另一种方式打开该管道后才会继续运行,显然一个进程可以通过可读可写的方式打开管道,当前进程充满了读写2个身份,此时进程入会阻塞。
<2>两进程已经完全打开管道,阻塞读操作
无数据,阻塞读
有数据,读出来
<3>两进程已经完成打开管道的操作,阻塞写操作
管道中没空间,阻塞
有空间写入,写满阻塞。
<4>两进程已经完全打开管道操作,中途其中一个进程退出
未退出一端是写操作,将返回SIGPIPE信号
未退出一端是读操作,读操作将不再阻塞,直接返回0(父子进程)
【3】信号---进程间唯一的一种异步通信方式。
函数接口:
kill
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
函数的功能:向执行的进程发送信号
参数:pid 执行的进程号
pid <-1 发送当前的进程|id|等于调用进程组id下的任意一个子进程
pid=-1 发送给所有的进程,除了init(1号进程)
pid =0 发送给同组下的所有进程
pid >0 发送给指定的进程
sig 指定的信号(kill -l)
返回值:成功返回0,失败-1;
signal
#include <signal.h>
typedef void (*sighandler_t)(int);===typedef sighandler void (*)(int)
sighandler_t signal(int signum, sighandler_t handler);
函数的功能:向当前进程发送信号,并设置信号处理方式
参数:signum 发送信号的类型
handler 信号的处理方式
返回值:一般不做返回。
对于信号的3种处理方式
(1)忽略 SIGIGN 不能控制内核发送的信号,但是可以选择不处理。
(2)默认 内核发送什么信号---做相应的处理 SIGDLF
(3)捕捉 当前接收来自内核的信号,在对应的信号处理函数当中做处理 handler
alarm
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
函数的功能:设置闹钟时间,等到时间到达,内核向当前进程发送SIGALARM信号,结束当前进程
参数:seconds 设置的闹钟时间
返回值:1)如果在当前alarm之前没有设置过闹钟时间返回0
2)如果在当前alarm之间已有设置的闹钟时间,返回上一次剩余的时间
3)alarm以最新设置的alarm为主。
raise=kill(getpid(),sig)
【4】IPC对象
system V IPC对象
和文件一样,IPC在使用前必须创建,每种IPC有特定的生产者,所有者和访问权限
使用ipcs查看
手动删除某个ipc机制,使用ipcrm命令
linux系统为每个ipc机制都分配了唯一的id,所有针对该ipc机制的操作都使用该id值,那么通信双方
需要通过某个办法获取ID值,创建者根据创建函数的返回值可以获取该值,linux2个进程不能随意访问对方的空间
,也就不能直接获取这一ID值。
解决办法:ipc在实现时约定使用可以值作为参数创建,如果在创建时使用了相同的可以值将得到一个IPC对象的ID
(即一方创建,另一方获取的是ID)
ftok---获取key值
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
函数的功能:生成外部键值用于2个不相关的进程建立联系
参数:pathname 路径名,必须传一个已经存在的 一般传入当前路径"."
proj_id 整型变量 字符 ASCII
返回值:成功返回key(键值)
失败-1
注意:如果使用相同的文件路径及整数,得到的key值是唯一的,唯一的key值创建某类IPC机制时将得到同一个IPC
机制(但是使用使用相同的key值分别创建一个消息队列和一个信号,两者没有关系),而文件的路径的
访问对两个进程来说很容易统一,因此便捷地实现了2个进程间通信机制ID的确定
ftok(讲解到这里,要是时间来的及讲解下面的)
【5】消息队列
消息对列是消息的链式队列,消息队列的FIFO原则仅仅适用于同类型的消息,在消息队列进行如下规定,不同
系统的限制值可以通过msgctl函数使用IPC_INFO参数获得,注意不同的linux版本此值不同。
消息队列即满足的管道的先进先出的原则,用户又可以按照指定的消息类型去存取,消息队列存在于内存当中,
在使用消息队列的时候,通过指定消息对列的标识符。
默认的情况下:整个系统最多允许有16个消息队列。
每个消息对列最大为16384字节
消息对列中每个消息最大为8192字节。
消息对列的使用步骤:
1.ftok----使外部不相关的进程建立联系
2.msgget---创建一个消息队列
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
函数的功能:创建或者打开一个消息队列
参数:key 生成key值
msgflg IPC_CREAT 0664
IPC_EXCL
返回值:成功返回消息队列的标识符(消息队列id)
失败-1;
3.msgctl---删除消息队列
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
函数的功能:删除消息队列
参数:msqid 消息队列的标识符
cmd
IPC_STAT 获取消息队列的信息
IPC_SET 设置消息队列
IPC_RMID 删除消息队列(常用)
buf 消息队列的属性信息 NULL
返回值:成功返回0,失败-1
4.msgsnd--发送消息
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
函数的功能:发送消息
参数:msqid 消息队列的标识符
msgp 指针 执向一个结构体
struct msgbuf {
long mtype; /* message type, must be > 0 */---消息的类型
char mtext[1]; /* message data */----消息内容(正文)
};
msgsz 消息的内容的大小
msgflg IPC_NOWAIT 非阻塞
0 阻塞
返回值:成功返回0,失败-1
5.msgrcv----接收消息
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
int msgflg);
函数的功能:接收消息
参数:msqid 消息队列的标识符
msgp 指针 执向一个结构体
struct msgbuf {
long mtype; /* message type, must be > 0 */---消息的类型
char mtext[1]; /* message data */----消息内容(正文)
};
msgsz 消息的内容的大小
msgtyp 消息的类型
>0 接收指定的消息队列中的第1条消息
=0 接收消息队列中的第1条消息
<0 接收消息小于消息|id|中消息类型最小的消息队列中的第1条消息
返回值:成功返回接收消息的实际字节数
失败-1;
作业:实现仿qq聊天
要求:2个不相关的外部进程,通过key值使2个进程建立外部连接
在每个进程当中去创建一个子进程
每一端都可以发送和接收消息