通讯方式
单机通讯:FIFO,命名全双工通道,消息队列,信号,共享存储
联网通讯:STREAMS(嵌套字),socket
无名通道
只能用于父子进程或者兄弟进程中,有固定的写端和读端。生成的写端和读端的文件标识符不属于文件系统中,但能但文件的io进行操作,关通道,读通道,写通道都用标准c库中的文件操作函数执行。
创建函数:
int pipe(int fd[2]);
返回值:成功0 失败-1
fd[0]是读通道,fd[1]是写通道,若要关闭管道,将两个通道关闭即可。
运用实例:
先创建无名管道,然后创建子进程,子进程将sendbuf里的数据写入管道,父进程等待子进程结束,然后读管道中的数据。
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int pid,flag;
int fd[2];
char *sendBuf = "yzwddsg";
char readBuf[1024] = {0};
flag = pipe(fd);
pid = fork();
if(pid < 0)
{
printf("fork error\n");
}
else if(flag == -1)
{
printf("pipe error\n");
}
else if(pid > 0)
{
wait();
printf("here is ppid\n");
close(fd[1]);
read(fd[0],readBuf,30);
printf("get buff is:\n%s\n",readBuf);
}
else if(pid == 0)
{
close(fd[0]);
printf("here id cpid\n");
write(fd[1],sendBuf,30);
printf("write succues\n");
exit(0);
}
close(fd[0]);
close(fd[1]);
}
我学习时找到的例程文件都是要在一个进程运行读或写时将另一个通道关闭,应该是怕双方同时进行同一个操作时会导致文件出错,我尝试不关闭另一个通道进行操作发现也能正常运行,甚至我运行完一个接一个收后,将功能翻转,发现数据传输是正常的,但莫名的是不知道为什么我printf输出的顺序被破坏了。
命名管道
以文件的形式存在磁盘中,无关进程间可以进行通讯,也是用标志c库下的文件操作函数进行操作
相关函数:
创建函数:包含:<sys/stat.h>
int mkfifo(const char* pathname,mode_t mode);
pathname:管道文件路径:例"./a.out"
mode:
运用:
需要写两个函数,一个进行读管道,一个进行写管道
然后在读管道中运用mkfifo函数创建管道,如果创建成功返回0,失败返回-1(值得一提的是当管道文件已经操作的时候属于创建失败范畴),然后打开管道文件,读管道中的数据,如果管道中没数据那么将堵塞在读操作中,不再运行下去。
在写文件中直接进行打开管道文件,进行写操作。
读文件:
#include <sys/stat.h>
#include <stdio.h>
#include <sys/types.h>
#include <fcntl.h>
int main(void)
{
char readBuf[1024] = {0};
if(mkfifo("./file11",0600)==-1)
{
printf("fifo error\n");
}
printf("wait\n");
sleep(3);
int fd = open("./file11",O_RDONLY);
read(fd,readBuf,30);
printf("read succse,readBuf=%s\n",readBuf);
close(fd);
}
写文件:
#include <sys/stat.h>
#include <stdio.h>
#include <sys/types.h>
#include <fcntl.h>
int main(void)
{
char *sendBuf = "wtmznb";
int fd = open("./file11",O_WRONLY);
int nwrite = write(fd,sendBuf,30);
printf("write succes,size = %d\n",nwrite);
close(fd);
}
消息队列
消息队列独立用于读写文件之外,读写文件都关闭后消息队列仍然存在。
消息队列可以,获取特定消息,如发送时的信息类型为111,只有接收函数的该参数也为111才能接收到。
相关函数:
声明头文件<sys/msg.c>
创建函数:
int msgget(key_t key , int flag);
成功:返回队列id 失败:返回-1
key_t :消息队列独有的键值区别其他消息队列。
flag:消息队列创建,一般给IPC_CREAT|0777,前一位是创建队列的宏,后面是读写权限。
添加消息函数:
int msgsnd(int msqid,const void *ptr,size_t size,int flag)
返回值:成功0 失败-1
msqid:创建队列时的key值
ptr:要发送数据存放的地址:一般定义为
struct msgbuf
{
long mtype;
char mtext[128];
};
该结构体型的数据。例:struct msgbuf sendbuf = {888,“hhhhhhhh”}然后将&sendbuf放入ptr
size:发送数据大小strlen(readbuf)
flag:为0为读写权限,同前面以及后面的函数中的flag参数一样
IPC_CREATE : 调用 shmget 时,系统将此值与其他共享内存区的 key 进行比较,如果存在相同的 key ,说明共享内存区已存在,此时返回该共享内存区的标识符,否则新建一个共享内存区并返回其标识符。
IPC_EXCL : 该宏必须和 IPC_CREATE 一起使用,否则没意义。当 shmflg 取 IPC_CREATE | IPC_EXCL 时,表示如果发现内存区已经存在则返回 -1,错误代码为 EEXIST 。
接收消息函数:
int msgrcv(int msqid,void* ptr,size_t size,long type,int flag)
type:需要接收的消息的类型
其他参数同发送函数
控制队列函数:
int msgctl(int msqid,int cmd,struct msqid_ds *buf)
cmd:队列状态:IPC_STAT,IPC_SET,IPC_RMID(将消息队列的链表移除),IPC_INFO
buf:如果要关闭队列用NULL即可。
根据参数生成键值key函数:
key_t ftok(const char *fname,int id)
返回:根据我们需求的参数生成一个新的队列键值
fname:路径名。例:“.”
id:我们自定义的一个id
应用:
在读文件中创建消息队列,然后用msgrcv函数等待写入数据
在写文件中,定义一个数据结构体,然后将结构体写入发送队列函数msgsnd中,实现发送数据。
读文件
#include <sys/msg.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdlib.h>
#include <string.h>
struct msgbuf
{
long mtype;
char mtext[128];
};
int main(void)
{
key_t key;
int msgId;
struct msgbuf readbuf;
key = ftok(".",1);
if(key == -1)
{
printf("msg error\n");
exit(-1);
}
msgId = msgget(key,IPC_CREAT|0777);
msgrcv(msgId,&readbuf,sizeof(readbuf.mtext),888,0);
printf("get buf is:%s\n",readbuf.mtext);
msgctl(msgId,IPC_RMID,NULL);
}
~
写文件
#include <sys/msg.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdlib.h>
#include <string.h>
struct msgbuf
{
long mtype;
char mtext[128];
};
int main(void)
{
key_t key;
int msgId;
struct msgbuf sendbuf = {888,"yzwddsg"};
key = ftok(".",1);
if(key == -1)
{
printf("msg error\n");
exit(-1);
}
msgId = msgget(key,IPC_CREAT|0777);
msgsnd(msgId,&sendbuf,strlen(sendbuf.mtext),0);
printf("get buf is:%s",sendbuf.mtext);
msgctl(msgId,IPC_RMID,NULL);
}
共享内存
在物理内存中创建一个共享内存,文件的虚拟地址映射到同一个物理内存上,那么这块内存就是共享内存。
相关函数:
声明#include <sys/shm.h>
创建、获取共享内存函数:
int shmget(key_t key,size_t size,int flag)
返回值:共享内存的标识符ID,失败-1
key:键值开辟方法与消息队列一样
size:共享内存需要开辟的大小(必须为1M的倍数)
flag:也与消息队列中使用的一样
连接共享内存到当前进程的地址空间函数:
void* shmat(int shm.id,const void *addr,int flag);
连接成功返回指向共享内存的指针,失败返回-1。
shm.id:共享内存的标识符ID
addr:若为NULL:共享内存会被attach到一个合适的虚拟地址空间,建议使用NULL;不为NULL:系统会根据参数及地址边界对齐等分配一个合适的地址
flag:IPC_RDONLY:附加只读权限,不指定的话默认是读写权限;IPC_REMAP:替换位于shmaddr处的任意既有映射:共享内存段或内存映射(如果为开辟空间还要或是权限0666)
断开与共享内存的链接函数:
int shmdt(void *addr)
addr: 开辟的共享内存段的地址
控制共享内存函数:
int shmctl(int shm_id,int cmd,struct shmid_ds *buf)
cmd:和控制队列函数msgctl中的一样。
应用:
写函数开辟共享内存,读函数打开该共享内存,写函数将数据写入后等待5s,然后关闭共享内存,读函数在5s内读取共享内存。
写函数
#include <sys/msg.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdlib.h>
#include <string.h>
#include <sys/shm.h>
int main(void)
{
key_t key;
char *shmaddr;
int shmid;
key = ftok(".",1);
if(key == -1)
{
printf("msg error\n");
exit(-1);
}
shmid = shmget(key,1024*2,IPC_CREAT|0666);
shmaddr = shmat(shmid,0,0);
strcpy(shmaddr,"slyyy");
printf("shmid is:%d\n",shmid);
sleep(8);
shmdt(shmaddr);
shmctl(shmid,IPC_RMID,NULL);
}
读函数
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdlib.h>
#include <string.h>
#include <sys/shm.h>
int main(void)
{
key_t key;
char *shmaddr;
int shmid;
key = ftok(".",1);
if(key == -1)
{
printf("msg error\n");
exit(-1);
}
shmid = shmget(key,1024*2,0);
shmaddr = shmat(shmid,0,0);
printf("shmid is:%d\n",shmid);
printf("shmid is:%s\n",shmaddr);
shmdt(shmaddr);
return 0;
注意:如果写函数在读函数读取前就已经关闭共享内存了,那么读函数打开不了共享内存。
信号(初级用法)
信号定义在signal.h头中,具体信号名、信号id可以使用kill -l来查看,不存在0信号,kill对于信号0有特殊应用。
信号的处理方式有三种:忽略、捕捉、默认动作
相关函数:
声明:#include <signal.h>
捕捉信号函数:
sighandler_t signal(int signum, sighandler_t handler);
返回值:返回先前的信号处理函数指针,如果有错误则返回SIG_ERR(-1)。
signum:捕获的信号宏或者id,并传入handler函数的参数中
handler:信号处理函数:void handler(int signum){“处理的代码”};(这个函数需要自定义)如果想对该信号进行忽略操作,那么这个参数输入STGIGM
信号发送函数
int kill(pid_t pid, int sig);
pid:操作的进程id。
sig:信号id。
应用:
信号接收处理函数:在mian中用signal捕获信号,然后到handler进行信号的处理,将接收到的信号id打印出来,并且打印对应的宏。
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
void handler(int signum)
{
printf("signum = %d\n",signum);
switch(signum)
{
case 2:
printf("signum = %d",signum);
break;
case 9:
printf("signum = %d",signum);
break;
case 10:
printf("signum = %d",signum);
break;
}
}
int main(void)
{
signal(SIGINT,handler);
while(1);
return 0;
}
信号发送函数,main设置为外部输入参数的模式,将argv[1]值将asc码转化为int型,写入sig。argv[2]值转化为int型写入pid,然后用kill函数发送信号。
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
int main(int argc,char **argv)
{
int pid,sig;
sig = atoi(argv[1]);
pid = atoi(argv[2]);
kill(pid,sig);
return 0;
}
信号(高级)
在发送信号的同时可以携带数据,接收时也接收得到数据,进行相应处理。
相关函数:
接收信号函数:
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
signum:接收到的信号id
act:参数指定新的信号处理方式,结构体struct sigaction:
sa_handler此参数和signal()的参数handler相同,代表新的信号处理函数
sa_mask 用来设置在处理该信号时暂时将sa_mask 指定的信号集搁置
sa_flags 用来设置信号处理的其他相关操作,下列的数值可用。
SA_RESETHAND:当调用信号处理函数时,将信号的处理函数重置为缺省值SIG_DFL
SA_RESTART:如果信号中断了进程的某个系统调用,则系统自动启动该系统调用
SA_NODEFER :一般情况下, 当信号处理函数运行时,内核将阻塞该给定信号。但是如果设置了 SA_NODEFER标记, 那么在该信号处理函数运行时,内核将不会阻塞该信号
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
}
sa_sigaction结构体中存储着:signum、siginfo_t结构体(其中包含信号发送时携带的数据,发送信号进程的pid等信息)、指针context(空为信号没携带信息,非空为信号携带了信息)
oldact:参数输出先前信号的处理方式(如果不为NULL的话)。
发送携带信息的信号函数:
int sigqueue(pid_t pid,int sig,const union sigval value)
pid:发送目标的pid
sig:发送的信号
value:携带的信息
应用:
数据接收函数:
将处理函数handler放入act结构体的函数指针中。
act结构体的sa_flag接收信号为SA_SIGINFO
然后用sigaction函数处理接收。
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
void handler(int signum,siginfo_t *info,void *context)
{
printf("signum = %d\n",signum);
if(context != NULL)
{
printf("get data = %d\n",info->si_int);
printf("get data = %d\n",info->si_value.sival_int);
}
}
int main(void)
{
printf("pid = %d",getpid());
sleep(3);
struct sigaction act;
act.sa_sigaction = handler;
act.sa_flags = SA_SIGINFO;
sigaction(SIGUSR1,&act,NULL);
while(1);
return 0;
}
发送数据:
大致与初级的信号处理代码一样,多了一个union sigval value的参数输入。然后在用sigqueue输出信号及value
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
int main(int argc,char **argv)
{
int pid,sig;
sig = atoi(argv[1]);
pid = atoi(argv[2]);
union sigval value;
value.sival_int = 100;
sigqueue(pid,sig,value);
return 0;
}
总结
无名管道,只能在有关系的进程中进行通讯,而且一般一个进程就只能执行读或写其中一种
有名管道,能在无关进程之间进行通讯,一般一个进程只能进行单向通讯
消息队列,独立于进程外,能进行双向通讯,优于管道的地方:队列一直存在,如果有新的文件接入直接读取就好了,而管道需要给他接个新的接口,有时候接口多了会很难debug,具体看这个链接什么是消息队列_牧小七的博客-CSDN博客
共享内存,可以双向通讯,共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。
信号