Linux系统提供了多种进程之间的通信方式
例如:无名管道,有名管道,信号等
1、如果是同一进程下的多个线程,共享资源,所以线程之间的通信只需要注意互斥和同步即可。
2、如果是多个进程。由于进程之间不共享资源,所以进程间的通信需要用到,系统提供的内核空间。

无名管道
1、本质也是一个内核空间的文件,存储在内存中,读写数据都是一次性的。
2、无名管道写入数据后,一旦读出数据,数据就消失了。
3、无名管道,一旦打开,就出现读端和写端,如果进行读取数据,就要先关闭写端,如果想要写数据,就要先关闭读端。
4、无名管道工作原理是半双工,也就是同一时刻只能是A写B读,或者A读B写。
5、无名管道只能进行亲缘进程间的通信。
6、管道文件相关的函数属于文件IO部分,只能使用文件IO读写。
7、管道的读写端属于文件描述符,也遵循最小未分配原则。
单工:只能是A向B发信息,B不能向A发信息。
半双工:同一时刻只能A向B发信息,或者B向A发信息。
全双工:同一时刻AB都是收发信息。
无名管道API:
#include <unistd.h>
int pipe(int pipefd[2]);
功能:创建一个无名管道.pipefd[0]读端 pipefd[1]:写端
参数:文件描述符数组
返回值:成功返回0,失败返回-1,并置位错误码。
eg:
int pipefd[2];
if(pipe(pipefd)==-1)
{
perror("pipe");
return -1;
}
练习:父进程写入管道,子进程读取管道数据
#include <myhead.h>
int main(int argc, const char *argv[])
{
int pipefd[2];
char buff[1024] = "hello world";
char s[1024];
if(pipe(pipefd)==-1)
{
perror("pipe");
return -1;
}//读端pipefd[0] 写端pipefd[1]
pid_t pid = fork();//创建子进程
if(pid==0)
{
close(pipefd[1]);//先关闭写端
while(1)
{
sleep(1);
read(pipefd[0],s,sizeof(s));
printf("儿子在读取:%s\n",s);//输出读取的数据
}
close(pipefd[0]);//关闭读端
}
else if(pid>0)
{
close(pipefd[0]);//先关闭读端
while(1)
{
sleep(1);
write(pipefd[1],buff,sizeof(buff));
}
close(pipefd[1]);//完成后关闭写端
}
else
{
perror("fork");
return -1;
}
return 0;
}
验证无名管道大小
#include <myhead.h>
int main(int argc, const char *argv[])
{
int pipefd[2];
char buff[1024] = "hello world";
char s[1024];
if(pipe(pipefd)==-1)
{
perror("pipe");
return -1;
}//读端pipefd[0] 写端pipefd[1]
pid_t pid = fork();//创建子进程
if(pid==0)
{
close(pipefd[1]);//先关闭写端
while(1)
{
sleep(1);
read(pipefd[0],s,sizeof(s));
printf("儿子在读取:%s\n",s);//输出读取的数据
}
close(pipefd[0]);//关闭读端
}
else if(pid>0)
{
close(pipefd[0]);//先关闭读端
while(1)
{
sleep(1);
write(pipefd[1],buff,sizeof(buff));
}
close(pipefd[1]);//完成后关闭写端
}
else
{
perror("fork");
return -1;
}
return 0;
}
1、当读端存在时,有多少就写多少,写够2^16K为止
2、当写端存在时,有多少就读多少,读完为止会在read处阻塞。
3、当读端不存在,写入管道,会导致管道破裂。
4、当写端不存在时,有多少就读多少,读完为止不会在read处阻塞。
有名管道
1、有名字的管道,相对于无名管道,可以进程非亲缘进程间的通信。
2、有名管道写入一次读取一次。
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
功能:创建一个有名管道用于非亲缘进程间的通信
参数1:有名管道名
参数2:创建时的权限。
返回值:成功返回0,失败返回-1,并置位错误码。
创建有名管道实现非亲缘进程间的通信
写端:
#include <myhead.h>
int main(int argc, const char *argv[])
{
int k = mkfifo("./myfifo",0664);//创建有名管道
if(k==-1)
{
perror("mkfifo");
return -1;
}
int fd1 = open("./myfifo",O_WRONLY);//打开管道
if(fd1==-1)
{
perror("open");
return -1;
}
char buff[1024];
while(1)//循环写入数据
{
printf("请输入内容:");
int res = read(0,buff,sizeof(buff));//输入从0号描述符读取数据
write(fd1,buff,res);//写入有名管道
}
close(fd1);//关闭有名管道
return 0;
}
读端:
#include <myhead.h>
int main(int argc, const char *argv[])
{
int fd2 = open("./myfifo",O_RDONLY);//打开管道文件
if(fd2==-1)
{
perror("open");
return -1;
}
char buff[1024];
while(1)//循环读取数据
{
int res = read(fd2,buff,sizeof(buff));
if(res==0)
{
printf("写入端退出\n");
break;
}
write(1,buff,res);//写入标准输出描述符
}
close(fd2);//关闭管道文件
return 0;
}
练习:创建两个子父进程父进程写入管道1,子进程读取管道2,父进程写入管道2,子进程读取管道1,实现全双工通信。

