进程间通信:指在不同进程间传递或交换信息。
IPC技术Linux的三种主要通信方式:管道,套接字,System V IPC。
管道:
普通管道:半双工,单向传送数据,且只能在同源进程间使用。
命名管道:FIFO队列,借助外存解除普通管道只能在同源进程间使用的限制。
System V IPC包括:消息队列、信号量和共享内存
套接字:分为UNIX域套接字和Internet套接字。
信号:不能称之为真正意义上的IPC,只是简单的事件通知,并不能完成数据传输。
信号一种软中断,信号作为进程间通信的一种机制,由一个进程发送给另一个进程。
信号的生命周期:从创建一个信号,并且一直保存,直到内核可以根据这个信号做出相应动作为止,然后引发这个动作。
信号产生情况-
用户在终端按下一个组合键;
-
硬件异常;
-
进程调用kill函数发送信号;
-
当检测到某软件异常时产生信号;
忽略信号:不进行任何处理
捕获信号:定义信号处理函数,当信号发生时,执行相应的处理函数
执行默认动作:Linux为每种信号规定了默认操作
SIGKILL与SIGSTOP无法被捕捉和忽略,作用:管理员在任何时候都可以中断结束任意进程。
发送信号kill调用
#include
#include
int kill(pid_t pid,int sig);//向指定进程发送sig信号
pid: >0:发送指定PID进程;=0:将信号发送给和目前进程所在进程组的所有进程;=-1:将信号广播给系统内所有进程;<-1:发送信号给PID的绝对值进程。
alarm定时器
#include
int alarm(int seconds);//设定一个定时器,在指定秒数后向进程自身发送一个SIGALRM信号,该信号默认处理是终止当前进程。
seconds:设置为0,取消所有闹钟;否则覆盖前一个闹钟并返回剩余秒数。
函数返回值:0或者剩余秒数。
raise
#include
int raise(int sig);//给自己发信号sig,等价与kill(getpid(),sig);
进程挂起
#include信号捕获与处理int pause(void);//挂起进程直到有信号到达 int sleep(int sec);//挂起进程直到有信号到达或过去sec秒,返回0或者剩余秒数
sighandler_t signal(int signum,sighandler_t handler);//向内核注册指定信号的处理
signum:待捕获或忽略的信号
handler:捕获信号由系统回调的函数,特殊值:SIG_IGN:忽略信号;SIG_DFL:恢复默认
返回值:成功时返回原定义信号处理函数,失败返回:SIG_ERR
流程
- 定义处理函数
- 使用signal注册
- 使用完后,恢复到默认处理:signal(signum,SIG_DFL)
#include信号阻塞#include #include #include void fun_ctrl_z(); int main() { (void) signal(SIGTSTP,fun_ctrl_z);//如果摁了 +Z,调用fun_ctrl_z函数 printf("主程序:程序进入一个无限循环!n"); while(1) { printf("这是一个无限循环(要退出请按 +Z键!)n"); sleep(10); } return 0; } void fun_ctrl_z()//自定义信号处理函数 { printf("t已进入自定义信号处理函数,再按 +Z退出n"); printf("t此例不处理,重新恢复SIGINT信号的系统默认处理。n"); (void) signal(SIGTSTP,SIG_DFL);//恢复默认处理 }
sigprocmask(int how,sigset_t *set,sigset_t *oldset)//设置或查询阻塞信号集
how:
-
SIG_BLOCK:将set中的信号添加到阻塞信号集
-
SIG_UNBLOCK:将set中的信号从阻塞信号集中删除
-
SIG_MASK:将set信号集设置为阻塞信号集
set:新的信号集,若为NULL则忽略how,只是将原信号集保存到oldset
oldset:保存原信号集
int sigemptyset(sigset_t *set);//清空信号集 int sigfillset(sigset_t *set);//初始化信号集,并用系统所有有效信号填充到信号集 int sigaddset(sigset_t *set, int signo)//将指定信号添加到信号集 int sigdelset(sigset_t *set, int signo)//将指定信号从信号集删除 int sigismember(sigset_t *set int signo)//判断指定信号是否包含在信号集中。包含返回1;不包含返回0;失败返回-1
#include管道#include #include #include #include void fun_ctrl_c(); int main() { int i; sigset_t set,pendset; struct sigaction action; (void) signal(SIGINT,fun_ctrl_c); if(sigemptyset(&set)<0)//获得一个空信号集 perror("初始化信号集合错误"); if(sigaddset(&set,SIGINT)<0)//向信号集加入 +C信号 perror(" +C加入信号集合错误"); if(sigprocmask(SIG_BLOCK,&set,NULL)<0)//将信号集加入到阻塞信号集 perror("往信号阻塞集增加一个信号集合错误"); else { for(i=0;i<5;i++) { printf("按下 +C试试,此处语句依旧显示n"); sleep(2); } } if(sigprocmask(SIG_UNBLOCK,&set,NULL)<0)//将信号集从阻塞信号集移出 perror("从信号阻塞集删除一个信号集合错误"); return 0; } void fun_ctrl_c() { printf("t您按了 +C,但不予执行n"); (void) signal(SIGINT,SIG_DFL); }
pipe系统调用
int pipe(int fds[2]);
创建管道同时创建两个文件描述符,其中fds[0]已读打开,fds[1]以写打开。成功返回0,失败返回-1。
头文件:unistd.h
使用步骤如下:
-
创建所需的管道;
-
生成(多个)子进程;
-
关闭/复制文件描述符,使之与相应的管道末端相联系;
-
关闭不需要的管道末端;
-
进行通信活动;
-
关闭所有剩余的打开文件描述符
#include#include #include #include int mkfifo(char *pname,mode_t mode);//创建一个命令管道,成功返回0,失败返回-1. int mknod(const char *pname,mode_t mode,dev_t dev);//创建一个特殊文件
pname:路径
mode:新建管道文件的权限
dev_t:文件类型,S_IFREG, S_IFCHR, S_IFBLK, S_IFIFO or S_IFSOCK
步骤:
-
使用mkfifo或mkmod创建管道文件
-
以只读或以只写方式打开(open)管道文件
-
以read读取或write写管道
-
关闭管道文件
默认管道的读写是阻塞的,而且只有两端都打开后才能进行读写。
#include#include #include #include #include #include int main(int argc,char *argv[]){ mkfifo("tp",0644);//建立命令管道 int infd; infd = open("test",O_RDONLY);//打开源文件 if(-1 == infd){ perror("open"); exit(1); } int outfd; outfd = open("tp",O_WRONLY);//打开管道 if(-1 == outfd){ perror("open"); exit(1); } char buf[1024]; int n; while((n = read(infd,buf,1024)) > 0){ write(outfd,buf,n);//写入管道 } //关闭读端写端 close(infd); close(outfd); return 0; }
#include消息对列#include #include #include #include #include int main(int argc,char *argv[]){ //写端 int outfd; outfd = open("target.bak",O_WRONLY | O_CREAT | O_TRUNC,0644); //打开或建立目标文件 if(-1 == outfd){ perror("open"); exit(1); } int infd; infd = open("tp",O_RDONLY);//只读打开命名管道 if(-1 == infd){ perror("open"); exit(1); } char buf[1024]; int n; while((n = read(infd,buf,1024))>0){ write(outfd,buf,n);//写入目标文件 } close(infd); close(outfd); unlink("tp"); return 0; }
消息队列是链表队列,提供了一种从一个进程向另一个进程发送一个数据块的方法。
在程序中若要使用消息队列,必须要能知道消息队列key,因为应用进程无法直接访问内核消息队列中的数据结构,因此需要一个消息队列的标识,让应用进程知道当前操作的是哪个消息队列,同时也要保证每个消息队列key值的唯一性。
struct msg{//消息体
long mtype;//消息类型,固定项,必须大于0
···//自定义项
};
#include#include #include key_t ftok(char *path,int proj_id);//产生唯一的msgID int msgget(key_t mkey,int msgflg);//创建、打开消息对列 int msgsnd(int msqid,const void *msgp,size_t msgsz,int msgflg);//发送消息 ssize_t msgrcv(int msqid,void *msgp,size_t msgsz,long msgtyp,int msgflg);//接受消息 int msgctl(int msqid, int cmd, struct msqid_ds *buf);//控制消息队列
创建一个key
path:指定一个路径,在不同的进程中通过约定的path确保可产生相同的msgid。
proj_id:一个序数,取值在0-127之间。
创建、打开消息队列
key:消息队列的ID,若为IPC_PRIVATE或key代表的消息队列不存在且在msgflg中指明了IPC_CREAT。
msgflg:
-
IPC_CREAT:若消息队列不存在则创建,同时指定权限:IPC_CREAT|0666
-
IPC_EXCL:和IPC_CREAT配合使用,若消息队列存在则失败。
发送消息
把指定消息插入到指定的消息队列中
-
msqid:消息队列ID
-
msgp:消息体,应该是一个结构体指针
-
msgsz:发送消息的大小
-
msgflg:0或IPC_NOWAIT,0则若队列满则被阻塞;IPC_NOWAIT若队列满在立即返回错误。
接收消息
从指定消息队列接收消息
-
msqid:消息队列ID
-
msgp:消息体,应该是一个结构体指针
-
msgsz:接收消息的大小
-
msgtyp:接收那条消息,0:队列第一条消息;绝对值>0:接收|msgtype|与msgp的mtype相同的消息
-
msgflg:0:若没有消息则阻塞;IPC_NOWAIT:若没有消息则立即返回错误。
控制消息队列
- msqid:消息队列ID
cmd的指定操作:
-
IPC_RMID:删除指定的消息队列,buf指定为NULL
-
IPC_STAT:将内核中的消息结构复制到用户空间中的buf中
-
IPC_SET:设置消息队列的有效UID和GID、操作权限等。
#include#include #include #include #include #include #include #include #include #include #define SERVER_MSG 20 #define CLIENT_MSG 21 #define NAMESIZE 20 #define QUIT "88" struct msgbuf{ long msg_type; char msg_user[20]; char msg_time[30]; char msg_text[100]; }; int main(){ int qid; char username[NAMESIZE]; key_t key; int len, rfd; fd_set read_set; struct msgbuf msg; struct timeval timeout;//这个是用来判断是否超时 time_t t; if ((key=ftok(".",'a')) == -1){ printf("failed to make a standard key.n"); exit(1); } if((qid=msgget(key,IPC_CREAT|0666)) == -1){ printf("failed to get message queue"); exit(1); } printf("给自己起个名字吧: "); scanf("%s",username); len = sizeof(msg.msg_text); rfd = fileno(stdin); fflush(stdin); printf("----------n"); fflush(stdout); while(1){ if(msgrcv(qid, &msg, len,CLIENT_MSG,IPC_NOWAIT)>0 ){ //读取来自client的消息 printf("%s:%sn%sn",msg.msg_user,msg.msg_time,msg.msg_text); printf("----------n"); fflush(stdout); } FD_ZERO(&read_set); FD_SET(rfd, &read_set); timeout.tv_sec = 0; timeout.tv_usec = 500000; if(select(rfd+1, &read_set, NULL, NULL, &timeout) <= 0) continue; if(FD_ISSET(rfd, &read_set)){ fgets(msg.msg_text, len, stdin); msg.msg_text[strlen(msg.msg_text) - 1]=' '; if( strlen(msg.msg_text) == 0) continue; if( strcmp(QUIT, msg.msg_text) == 0){//输入QUIT 结束聊天 msgctl(qid,IPC_RMID,NULL);//删除消息 break; } msg.msg_type = SERVER_MSG;//设置消息类型 time(&t); strcpy(msg.msg_time, ctime(&t)); strcpy(msg.msg_user, username); msgsnd(qid, &msg, len, IPC_NOWAIT);//队列未满则发送消息 printf("----------n"); fflush(stdout); } } return 0; } //作为client与server相似,更改部分代码即可



