进程间通信

目录

思维导图:

学习内容:

1. 进程间通信的引入

1.1 概念

2. 无名管道 

3> 无名管道的API

4>实例

5>特点

6> 管道的读写特点(笔试面试)

3. 有名管道

3> 有名管道的API函数

create.c

snd.c 

recv.c 

4. 信号通信 

1> 信号的使用机制

2> 信号的处理方式

3> 信号的种类及功能:可以通过 kill -l查看所有信号

4> 对于信号的处理方式有三种

7>绑定信号函数(signal)

例如:

 5. 特殊的信号处理

1> SIGCHLD信号:

2> SIGALRM:定时器信号 

使用SIGALRM模拟斗地主出牌:

课外作业:

1> 使用有名管道实现,一个进程用于给另一个进程发消息,另一个进程收到消息后,展示到终端上,并且将消息保存到文件上一份

create.c

send.c

receive.c

2> 使用有名管道实现两个进程间相互通信

create.c

send.c

receive.c


思维导图:


学习内容:

1. 进程间通信的引入

1.1 概念

1> IPC: inter process communication

2> 线程间通信,可以使用全局变量来完成,原因是多个线程共享进程的资源

3> 由于进程间通信时,每个进程的用户空间是独立的,所以不能使用全局变量来完成进程间的通信

4> 可以使用文件来完成两个进程间通信,一个进程向文件中写数据,另一个进程从文件中读数据,但是,又因为进程的调度是时间片轮询,上下文切换,所以,不确定先执行哪一个进程,文件操作有些行不通,但是不完全行不通

5> 由于多个进程用户空间是独立的,但是共享内核空间

6> 进程间通信的种类

        内核提供的传统通信方式:

                1、无名管道

                2、有名管道

                3、信号

        system V提供了三种:

                4、消息队列

                5、共享内存

                6、有名信号量(信号灯集)

        用于跨主机传输的通信

                7、socket 套接字通信(网络编程重点说明)

2. 无名管道 

1> 无名管道顾名思义就是没有名字的管道文件,由于该文件没有名字,导致其他进程不能打开该文件,所以就不能完成非亲缘间的进程的通信

2> 无名管道适用于亲缘进程间的通信

3> 无名管道的API

       #include <unistd.h>

       int pipe(int pipefd[2]);
功能:创建一个管道,用于进程间通信,并通过参数返回该管道的两端,pipefd[0]是管道文件的读端,pipefd[1]是管道文件的写端
参数:整形数组,存放管道文件的文件描述符
返回值:成功返回0,失败返回-1并置位错误码

4>实例

#include<myhead.h>

int main(int argc, const char *argv[])
{
    //定义一个数组,存放管道文件的两端
    int pipefd[2];

    //定义进程号
    pid_t pid;

    //打开管道文件
    if(pipe(pipefd) == -1)
    {
        perror("pipe error");
        return -1;
    }

    //创建子进程
    pid = fork();
    if(pid < 0)
    {
        perror("fork error");
        return -1;
    }else if(pid == 0)
    {
        //子进程,向管道中写入数据
        //关闭子进程的读端
        close(pipefd[0]);

        while(1)
        {
            char buf[128] = "";
            fgets(buf, sizeof(buf), stdin);
            //将回车变成'\0'
            buf[strlen(buf) - 1] = '\0';

            //将该字符串写入到管道文件中
            write(pipefd[1], buf, strlen(buf));

            //判断字符串
            if(strcmp(buf, "quit") == 0)
            {
                break;
            }

        }

        //关闭写端
        close(pipefd[1]);
        //退出子进程
        exit(EXIT_SUCCESS);


    }else
    {
        //父进程
        //关闭写端
        close(pipefd[1]);

        while(1)
        {
            char buf[128] = "";

            //从管道文件中读取数据
            read(pipefd[0], buf, sizeof(buf));
            //对读取的数据判断
            if(strcmp(buf, "quit") == 0)
            {
                break;
            }

            //将数据输出
            printf("父进程读取了数据:%s\n", buf);

        }

        //关闭读段
        close(pipefd[0]);

        wait(NULL);
    }

    return 0;
}

5>特点

