IO与进程篇 LESSON7 传统进程通信

目录

进程通信介绍

进程通信方式

传统进程通信

system v IPC对象

注:本文只解释传统进程通信,system v IPC对象在下篇文章

无名管道

特点

注意事项

无名管道函数接口

代码实例

父进程循环从终端输入字符串,子进程循环打印数据

代码分析:

代码结果

 有名管道

特点

注意事项

简单的有名管道的使用步骤(重点)

函数接口

错误处理方式:

代码实例

通过有名管道实现文件复制功能

读文件进程

写文件进程

信号

信号概念

常见的信号种类

信号的响应方式

信号的函数接口

signal(重点掌握)

代码实例

kill和raise

alarm:注意分清alarm和sleep区别,alarm只是到达时间后内核发送信号,alarm不是阻塞函数,不会和sleep一样阻塞等待时间,下面是结合pause函数的一段代码示例

signal(重点掌握)


进程通信介绍

进程通信是指两个或多个进程之间进行数据交换、共享资源或者互相协调的过程。进程通信的实现需要一定的机制和协议来保证通信的正确性、可靠性和效率。

进程通信方式

传统进程通信

  1. 无名管道:是一种半双工的通信方式,只能在具有父子或兄弟关系的进程之间使用。它使用pipe()函数创建管道,通过读写文件描述符来实现进程间通信。

  2. 有名管道:也叫FIFO(First In First Out),是一种可以在无亲缘关系的进程之间进行通信的机制。它使用mkfifo()函数创建管道,通过读写文件描述符来实现进程间通信。

  3. 信号:是一种异步的通信方式,用于通知目标进程发生了某种事件。它使用kill()函数发送信号,通过注册信号处理函数来处理信号。

system v IPC对象

  1. 共享内存:是一种允许不同进程访问同一块物理内存的方式。它可以通过映射一个共享内存区域到进程的虚拟地址空间来实现。不过由于多个进程可以同时访问同一块内存,因此需要加锁来保证数据的一致性。

  2. 消息队列:是一种实现进程间异步通信的方式。它使用msgget()函数创建消息队列,通过向消息队列发送消息实现进程间通信。

  3. 信号量:是一种用于实现进程间同步和互斥的机制。它使用semget()函数创建信号量集,通过P(wait)和V(signal)操作来控制对共享资源的访问。

注:本文只解释传统进程通信,system v IPC对象在下篇文章

无名管道

