进程通信
进程间通信:指在不同进程间传递或交换信息。
IPC技术
Linux的三种主要通信方式:管道,套接字,System V IPC。
管道:
普通管道:半双工,单向传送数据,且只能在同源进程间使用。
命名管道:FIFO队列,借助外存解除普通管道只能在同源进程间使用的限制。
System V IPC包括:消息队列、信号量和共享内存
套接字:分为UNIX域套接字和Internet套接字。
信号:不能称之为真正意义上的IPC,只是简单的事件通知,并不能完成数据传输。
信号
一种软中断,信号作为进程间通信的一种机制,由一个进程发送给另一个进程。
信号的生命周期:从创建一个信号,并且一直保存,直到内核可以根据这个信号做出相应动作为止,然后引发这个动作。
信号产生情况
-
用户在终端按下一个组合键;
-
硬件异常;
-
进程调用kill函数发送信号;
-
当检测到某软件异常时产生信号;
信号处理
忽略信号:不进行任何处理
捕获信号:定义信号处理函数,当信号发生时,执行相应的处理函数
执行默认动作:Linux为每种信号规定了默认操作
SIGKILL与SIGSTOP无法被捕捉和忽略,作用:管理员在任何时候都可以中断结束任意进程。
发送信号
kill调用
#include<sys/types.h>
#include<signal.h>
int kill(pid_t pid,int sig);//向指定进程发送sig信号
pid: >0:发送指定PID进程;=0:将信号发送给和目前进程所在进程组的所有进程;=-1:将信号广播给系统内所有进程;<-1:发送信号给PID的绝对值进程。
alarm定时器
#include<unistd.h>
int alarm(int seconds);//设定一个定时器,在指定秒数后向进程自身发送一个SIGALRM信号,该信号默认处理是终止当前进程。
seconds:设置为0,取消所有闹钟;否则覆盖前一个闹钟并返回剩余秒数。
函数返回值:0或者剩余秒数。
raise
#include<unistd.h>
int raise(int sig);//给自己发信号sig,等价与kill(getpid(),sig);
进程挂起
#include<unistd.h>
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<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<stdlib.h>
void fun_ctrl_z();
int main()
{
(void) signal(SIGTSTP,fun_ctrl_z);//如果摁了<Ctrl>+Z,调用fun_ctrl_z函数
printf("主程序:程序进入一个无限循环!\n");
while(1)
{
printf("这是一个无限循环(要退出请按<Ctrl>+Z键!)\n");
sleep(10);
}
return 0;
}
void fun_ctrl_z()//自定义信号处理函数
{
printf("\t已进入自定义信号处理函数,再按<Ctrl>+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<stdio.h>
#include<stdlib.h>
#include<signal.h>
#include<sys/types.h>
#include<unistd.h>
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)//向信号集加入<Ctrl>+C信号
perror("<Ctrl>+C加入信号集合错误");
if(sigprocmask(SIG_BLOCK,&set,NULL)<0)//将信号集加入到阻塞信号集
perror("往信号阻塞集增加一个信号集合错误");
else
{
for(i=0;i<5;i++)
{
printf("按下<Ctrl>+C试试,此处语句依旧显示\n");
sleep(2);
}
}
if(sigprocmask(SIG_UNBLOCK,&set,NULL)<0)//将信号集从阻塞信号集移出
perror("从信号阻塞集删除一个信号集合错误");
return 0;
}
void fun_ctrl_c()
{
printf("\t您按了<Ctrl>+C,但不予执行\n");
(void) signal(SIGINT,SIG_DFL);
}
管道
pipe系统调用
int pipe(int fds[2]);
创建管道同时创建两个文件描述符,其中fds[0]已读打开,fds[1]以写打开。成功返回0,失败返回-1。
头文件:unistd.h
使用步骤如下:
-
创建所需的管道;
-
生成(多个)子进程;
-
关闭/复制文件描述符,使之与相应的管道末端相联系;
-
关闭不需要的管道末端;
-
进行通信活动;
-
关闭所有剩余的打开文件描述符
命令管道
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
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写管道
-
关闭管道文件
默认管道的读写是阻塞的,而且只有两端都打开后才能进行读写。
/*send.c*/
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
#include<fcntl.h>
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;
}
/*get.c*/
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<fcntl.h>
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<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
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、操作权限等。
/*server.c*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/stat.h>
#include <sys/select.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#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:%s\n%s\n",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]='\0';
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相似,更改部分代码即可