1、管道通信是半双工的通信方式 单工:任意时刻只能A向B发送消息,B不能向A发送消息 半双工:同一时刻,只能A向B发送消息或者B向A发消息 全双工:任意时刻,AB可以互相发送消息

2、无名管道只适用于亲缘进程间通信

3、无名管道也能完成自己跟自己的通信

4、无名管道文件的大小:64KB 

6> 管道的读写特点(笔试面试)

1、当管道读端存在时,写管道有多少写多少,直到写满64K为止

2、当管道读端不存在时,写管道写入数据时,会出现管道破裂,此时内核空间会向用户空间发送一个SIGPIPE信号

3、当写端存在时,读管道有多少读多少,没有数据会在read处阻塞

4、当写端不存在时,读管道有多少读多少,没有数据也不会在read处阻塞

3. 有名管道

1> 顾名思义就是有名字的管道文件,会在文件系统中创建一个有名字的管道文件

2> 可以用于亲缘进程间的通信,也可以用于非亲缘进程间的通信

3> 有名管道的API函数

 #include <sys/types.h>

#include <sys/stat.h>

int mkfifo(const char *pathname, mode_t mode);

功能:在文件系统中创建一个有名管道,但是并没有打开该文件

参数1:有名管道的名称

参数2:管道文件的权限

返回值:成功返回0,失败返回-1并置位错误码

create.c

#include<myhead.h>

int main(int argc, const char *argv[])
{
    //创建一个有名管道文件
    if(mkfifo("./linux", 0664)==-1)
    {
        perror("mkfifo error");
        return -1;
    }

    getchar();

    system("rm linux");
    
    return 0;
}

snd.c 

#include<myhead.h>

int main(int argc, const char *argv[])
{
    //以写的形式打开管道文件
    int wfd = open("./linux", O_WRONLY);
    if(wfd == -1)
    {
        perror("open error");
        return -1;
    }
    printf("管道文件已经打开\n");

    //发送数据
    char wbuf[128] = "";
    while(1)
    {
        printf("请输入>>>>");
        fgets(wbuf, sizeof(wbuf), stdin);
        wbuf[strlen(wbuf)-1] = 0;

        //将数据发送给到管道中
        write(wfd, wbuf, strlen(wbuf));

        //判断数据
        if(strcmp(wbuf, "quit") == 0)
        {
            break;
        }
    }

    //关闭文件描述符
    close(wfd);

    
    return 0;
}

recv.c 

#include<myhead.h>

int main(int argc, const char *argv[])
{
    //以读的形式打开文件
    int rfd = open("./linux", O_RDONLY);
    if(rfd == -1)
    {
        perror("open error");
        return -1;
    }
    printf("管道文件读端打开\n");

    //定义接受容器
    char rbuf[128] = "";
    while(1)
    {
        bzero(rbuf, sizeof(rbuf));
        //读取数据
        read(rfd, rbuf, sizeof(rbuf));
        if(strcmp(rbuf, "quit") == 0)
        {
            break;
        }
        printf("收到消息为:%s\n", rbuf);
    }

    //关闭文件描述符
    close(rfd);
    
    return 0;
}

4. 信号通信 

1> 信号的使用机制

        在linux应用层的信号,其实是模拟底层的中断,当一个进程正在运行时,如果有信号产生,则会执行该信号对应的操作

2> 信号的处理方式

        1、捕获:当捕获一个信号后,可以进行处理信号处理函数

        2、忽略:当信号发射后,不做任何处理

        3、默认:一般都是杀死进程

3> 信号的种类及功能:可以通过 kill -l查看所有信号

 1) SIGHUP     2) SIGINT     3) SIGQUIT     4) SIGILL     5) SIGTRAP
 6) SIGABRT     7) SIGBUS     8) SIGFPE     9) SIGKILL    10) SIGUSR1