#include <myhead.h>
int main(int argc, const char *argv[])
{
int fd1 = open("./myfo1",O_WRONLY);
int fd2 = open("./myfo2",O_RDONLY);
if(fd1==-1||fd2==-1)
{
perror("open");
return -1;
}
char buff[1024];
pid_t pid = fork();
if(pid>0)//父进程写入管道1
{
while(1)
{
printf("请输入内容:\n");
int res = read(0,buff,sizeof(buff));
write(fd1,buff,res);//写入管道1
}
}
else if(pid==0)//子进程读取管道2
{
while(1)
{
int res = read(fd2,buff,sizeof(buff));
write(1,buff,res);//读取内容显示出来
}
}
else
{
perror("fork");
return -1;
}
return 0;
}
信号发送函数signal
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
功能:信号处理函数,忽略,默认和捕获操作。
参数1:信号号
参数2:
SIG_IGN:忽略信号,不执行任何的操作。
SIG_DFL:默认信号,执行信号原始的操作。
自定义函数:捕获,某个信号,捕获的信号处理方式由程序员决定。
返回值:成功返回前一个信号号,失败返回SIG_ERR, 并置位错误码。
2、尝试 忽略,默认,捕获(2) SIGINT也就是ctrl+c
#include <myhead.h>
void hander(int tmy)
{
if(tmy==SIGINT)
{
printf("捕获了ctrl+c\n");
}
}
int main(int argc, const char *argv[])
{
#if 0
if(signal(SIGINT,SIG_IGN)==SIG_ERR)//忽略ctrl +c信号
{
perror("signal");
return -1;
}
if(signal(SIGINT,SIG_DFL)==SIG_ERR)//默认ctrl +c信号
{
perror("signal");
return -1;
}
#endif
if(signal(SIGINT,hander)==SIG_ERR)//hander将会捕获SIGINT信号作为自己的参数
{
perror("signal");
return -1;
}
int k = 0;
while(1)
{
sleep(1);
printf("唐明宇打呼噜k = %d\n",k);
k++;
}
return 0;
}
3、尝试 捕获(17) SIGCHLD
#include <myhead.h>
//SIGCHLD
void hander(int tmy)
{
if(tmy==SIGCHLD)
{
printf("捕获到了SIGCHLD\n");
}
}
int main(int argc, const char *argv[])
{
pid_t pid;
pid = fork();
if(pid>0)
{
if(signal(SIGCHLD,hander)==SIG_ERR)//捕获SIGCHLD信号
{
perror("signal");
return -1;
}
}
else if(pid==0)
{
sleep(1);
exit(0);//成功退出子进程
}
else
{
perror("fork");
return -1;
}
wait(NULL);//阻塞回收子进程资源
return 0;
}
4、尝试捕获SIGSEGV信号
#include <myhead.h>
void fun(int tmy)
{
sleep(1);
if(SIGSEGV==tmy)
{
printf("内核发送了段错误信号\n");
}
}
int main(int argc, const char *argv[])
{
if(signal(SIGSEGV,fun)==SIG_ERR)//绑定信号
{
perror("signal");
return -1;
}
int *p = NULL;
*p = *p+1;
while(1);
return 0;
}
4、捕获SIGTSTP信号 。
#include <myhead.h>
void hander(int tmy)
{
if(tmy==SIGTSTP)
{
printf("捕获到了SIGTSTP\n");
}
}
int main(int argc, const char *argv[])
{
if(signal(SIGTSTP,hander)==SIG_ERR)//捕获SIGCHLD信号
{
perror("signal");
return -1;
}
while(1)
{
printf("hello\n");
sleep(1);
}
return 0;
}
5、SIGALRM信号使用,模拟下棋alarm函数
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
功能:设置定时时间,时间到了内核会向调用进程发送SIGALRM信号
如果第二次设置了定时时间,那么alarm函数返回的是上一次剩余的秒数。
参数:设置的秒数。
返回值:成功返回上一次预定的剩余秒数,如果没有上一次预定的秒数返回0
#include <myhead.h>
void hander(int sss)
{
if(sss==SIGALRM)
{
printf("收到内核发送的SIGALRM\n");
}
}
int main(int argc, const char *argv[])
{
if(signal(SIGALRM,hander)==SIG_ERR)//绑定SIGALRM信号
{
perror("signal");
return -1;
}
unsigned int k = alarm(3);//设置3秒
printf("k = %d\n",k);
sleep(4);//睡1秒
unsigned int m = alarm(5);//再次设定时间为5秒
printf("m = %d\n",m);
while(1);
return 0;
}
实现斗地主
#include <myhead.h>
void hander(int sss)
{
if(sss==SIGALRM)//捕获到信号
{
printf("系统为您自动出牌一张\n");
}
alarm(5);//重新设置定时时间
}
int main(int argc, const char *argv[])
{
if(signal(SIGALRM,hander)==SIG_ERR)
{
perror("signal");
return -1;
}
while(1)
{
alarm(5);//设定5秒的定时时间
printf("请输入你要出的牌:");//输入出的牌
char ch = fgetc(stdin);
getchar();
printf("您出的牌:%c\n",ch);
}
return 0;
}
6、信号发送函数:kill raise
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
功能:发送信号给调用进程的函数,可以给自己发送信号也可以给其他进程发送信号
参数1:进程号
>0:向指定的进程号发送信号
=0:向调用进程所在的进程组中每一个进程发送信号。
=-1:向任意的进程发送信号
<-1:向进程组ID为pid绝对值的所有进程发送信号。
参数2:发送的信号号
返回值:成功返回0,失败返回-1,并置位错误码。
#include <signal.h>
int raise(int sig);
功能:调用进程向自己发送一个信号
参数:信号号
返回值:成功返回0,失败返回非0.
原理:子进程向父进程发送一个信号(这个信号是自定义信号),父进程收到信号后,调用raise函数自杀。
#include <myhead.h>
void hander(int sss)
{
printf("逆子啊,我要死了\n");
raise(SIGKILL);//自杀
}
int main(int argc, const char *argv[])
{
pid_t pid;
pid = fork();
if(pid>0)
{
if(signal(SIGUSR1,hander)==SIG_ERR)//父进程绑定了自定义信号
{
perror("signal");
return -1;
}
}
else if(pid==0)
{
kill(getppid(),SIGUSR1);//子进程向父进程发送自定义信号
}
else
{
perror("fork");
return -1;
}
while(1);
return 0;
}

208

被折叠的 条评论
为什么被折叠?