特点

  1. 只能用于具有亲缘关系的进程之间的通信(父子进程)
  2. 半双工的通信模式,具有固定的读端和写端(不能同时进行读写操作
  3. 管道是基于文件描述符的通信方式。当一个管道建立时,它会创建两个文件描述符fd[0]和fd[1]。其中fd[0]为读端,而fd[1]为写端

注意事项

  1. 当管道中无数据的时候,读操作阻塞
  2. 当管道中无数据,关闭写端,读操作不会阻塞,会立即返回
  3. 管道也是有大小的(大小为64K),当管道满的是时候,写操作会阻塞,直到管道空出4k空间的时候,才能继续fd[1]
  4. 将管道得读端关闭,执行写操作,会导致管道的破裂。向管道中写入数据的进程会收到内核传来的SIGPIPE信号

无名管道函数接口

int pipe(int fd[2])
功能:创建无名管道
参数:文件描述符 fd[0]:读端  fd[1]:写端
返回值:成功 0
      失败 -1
  1. 代码实例

  2. 父进程循环从终端输入字符串,子进程循环打印数据

#include 
#include 
#include 
#include 
#include 

#define N 128

int main(int argc, char const *argv[])
{
    char buf[N] = {};
    int fd[2] = {};
    pid_t pid;

    if (pipe(fd) < 0)
    {
        perror("pipe err.");
        return 0;
    }

    pid = fork();
    if (pid < 0)
    {
        perror("fork err.");
        return -1;
    }
    else if (pid == 0)
    {
        while (1)
        {
            ssize_t len = read(fd[0], buf, N);
            if (len < 0)
            {
                perror("read error");
                return -1;
            }
            if (len == 0)
            { //读到文件结尾
                printf("EOF\n");
                return 0;
            }
            write(STDOUT_FILENO, buf, len); //写入标准输出
        }
        close(fd[0]); //关闭读取端
    }
    else
    {
        while (1)
        {
            if (fgets(buf, N, stdin) == NULL)
            {
                perror("fgets error");
                return -1;
            }
            ssize_t len = write(fd[1], buf, strlen(buf) + 1);
            if (len < 0)
            {
                perror("write error");
                return -1;
            }
        }
        close(fd[1]); //关闭写入端
        wait(NULL);   //等待子进程结束
    }
    return 0;
}

代码分析:

  1. 上述代码在主函数中先使用pipe函数创建了一根管道fd,用来进行进程间的通信。
  2. 使用fork函数创建了具有亲缘关系的父子继承
  3. 子进程先使用read函数,从读端fd[0]读取数据存放到buf数组中,并且利用了read数组的返回值为读取到字符个数特性进行判错处理。
  4. 子进程继续使用write函数将存在buf里的len(read的返回值),写到标准输出端口。当循环结束时,关闭管道的读端。
  5. 父进程使用fget函数从标准输入端获取我们输入的字符串,写入buf数组中,并利用fgets返回值为NULL的特性进行判错处理。
  6. 父进程继续使用write函数将,将buf里面的内容写到管道fd的写端fd[1]中。最后循环结束的时候关闭写端,并使用wait回收子进程资源

代码结果

IO与进程篇 LESSON7 传统进程通信_第1张图片

 有名管道

特点

  1. 有名管道可以使两个互不相关的进程进行通信
  2. 有名管道可以 通过路径名 来指出,并且在 文件系统中可见 ,但内容存 放在内存 中,过会通过代码就可体现出
  3. 在linux系统下,进程通过 文件IO来操作有名管道
  4. 有名管道遵循先入先出原则(队列特性),所以不支持文件io中的lseek操作

注意事项

因为需要使用open函数来打开管道文件就牵扯到了下面特性:

  1. 只写的方式打开,写阻塞,直到另一进程将读打开
  2. 只读的方式打开,读阻塞,直到另一进程将写打开
  3. 可读可写方式打开,管道中无数据,读阻塞

简单的有名管道的使用步骤(重点)

  1. 使用mkfifo创建有名管道
  2. 打开管道文件
  3. 对管道文件进行读写操作

函数接口

int mkfifo(const char *filename,mode_t mode);
功能:创健有名管道
参数:filename:有名管道文件名
       mode:权限
返回值:成功:0
       失败:-1,并设置errno号

错误处理方式:

创建管道牵扯到若是管道文件已经存在,也会返回-1,此时普通的不等于0就是返回的判错方式无法达到我们想要的结果

在错误的同时会设置errno号,当errno==EEXIST的时候,说明文件存在,我们可以利用这个特性进行处理

代码实例

通过有名管道实现文件复制功能

读文件进程

#include 
#include 
#include 
#include 
#include 
#include 
#define N 128
int main(int argc, char const *argv[])
{
    char buf[N] = "";
    //1.创建有名管道
    if (mkfifo("./fifo", 0666) < 0)
    {
        if (errno == EEXIST)
        {
            perror("fifo is cretae");
        }
        else
        {
            perror("mkfifo err.");
            return -1;
        }
    }
    //2.打开要读得文件
    int fd;
    fd = open("./1.txt", O_RDONLY);
    if (fd < 0)
    {
        perror("open err.");
        return -1;
    }
    //3.打开管道
    int fifo_fd;
    fifo_fd = open("./fifo", O_WRONLY);
    if (fifo_fd < 0)
    {
        perror("open fifo err.");
        return -1;
    }
    //4.循环读文件写到管道
    while (1)
    {
        int num1 = read(fd, buf, N);
        write(fifo_fd, buf, num1);
        if (num1 == 0)
        {
            puts("hello");
            break;
        }
    }
    close(fd);
    close(fifo_fd);
    return 0;
}

写文件进程

#include 
#include 
#include 
#include 
#include 
#include 
#define N 128
int main(int argc, char const *argv[])
{
    char buf[N] = "";
    //1.创建有名管道
    if (mkfifo("./fifo", 0666) < 0)
    {
        if (errno == EEXIST)
        {
            perror("fifo is cretae");
        }
        else
        {
            perror("mkfifo err.");
            return -1;
        }
    }
    //2.打开要写入得文件
    int fd;
    fd = open("./2.txt", O_WRONLY | O_CREAT | O_TRUNC ,0666);
    if (fd < 0)
    {
        if (errno == EEXIST)
        {
            perror("2.txt create");
        }
        else
        {
            perror("open err.");
            return -1;
        }
    }
    //3.打开管道
    int fifo_fd;
    fifo_fd = open("./fifo", O_RDONLY);
    if (fifo_fd < 0)
    {
        perror("open fifo err.");
        return -1;
    }
    //4.循环读文件写到管道
    while (1)
    {
        int num1 = read(fifo_fd, buf, N);
        write(fd, buf, num1);
        if (num1 == 0)
        {
            break;
        }
    }
    close(fd);
    close(fifo_fd);
    return 0;
}

 

 上面的fifo就是我们创建的管道文件,在属性中是p开头,且大小为0;有名管道可以通过路径名来指出,并且在文件系统中可见,但内容存放在内存中,过会通过代码就可体现出(详见特点第二条)
有名管道和无名管道区别统计

无名管道

有名管道

使用场景

具有亲缘关系的进程

不相干的进程

特点

半双工通信

固定的读端和写端

看做特殊的文件,通过文件IO操作

先进先出,不支持lseek

在文件系统中可见管道文件,数据存放在内存中

文件IO操作

函数

pipe

直接read、write

mkfifo

先open、在进行read、write

读写特性

当管道中无数据,都阻塞

当管道中写满数据,写阻塞

读端关闭,向管道中写数据管道破裂

只写方式,写阻塞,直到另一个进程将读打开

只读方式,读阻塞,直到另一个进程把写打开

可读可写,若管道中无数据,读阻塞

信号

信号概念

  1. 信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式
  2. 信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户空间进程发生了哪些系统事件。
  3. 如果该进程当前 并未处于执行态 ,则该信号就由内核保存起来,直到该进程 恢复执行再传递 给它;如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程

常见的信号种类

数值 名称 含义
2
SIGINT
结束进程,对应快捷方式ctrl+c
3
SIGQUIT
退出信号,对应快捷方式ctrl+\
9
SIGKILL
结束进程,不能被忽略不能被捕捉
15
SIGTERM
结束终端进程,kill 使用时不加数字默认是此信号
17
SIGCHLD
子进程状态改变时给父进程发的信号
19
SIGSTOP
结束进程,不能被忽略不能被捕捉
20
SIGTSTP
暂停信号,对应快捷方式ctrl+z
21
SIGALRM
闹钟信号,alarm函数设置定时,当到设定的时间时,内核会向进程发送此信号结束进程

信号的响应方式

上面表格中出现捕捉,忽略缺省等字样,他们是信号的响应方式

  1. 忽略信号:对信号不做任何处理,但是有两个信号不能忽略:即 SIGKILL及SIGSTOP
  2. 捕捉信号:定义信号处理函数,当信号发生时,执行相应的处理函数。
  3. 执行缺省操作:Linux对每种信号都规定了默认操作

信号的函数接口

int kill(pid_t pid, int sig);
功能:信号发送
参数:pid:指定进程
   sig:要发送的信号
返回值:成功 0     
       失败 -1
int raise(int sig);
功能:进程向自己发送信号
参数:sig:信号
返回值:成功 0   
       失败 -1
int pause(void);
功能:用于将调用进程挂起,直到收到信号为止。
unsigned int alarm(unsigned int seconds)
功能:在进程中设置一个定时器
参数:seconds:定时时间,单位为秒
返回值:如果调用此alarm()前,进程中已经设置了闹钟时间,则
返回上一个闹钟时间的剩余时间,否则返回0。
注意:一个进程只能有一个闹钟时间。如果在调用alarm时
已设置过闹钟时间,则之前的闹钟时间被新值所代替

signal(重点掌握)

#include 
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
功能:信号处理函数
参数:signum:要处理的信号
      handler:信号处理方式
            SIG_IGN:忽略信号
            SIG_DFL:执行默认操作
           handler:捕捉信号  void handler(int sig){} //函数名可以自定义
返回值:成功:设置之前的信号处理方式
      失败:-1

代码实例

kill和raise

#include 
#include 
#include 
#include 
int main(int argc, char const *argv[])
{
    printf("测试语句1\n");
    kill(getpid(),SIGINT);
    //raise(SIGINT);
    printf("测试语句2\n");    
    pause();
    return 0;
}

IO与进程篇 LESSON7 传统进程通信_第2张图片

alarm:注意分清alarm和sleep区别,alarm只是到达时间后内核发送信号,alarm不是阻塞函数,不会和sleep一样阻塞等待时间,下面是结合pause函数的一段代码示例

#include 
#include 
#include 
#include 
int main(int argc, char const *argv[])
{
    alarm(5);
    printf("测试语句1\n");
    printf("测试语句2\n");    
    pause();//将进程挂起,直到收到信号为止。
    return 0;
}

IO与进程篇 LESSON7 传统进程通信_第3张图片

signal(重点掌握)

#include 
#include 
#include 
#include 
#include 
void handly(int sig){
    if(sig==SIGINT){
        puts("1......SIGINT");
    }
    if(sig == SIGQUIT){
        puts("2......SIGQUIT");
    }
}
int main(int argc, char const *argv[])
{
    signal(SIGINT,handly);//ctrl+c结束进程
    signal(SIGQUIT,handly);//ctrl+\推出信号
    pause();//捕捉到信号结束
    return 0;
}

IO与进程篇 LESSON7 传统进程通信_第4张图片

关于signal函数有道题目,详见http://t.csdn.cn/kNjJC

你可能感兴趣的:(信号,网络编程,c语言,笔记)