11) SIGSEGV    12) SIGUSR2    13) SIGPIPE    14) SIGALRM    15) SIGTERM
16) SIGSTKFLT    17) SIGCHLD    18) SIGCONT    19) SIGSTOP    20) SIGTSTP
21) SIGTTIN    22) SIGTTOU    23) SIGURG    24) SIGXCPU    25) SIGXFSZ
26) SIGVTALRM    27) SIGPROF    28) SIGWINCH    29) SIGIO    30) SIGPWR
31) SIGSYS    34) SIGRTMIN    35) SIGRTMIN+1    36) SIGRTMIN+2    37) SIGRTMIN+3
38) SIGRTMIN+4    39) SIGRTMIN+5    40) SIGRTMIN+6    41) SIGRTMIN+7    42) SIGRTMIN+8
43) SIGRTMIN+9    44) SIGRTMIN+10    45) SIGRTMIN+11    46) SIGRTMIN+12    47) SIGRTMIN+13
48) SIGRTMIN+14    49) SIGRTMIN+15    50) SIGRTMAX-14    51) SIGRTMAX-13    52) SIGRTMAX-12
53) SIGRTMAX-11    54) SIGRTMAX-10    55) SIGRTMAX-9    56) SIGRTMAX-8    57) SIGRTMAX-7
58) SIGRTMAX-6    59) SIGRTMAX-5    60) SIGRTMAX-4    61) SIGRTMAX-3    62) SIGRTMAX-2
63) SIGRTMAX-1    64) SIGRTMAX    

4> 对于信号的处理方式有三种

        默认、捕获、忽略

        有两个信号既不能被捕获,也不能被忽略:SIGKILL、SIGSTOP

5> 上面的信号触发条件以及默认处理方式可以通过指令 man 7 signal进行查看

6> 信号的发送者可以是内核、其他进程、用户自己

