day32 进程间通信

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;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值