7>绑定信号函数(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

例如:

#include<myhead.h>
//自定义处理信号的函数
void handler(int signo)
{
    //判断传过来的信号是哪个
    if(signo == SIGINT)
    {
        printf("当前进程收到了ctrl + c,但是就是关不掉\n");
    }

    //判断是否为SIGKILL到位
    if(signo == SIGKILL)
    {
        printf("捕获了SIGKILL\n");
    }
    
}


/**************************主程序*******************/
int main(int argc, const char *argv[])
{

    //将2号信号与对应的信号处理函数进行绑定
    /*尝试忽略2号信号
    if(signal(2, SIG_IGN) == SIG_ERR)
    {
        perror("signal error");
        return -1;
    }
    */

    //尝试捕获2号信号
    if(signal(2, handler) == SIG_ERR)
    {
        perror("signal error");
        return -1;
    }

    /*尝试忽略SIGKILL信号:报错,参数不合法
        if(signal(SIGKILL, handler) == SIG_ERR)
    {
        perror("signal error");
        return -1;
    }*/

    /*尝试捕获SIGKILL信号:报错,参数不合法
        if(signal(SIGKILL, handler) == SIG_ERR)
    {
        perror("signal error");
        return -1;
    }
    */

    /*尝试默认处理:报错,参数不合法
    if(signal(SIGKILL, SIG_DFL) == SIG_ERR)
    {
        perror("signal error");
        return -1;
    }*/


    while(1)
    {
        printf("啦啦啦啦,我是卖报的小行家\n");
        sleep(1);
    }
    

    return 0;
}

 5. 特殊的信号处理

1> SIGCHLD信号:

        以非阻塞的形式回收僵尸进程

#include<myhead.h>

//自定义信号处理函数
void handler(int signo)
{
    if(signo == SIGCHLD)
    {
        while(1)
        {
            //判断是否将僵尸进程全部回收了
            if(waitpid(-1, NULL, WNOHANG) == 0)
            {
                break;
            }
        }
    }
}

/******************主程序**********************/
int main(int argc, const char *argv[])
{
    //将SIGCHLD信号与信号处理函数绑定到一起
    if(signal(SIGCHLD, handler) == SIG_ERR)
    {
        perror("signal error");
        return -1;
    }


    for(int i=0; i<10; i++)
    {
        if(fork() == 0)
        {
            exit(EXIT_SUCCESS);
        }
    
    }

    while(1);

    /*
    for(;;)
    {
        printf("12345\n");
        sleep(1);
    }*/

    return 0;
}

2> SIGALRM:定时器信号 

        程序允许启动一个定时器,当所定的时间到位后,会发送一个SIGALRM信号,我们可以将该信号绑定到对应的信号处理函数中,从而导致,给定时间后,处理自定义函数。该信号需要使用一个函数来发送超时信号:alarm闹钟函数

       #include <unistd.h>

       unsigned alarm(unsigned seconds);
    功能:给进程设置一个定时器,以秒为单位,当定时器到位后,后向该进程发送一个SIGALRM的信号
    参数:秒数,如果参数设置成0,表示删除定时器
    返回值:>0:表示返回的上一个定时器剩余的秒数,并且重置上一个定时器
            0:表示之前没有设置定时器

#include<myhead.h>

//定义信号处理函数
void handler(int signo)
{
    if(signo == SIGALRM)
    {
        printf("阎王叫你三更死,怎可留人到五更\n");
        sleep(2);
        exit(EXIT_SUCCESS);
    }
}


/********************主程序******************/
int main(int argc, const char *argv[])
{

    //将SIGALRM信号与信号处理函数绑定
    if(signal(SIGALRM, handler) == SIG_ERR)
    {
        perror("signal error");
        return -1;
    }


    printf("%d\n", alarm(10));          //0

    sleep(5);


    printf("%d\n", alarm(10));          //5


    int count = 0;
    while(1)
    {
        printf("能活一秒是一秒\n");
        sleep(1);
        /*
        count++;
        if(count == 3)
        {
            alarm(0);           //删除定时器
        }
        */
    }

    return 0;
}

使用SIGALRM模拟斗地主出牌:

#include<myhead.h>

//定义信号处理函数
void handler(int signo)
{
    if(signo == SIGALRM)
    {
        printf("系统已经自动帮您出了一张牌\n");
        alarm(5);
    }
}


/*******************主程序**********************/
int main(int argc, const char *argv[])
{

    //将信号与信号处理函数绑定
    if(signal(SIGALRM, handler) == SIG_ERR)
    {
        perror("signal error");
        return -1;
    }


    char ch;        //要出的牌

    while(1)
    {
        //启动一个定时器
        alarm(5);

        printf("请出牌>>");
        scanf("%c", &ch);
        getchar();

        printf("您出的牌为:%c\n", ch);
    }

    return 0;
}


课外作业:

1> 使用有名管道实现,一个进程用于给另一个进程发消息,另一个进程收到消息后,展示到终端上,并且将消息保存到文件上一份

解析:

create.c

#include<myhead.h>
int main(int argc, char const *argv[])
{
    if(mkfifo("./linux",0664) == -1)
    {
        perror("mkfifo error");
        return -1;
    }
    getchar();
    system("rm linux");
    return 0;
}

send.c

#include<myhead.h>
int main(int argc, char const *argv[])
{
    int wfd = open("linux", O_WRONLY);
    if (wfd == -1)
    {
        perror("open error");
        return -1;
    }
    printf("管道文件打开\n");
    char wbuf[128] = "";
    while (1)
    {
        printf("请输入:");
        // 从标准输入读取一行数据到wbuf
        fgets(wbuf, sizeof(wbuf), stdin);
        // 移除输入字符串末尾的换行符
        wbuf[strlen(wbuf) - 1] = '\0';
        // 将输入的数据写入管道文件
        write(wfd, wbuf, strlen(wbuf));
        // 若用户输入"quit",退出循环
        if (strcmp(wbuf, "quit") == 0)
        {
            break;
        }
    }
    close(wfd);
    return 0;
}

receive.c

#include<myhead.h>
int main(int argc, char const *argv[])
{
    int rfd = 0;
    // 打开只读文件"linux"
    if ((rfd = open("./linux", O_RDONLY)) == -1)
    {
        perror("open error");
        return -1;
    }
    int fd = -1;
    // 打开或创建"11.txt"文件
    if ((fd = open("./11.txt", O_WRONLY | O_CREAT | O_TRUNC, 0664)) == -1)
    {
        perror("open error");
        return -1;
    }
    printf("打开\n");
    char rbuf[128] = "";
    while (1)
    {
        int res = read(rfd, rbuf, sizeof(rbuf));
        // 若读取字节数为0,表示到达文件末尾,退出循环
        if (res == 0)
        {
            break;
        }
        // 将读取的内容写入"11.txt"文件
        write(fd, rbuf, res);
        // 若读取的内容为"quit",则退出循环
        if (strcmp(rbuf, "quit") == 0)
        {
            break;
        }
        printf("读取的信息为:%s\n", rbuf);
    }
    close(fd);
    close(rfd);
    return 0;
}

2> 使用有名管道实现两个进程间相互通信

解析:

create.c

int main(int argc, const char *argv[])
{
    // 第一个管道文件
    if(mkfifo("./mkfifo1", 0664) == -1)
    {
        perror("mkfifo error");
        return -1;
    }

    // 第二个管道文件
    if(mkfifo("./mkfifo2", 0664) == -1)
    {
        perror("mkfifo error");
        return -1;
    }

    getchar();

    // 删除第一个管道文件
    system("rm mkfifo1");
    // 删除第二个管道文件
    system("rm mkfifo2");

    return 0;
}

send.c

#include<myhead.h>
// 任务1线程的执行函数,用于从标准输入读取命令,并通过命名管道发送这些命令
void *task1(void *arg)
{
    // 打开命名管道文件,用于写入数据
    int sfd = open("./mkfifo2",O_WRONLY);
    if(sfd == -1)
    {
        perror("open  error");
        return NULL;
    }
    // 定义发送缓冲区
    char sbuf[128]="";
    // 主循环
    while (1)
    {
        printf("A请输入:");
        fgets(sbuf,sizeof(sbuf),stdin);
        // 去除命令行末尾的换行符
        sbuf[strlen(sbuf)-1]='\0';
        // 将命令写入命名管道
        write(sfd,sbuf,strlen(sbuf));
        // 检查是否输入了"quit"命令,是则退出循环
        if(strcmp(sbuf,"quit") ==0)
        {
            break;
        }
    }

    pthread_exit(NULL);
}

int main(int argc, char const *argv[])
{
    pthread_t pid;
    // 创建线程,用于执行任务1
    if(pthread_create(&pid,NULL,task1,NULL) != 0)
    {
        printf("线程创建失败\n");
        return -1;
    }
    int sd;
    // 打开命名管道文件,只读模式
    if((sd = open("./mkfifo1",O_RDONLY)) == -1)
    {
        perror("open error");
        return -1;
    }
    char buf[128]="";
    while (1)
    {
        // 从文件描述符sd读取数据到缓冲区buf
        read(sd,buf,sizeof(buf));
        // 比较读取的数据是否为"quit"
        if(strcmp(buf,"quit") == 0)
        {
            break;
        }
        printf("读取的信息为:%s\n", buf);
    }
    pthread_join(pid, NULL);
    return 0;
}

receive.c

#include<myhead.h>
void *task2(void *arg)
{
    // 打开命名管道文件,只读方式打开
    int rfd = open("./mkfifo2",O_RDONLY);
    if(rfd == -1)
    {
        perror("open  error");
        return NULL;
    }
    char sbuf[128]="";
     while (1)
    {
        // 从命名管道中读取数据到接收缓冲区
        read(rfd, sbuf, sizeof(sbuf));

        // 检查接收到的数据是否为退出指令
        if (strcmp(sbuf, "quit") == 0)
        {
            // 接收到退出指令,退出循环
            break;
        }
    pthread_exit(NULL);
}

int main(int argc, char const *argv[])
{
    pthread_t tid;
    // 创建线程,用于执行task2任务
    if(pthread_create(&tid,NULL,task2,NULL) != 0)
    {
        printf("线程创建失败\n");
        return -1;
    }
    int rd;
    // 打开文件,以写方式打开
    if((rd = open("./mkfifo1",O_WRONLY)) == -1)
    {
        perror("open error");
        return -1;
    }
    char buf[128]="";
    while (1)
    {
        printf("B请输入:");
        // 从标准输入读取一行到buf
        fgets(buf,sizeof(buf),stdin);
        // 去除输入的最后一行的换行符
        buf[strlen(buf)-1]='\0';
        // 将输入写入到文件描述符rd中
        write(rd,buf,strlen(buf));
        // 如果输入为"quit",则退出循环
        if(strcmp(buf,"quit") == 0)
        {
            break;
        }
    }
    pthread_join(tid, NULL);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值