【ONE·Linux || 进程间通信(一)】

news2024/11/24 22:36:04

总言

  进程间通信:简述进程间通信,介绍通信方式之一,管道通信(匿名、名命)。

文章目录

  • 总言
  • 1、进程间通信简述
  • 2、管道
    • 2.1、简介
    • 2.2、匿名管道
      • 2.2.1、匿名管道的原理
      • 2.2.2、编码理解:用fork来共享管道
        • 2.2.2.1、准备工作一:创建一个管道pipe
        • 2.2.2.2、准备工作二:创建子进程,对父子进程构建单向通信的信道
        • 2.2.2.3、正式工作:父子进程间通信
        • 2.2.2.4、结束工作:收尾处理(附整体代码)
      • 2.2.3、总结说明(匿名管道特点、读写原则)
      • 2.2.4、扩展应用:进程池的模拟实现
        • 2.2.4.1、processpool.c
        • 2.2.4.2、Task.hpp
    • 2.3、名命管道
      • 2.3.1、是什么
      • 2.3.2、相关使用演示
        • 2.3.2.1、comm.hpp
        • 2.3.2.2、log.hpp
        • 2.3.2.3、server.cxx
        • 2.3.2.4、client.cxx
        • 2.3.2.5、server端加入子进程(多个读端演示)

  
  
  

1、进程间通信简述

  1)、为什么要进行进程间通信?

  以下为一些举例:
  数据传输:一个进程需要将它的数据发送给另一个进程。
  资源共享:多个进程之间共享同样的资源。
  通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
  进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
  
  
  
  2)、要达成上述进程间通信,需要面临哪些问题,为什么?
  说明:进程具有独立性,要实现进程间通信,则需要让通信双方进程能够看到同一份"资源",此处强调"共享"这一特性。
  
  
  
  3)、进程间通信发展简述
在这里插入图片描述

  
  
  
  

2、管道

2.1、简介

  1)、什么是管道
  管道是Unix中最古老的进程间通信的形式,我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”。
  
  特点:单向通信、传送的都是资源(数据是计算机里最重要的资源)
  
  说明:由于进程具有独立性,若让进程来提供管道,其它进程是看不到该管道存在的。因此,计算机里管道的实现,由操作系统提供,由进程来使用。
在这里插入图片描述

  
  
  

2.2、匿名管道

2.2.1、匿名管道的原理

  1)、问题引入:当进程打开一个文件后,创建一个子进程会发生什么?

在这里插入图片描述

  回答:对进程相关数据结构(*filefiles_structfile* fd_array等),子进程会进行写时拷贝;对被打开文件相关的内核数据结构(文件信息所生成的文件结构file),父子进程指向相同。由此诞生了共享文件,即管道。
  
  
  
  
  2)、如何实现一个管道?
  实际上,管道的底层原理就是通过文件实现的。相关步骤说明:
  1、分别以读写方式打开同一个文件
  2、fork()创建子进程(此时父子进程虽然彼此独立,但它们共享同一个文件)
  3、双方进程各自关闭自己不需要的文件描述符(假设父进程写入、子进程读取,则父进程关闭读端,子进程关闭写端。)
  
  PS:进程间通信属于内存级别的通信,大多以临时数据为主,不需要写入磁盘,虽然存在,但一般情况下没必要,且影响传送效率。
  
  

  
  
  

2.2.2、编码理解:用fork来共享管道

2.2.2.1、准备工作一:创建一个管道pipe

  1)、相关函数介绍
  这里我们使用pipe函数,其作用是创建一个匿名管道。头文件为<unistd.h>。(man 2 pipe查看具体信息。)

NAME
       pipe, pipe2 - create pipe

SYNOPSIS
       #include <unistd.h>

       int pipe(int pipefd[2]);

  
  说明:该函数用于获取两个输出型参数(这里返回文件描述符数组)。pipefd类似于基础IO中学习的文件描述标识符,这里pipefd[0]表示管道的读端,pipefd[1]表示管道的写端。注意参数的返回类型是int

 pipe()  
 		creates  a  pipe, a unidirectional data channel that can be used for interprocess communica‐tion.  
		
		The array pipefd is used to return two file descriptors referring to the ends  of  the  pipe.
		pipefd[0]  refers to the read end of the pipe.  
		pipefd[1] refers to the write end of the pipe.  

		Datawritten to the write end of the pipe is buffered by the kernel until it is read from the read end ofthe pipe.  
		For further details, see pipe(7).

  
  返回值:int类型,若成功,则返回0,若失败则返回-1

RETURN VALUE
       On success, zero is returned.  
       On error, -1 is returned, and errno is set appropriately.

  
  
  2)、相关使用如下

    //step1:创建一个管道
    int pipefd[2] = {0};//文件描述符数组,用于接受pipe读端、写端
    int ret = pipe(pipefd);
    assert(ret != -1);//用于检测是否成功创建管道
    (void)ret;

  说明:
  1、pipe(pipefd):这里我们并未给出管道名称,所以说pipe创建的是一个匿名管道。
  2、(void)retassert只在debug模式下有效,为了避免release模式下,ret变量因无效(未使用)而报警,此处设置(void)ret以证其使用性。
  
  
  
  3)、验证:pipefd表示的含义
在这里插入图片描述

  

    //验证:pipefd[2]具体含义
    cout << "pipefd[0]" << pipefd[0] << endl;
    cout << "pipefd[1]" << pipefd[1] << endl;
mypipe.out:mypipe.cc
	g++ -o $@ $^ -std=c++11 -DDEBUG //debug模式

  
  
  
  
  

2.2.2.2、准备工作二:创建子进程,对父子进程构建单向通信的信道

  说明:上述步骤,我们只是创建出管道,未曾实际使用它。此处我们以fork的形式创建父子进程,让其使用管道进行通信。根据之前描述的如何实现一个管道,相关示意图如下:
在这里插入图片描述

  
  相关代码如下:

    //step2:创建子进程,对父子进程构建单向通信的信道
    pid_t id = fork();
    assert(id != -1);//fork失败
    if(id = 1)//子进程:此处设子进程读取
    {
        close(pipefd[1]);//对子进程,保留读端,关闭写端
        //相关读取操作
		//……
        exit(0);
    }
    //父进程:此处设父进程写入
    close(pipefd[0]);//对父进程,保留写端,关闭读端
    //相关写入操作
    //……

  
  man 2 fork

SYNOPSIS
       #include <unistd.h>

       pid_t fork(void);

RETURN VALUE
       On  success, the PID of the child process is returned in the parent, and 0 is returned in the child.
       On failure, -1 is returned in the parent, no child process is created, and errno  is  set  appropri‐
       ately.

  
  

2.2.2.3、正式工作:父子进程间通信

  说明:在上述准备工作中,所创建的匿名管道能够让父子进程单向通信,只用在读端、写端分别编写相关代码操作即可。以下为一个演示案例(该部分内容不固定,仅做演示理解):

  对子进程,读取管道中的数据:使用read函数读取管道文件,并将读取到的数据存储在re_buffer中。

    if(0 == id)
    {
        close(pipefd[1]);//子进程保留读端

        //……相关读取操作
        char re_buffer[1024*8]={0};//子进程缓冲区:用于接收读端读取到的数据
        while(true)
        {
            
            //从管道中读取数据存储入缓冲区
            ssize_t re = read(pipefd[0], re_buffer, sizeof(re_buffer)-1);
            if(re > 0)
            {
                re_buffer[re]= 0;
                cout << "【childpid-" << getpid() << ", get a message 】:" << re_buffer << endl;
            }
            else if(re == 0)//end of file,读取到0,文件结尾。
            {
                cout << "child: end of file, quit." << endl;
                break;//退出循环
            }
        }

        exit(0);//终止子进程
    }

  
  man 2 read
在这里插入图片描述
  
  
  对父进程,写入数据:这里设置写10次结束。

    //父进程
    close(pipefd[0]);//父进程保留写端

    //……相关写入操作
    char wr_buffer[1024*8]={0};//写端缓冲区,用于存储发送的数据
    string message = "父进程写入数据";//提示文字1
    int count=0;//提示文字2,用于记录写入次数
    while(true)
    {
        //向wr_buffer中写入相关信息
        snprintf(wr_buffer,sizeof(wr_buffer),"fatherid-%d, %s, 第%d写入.",getpid(),message.c_str(),1+count++);
        //将写端缓冲区数据写入管道文件中
        write(pipefd[1],wr_buffer,strlen(wr_buffer));
        sleep(1);//便于观察输出结果而设
        //结束写端
        if(10 == count)
        {
            cout<<"father quit!\n"<<endl;
            break;
        }
    }

  
  
  

2.2.2.4、结束工作:收尾处理(附整体代码)

  结束后,需要将通信管道关闭。

    //step3:执行完毕
    close(pipefd[1]);
    pid_t ret2 = waitpid(id, nullptr, 0);
    printf("wait:%d, childpid:%d, count:%d\n",ret2,id,count);
    assert(ret2 > 0);
    (void)ret2;

  演示结果:
在这里插入图片描述

  
  
  
  整体代码如下:

#include<iostream>
#include<unistd.h>
#include<assert.h>
#include<string>
#include<cstring>
#include<cstdio>
#include<sys/types.h>
#include<sys/wait.h>

using namespace std;

//演示以fork父子进程的方式创建匿名管道
int main()
{
    //step1:创建一个管道
    int pipefd[2] = {0};//文件描述符数组,用于接受pipe读端、写端
    int ret = pipe(pipefd);
    assert(ret != -1);//用于检测是否成功创建管道
    (void)ret;//assert只在debug模式下有效,为了避免release模式下ret变量未被使用而报警设置。

    
    //验证:pipefd[2]具体含义
    #ifdef DEBUG
    cout << "pipefd[0]" << pipefd[0] << endl;
    cout << "pipefd[1]" << pipefd[1] << endl;
    cout<<endl;
    #endif

    //step2:创建子进程,对父子进程构建单向通信的信道
    pid_t id = fork();
    assert(id != -1);//fork失败
    if(0 == id)//子进程:此处设子进程读取
    {
        close(pipefd[1]);//对子进程,保留读端,关闭写端

        //……相关读取操作
        char re_buffer[1024*8]={0};//子进程缓冲区:用于接收读端读取到的数据
        while(true)
        {
            
            //从管道中读取数据存储入缓冲区
            ssize_t re = read(pipefd[0], re_buffer, sizeof(re_buffer)-1);
            if(re > 0)
            {
                re_buffer[re]= 0;
                cout << "【childpid-" << getpid() << ", get a message 】:" << re_buffer << endl;
            }
            else if(re == 0)//end of file
            {
                cout << "child: end of file, quit." << endl;
                break;//退出循环
            }
        }

        exit(0);//终止子进程
    }
    //父进程:此处设父进程写入
    close(pipefd[0]);//对父进程,保留写端,关闭读端

    //……相关写入操作
    char wr_buffer[1024*8]={0};//写端缓冲区,用于存储发送的数据
    string message = "父进程写入数据";//提示文字1
    int count=0;//提示文字2
    while(true)
    {
        //向wr_buffer中写入相关信息,并将其显示
        snprintf(wr_buffer,sizeof(wr_buffer),"fatherid-%d, %s, 第%d写入.",getpid(),message.c_str(),1+count++);
        //将写端缓冲区数据传入管道
        write(pipefd[1],wr_buffer,strlen(wr_buffer));
        //结束写端
        sleep(1);
        if(10 == count)
        {
            cout<<"father quit!\n"<<endl;
            break;
        }
    }

    //step3:执行完毕
    close(pipefd[1]);
    pid_t ret2 = waitpid(id, nullptr, 0);
    printf("wait:%d, childpid:%d, count:%d\n",ret2,id,count);
    assert(ret2 > 0);
    (void)ret2;

    return 0;
}

  
  
  

2.2.3、总结说明(匿名管道特点、读写原则)

  1)、管道特点
  1、匿名管道只能用于具有亲缘关系的进程之间进行通信。通常运用于父子进程(一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。假如不断在fork,那么就能实现父子、兄弟之间共用同一管道)。

  2、管道通过让进程间协同,提供了访问控制。(根据之前fork的学习,创建子进程后,父子进程执行是没有先后顺序的,即缺乏访问控制)。

  3、管道提供流式服务。(即面向字节流,后续需要通过订制协议来进行区分)。

  4、管道是基于文件的,文件的生命周期随进程,故管道的生命周期是随进程的。(一般而言,进程退出,管道释放)。

  5、管道是半双工的,数据只能向一个方向流动。(需要双方通信时,需要建立起两个管道。)
  
  
  2)、管道读写原则
  两种访问方式和两种退出方式:
  1、若写端快,读端慢,管道写满了就不再写入;
  2、若写端慢,读端快,管道内没有数据时,读端必须等待;
  3、若写端关闭,读端最终会读取到0,标志到了文件结尾。
  4、若读端关闭,OS会终止写进行。
  
  
  
  

2.2.4、扩展应用:进程池的模拟实现

  上述匿名管道,可运用于创建进程池。如下图,当有多个子进程时,父子之间建立起管道通信。当父进程发送指令(command code),可派遣任务让随机一个子进程执行。
  在这里插入图片描述
  
  PS:该部分相关代码如下,可结合思维导图理解。相关演示结果如下:
在这里插入图片描述
在这里插入图片描述

  
  

2.2.4.1、processpool.c


#include <iostream>
#include <vector>
#include <cstdlib>
#include <ctime>
#include <cassert>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include"task.hpp"

#define PROCESS_NUM 5 //子进程数目
using namespace std;

//子进程使用:用于等待接受父进程传递的任务编号指令,以便后续执行
int waitCommand(int pipefd, bool& quit)
{
    //读取:从对应的pipefd中读取到相关的指令,将其返回。
    uint32_t command = 0;
    int ret = read(pipefd, &command, sizeof(command));
    if(ret==0)//读取到文件末尾
    {
        quit=true;
        return -1;
    }
    assert(ret==sizeof(uint32_t));//用于检测读取到的指令是否合法
    return command;
}


//父进程使用:用于委派任务给子进程
void sentAndWakeup(pid_t id, int pipefd, uint32_t command)
{
    //写入:将对应的command传递到子进程对应的通信管道中
    write(pipefd,&command,sizeof(command));
    cout<< "【father process-" << getpid() <<"】: call child process-" << id << ", execute task-" << desc[command] << ", child pipefd-" << pipefd <<"\n" <<endl;
}

int main()
{
    //1、准备工作
    Load();//加载任务
    vector<pair<pid_t,int>> slots;//用于记录创建出的子进程,以及该进程对应的通信管道


    //2、创建子进程,建立单向通信管道
    for(int i = 0; i < PROCESS_NUM; ++i)
    {
        //建立管道
        int pipefd[2];
        int n = pipe(pipefd);
        assert(n == 0);
        (void)n;

        //创建子进程
        pid_t id = fork();
        assert(id!=-1);

        if(0==id)//对子进程
        {
            close(pipefd[1]);

            //3、子进程开始工作
            while(true)
            {
                // 等待父进程命令
                bool quit = false;
                int command = waitCommand(pipefd[0], quit);
                if(quit)//在管道中读取到文件尾
                    break;

                // 执行对应的命令:此处对command做了检测
                if(command >= 0 && command <handlerSize())
                {
                    calltasks[command];
                }
                else{
                    cout<<"command error: "<< command << endl;
                }
            }
            exit(1);//子进程结束,退出
        }
        //2、对父进程
        close(pipefd[0]);//对管道:关闭读端
        slots.push_back({id,pipefd[1]});//对进程:记录当前创建的子进程id及其通信管道fd
    }

    //3、父进程开始工作:派遣任务
    srand((unsigned long)time(nullptr)*getpid());//随机数方式的负载均衡,这里为了尽量随机可做一些处理
    while(true)
    {

        int command = rand() % handlerSize();// 选择一个任务:获取任务编号
        int choosechild = rand() % slots.size();// 选择一个子进程:获取到子进程id,通信管道fd
        sentAndWakeup(slots[choosechild].first, slots[choosechild].second, command); // 将任务派遣给子进程:根据选择的进程choosechild,将command任务编号传入该进程对应的通信管道。

        sleep(1);
    }

    //4、父进程结束工作:收尾处理
    for(const auto &it : slots)//关闭所有通信管道
    {
        close(it.second);
    }

    for(const auto &it : slots)//回收所有的子进程信息
    {
        waitpid(it.first,nullptr,0);
    }

    return 0;
}



  
  
  
  
  

2.2.4.2、Task.hpp

#pragma once
#include<iostream>
#include<vector>
#include<unordered_map>
#include<functional>
#include<unistd.h>


using func = std::function<void()>;
std::vector<func> calltasks;//任务表:存储各函数接口

std::unordered_map<int,std::string> desc;//任务编号及其文字描述


//任务:(各函数实现)
void readMySQL()
{
    std::cout << "【child process-" << getpid() << " 】 :执行访问数据库的任务\n" << std::endl;
}

void execuleUrl()
{
    std::cout << "【child process-" << getpid() << " 】 :执行url解析\n" << std::endl;
}

void cal()
{
    std::cout << "【child process-" << getpid() << " 】 :执行加密任务\n" << std::endl;
}

void save()
{
    std::cout << "【child process-" << getpid() << " 】 执行数据持久化任务\n" << std::endl;
}


//任务汇总
void Load()
{
    desc.insert({calltasks.size(),"readMySQL:读取数据库"});
    calltasks.push_back(readMySQL);

    desc.insert({calltasks.size(),"execuleUrl:进行url解析"});
    calltasks.push_back(execuleUrl);

    desc.insert({calltasks.size(),"cal:进行加密"});
    calltasks.push_back(cal);

    desc.insert({calltasks.size(),"saveL:进行数据持久化"});
    calltasks.push_back(save);

}


void showHandler()//用于展示目前所具有的任务信息及其编号
{
    for(auto &it: desc)
    {
        std::cout << it.first << "\t" << it.second << std::endl;
    }
}


int handlerSize()//用于获取函数数组的大小,即任务总数
{
    return calltasks.size();
}


  
  
  
  
  

2.3、名命管道

2.3.1、是什么

  1)、问题引入:对于非血缘关系的进程,如何让二者看到同一份资源?
  
  回答:在磁盘上建立名命管道文件(FIFO文件),可以被双方进程打开,但不会将内存数据刷新到磁盘上。(PS:命名管道是一种特殊类型的文件,其在磁盘中大小为0)。
  
  
  
  2)、如何创建一份名命管道文件?
  相关函数指令:mkfifo,(man 3 mkfifo 可在Linux中查看)。
  

在这里插入图片描述函数介绍

  头文件和对应函数声明:

NAME
       mkfifo - make a FIFO special file (a named pipe)

SYNOPSIS
       #include <sys/types.h>
       #include <sys/stat.h>

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

  具体介绍:

DESCRIPTION
       mkfifo()  makes  a FIFO special file with name pathname.  mode specifies the FIFO's permissions.
       It is modified by the process's umask in the usual way: the permissions of the created file  are
       (mode & ~umask).

       A FIFO special file is similar to a pipe, except that it is created in a different way.  Instead
       of being an anonymous communications channel, a FIFO special file is entered into the file  sys‐
       tem by calling mkfifo().

       Once  you  have  created a FIFO special file in this way, any process can open it for reading or
       writing, in the same way as an ordinary file.  However, it has to be open at both ends  simulta‐
       neously  before  you can proceed to do any input or output operations on it.  Opening a FIFO for
       reading normally blocks until some other process opens the  same  FIFO  for  writing,  and  vice
       versa.  See fifo(7) for nonblocking handling of FIFO special files.

  返回值:

RETURN VALUE
       On success mkfifo() returns 0.  In the case of an error, -1 is returned (in which case, errno is
       set appropriately).

  
  
  

在这里插入图片描述使用演示

  mkfifo named_pipe:mkfifo可在命令行上使用。

在这里插入图片描述

  

[wj@VM-4-3-centos T0917]$ mkfifo named_pipe
[wj@VM-4-3-centos T0917]$ ll
total 0
prw-rw-r-- 1 wj wj 0 Sep 16 20:48 named_pipe //可以看到其在磁盘上大小为0

[wj@VM-4-3-centos T0917]$ echo "hello" > named_pipe 

[wj@VM-4-3-centos T0917]$ cat < named_pipe 
hello

  
  以脚本演示:while :; do echo "hello pipe"; sleep 1 ; done > named_pipe
在这里插入图片描述
  
  
  
  
  

2.3.2、相关使用演示

  整体实现框架如下:
在这里插入图片描述
  演示结果:

在这里插入图片描述
  
  

2.3.2.1、comm.hpp

#ifndef _COMM_H_
#define _COMM_H_

#include<iostream>
#include<string>
#include<cstring>
#include<assert.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include "log.hpp"


using namespace std;

#define MODE 0666 //mkfifo中第二参数mode
#define SIZE 1024 //读写端缓冲区大小

//命名管道路径、名称:此处创建在当前路径下
string ipcPath="./fifo.ipc";



#endif

  
  
  
  

2.3.2.2、log.hpp

#ifndef _LOG_H
#define _LOG_H

#include<iostream>
#include<ctime>

#define Debug 0
#define Notice 1
#define Warning 2
#define Error 3

const std::string msg[]={
    "Debug",
    "Notice",
    "Warning",
    "Error"
};

std::ostream &Log(std::string masseage, int label)
{
    std::cout << "[" << (unsigned long)time(nullptr) << "] :" << msg[label]<< ", "<< masseage << std::endl;
    return std::cout;
}

#endif

  
  
  
  

2.3.2.3、server.cxx


#include "comm.hpp"

static void getMessage(int fd)
{
    char buffer[SIZE]={0};//读端缓冲区
    while(true)
    {
        //void * memset ( void * ptr, int value, size_t num );
        memset(buffer,'\0',sizeof(buffer));//为了每次接受信息前,清屏处理上此遗留的无效数据

        int rd = read(fd, buffer, sizeof(buffer) - 1);
        if (rd > 0) // 读取到数据
        {
            cout << "masseage from client:> " << buffer << endl;
        }
        else if (rd == 0) // 读取到文件尾end of file
        {
            cout << "end of the file, quit!" << endl;
            break;
        }
        else
        {
            perror("read"); // error
            break;
        }
    }
}


int main()//server:读取端,用于接受client发送的通信
{
    //1、创建管道文件
    int ret = mkfifo(ipcPath.c_str(), MODE);
    assert(0 == ret);
    (void)ret;
    Log("server:创建管道文件",Debug) << endl;

    //2、正常文件操作:接受通信、显示通信
    //2.1、打开名命管道
    int fd = open(ipcPath.c_str(), O_RDONLY);
    if(fd < 0)
    {
        perror("open");
        exit(2);//处理assert,上述mkfifo也可以用perror检测
    }
    Log("server:打开名命管道",Debug)<< endl;



    //2.2、进行通信
    getMessage(fd);

    //2.3、通信结束,关闭文件
    close(fd);
    Log("server:通信结束,关闭文件",Debug)<< endl;


    //3、删除管道文件
    unlink(ipcPath.c_str());
    Log("server:删除管道文件",Debug)<< endl;


    return 0;
}

  
  
  
  

2.3.2.4、client.cxx


#include "comm.hpp"

int main()//client:写入端,用于发送通信到管道中
{
    //1、获取管道文件
    int fd = open(ipcPath.c_str(), O_WRONLY);
    if(fd < 0)
    {
        perror("open");
        exit(1);
    }
    Log("client: 获取管道文件",Debug)<< endl;

    

    //2、ipc通信过程
    string buffer;
    while(true)
    {
        cout<< "client sent massage:> ";
        std::getline(std::cin, buffer);//获取数据到缓冲区
        write(fd,buffer.c_str(),buffer.size());//将缓冲区数据写入通信管道中

    }


    //3、关闭管道文件
    close(fd);
    Log("client: 关闭管道文件",Debug)<< endl;



    return 0;
}

  
  
  

2.3.2.5、server端加入子进程(多个读端演示)

  相关演示结果:
在这里插入图片描述
  
  只需要在server端做出小改动就行:

static void getMessage(int fd)
{
    char buffer[SIZE]={0};//读端缓冲区
    while(true)
    {
        //void * memset ( void * ptr, int value, size_t num );
        memset(buffer,'\0',sizeof(buffer));//为了每次接受信息前,清屏处理上此遗留的无效数据

        int rd = read(fd, buffer, sizeof(buffer) - 1);
        if (rd > 0) // 读取到数据
        {
            cout <<"child: " << getpid() <<" | masseage from client:> " << buffer << endl;
        }
        else if (rd == 0) // 读取到文件尾end of file
        {
            cout << "end of the file, quit!" << endl;
            break;
        }
        else
        {
            perror("read"); // error
            break;
        }
    }
}
    // //2.2、进行通信
    // getMessage(fd);

    //2.2、写法二:创建子进程,进行多项读端通信
    int nums=3;//进程数目
    for(int i = 0; i < nums; ++i)
    {
        int id = fork();
        assert(id >= 0);
        if(id == 0)//子进程:执行读取任务
        {
            getMessage(fd);
            exit(1);//子进程结束,退出进程
        }
    }
    for(int i = 0; i < nums; ++i)//父进程:等待子进程结束
    {
        waitpid(-1,nullptr,0);
    }

  
  
  
  
  
  
  
  
  
  
  
  

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1021669.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Rocketmq--消息发送和接收演示

使用Java代码来演示消息的发送和接收 <dependency><groupId>org.apache.rocketmq</groupId><artifactId>rocketmq-spring-boot-starter</artifactId><version>2.0.2</version> </dependency> 1 发送消息 消息发送步骤: 创建…

三维模型3DTile格式轻量化压缩必要性分析

三维模型3DTile格式轻量化压缩必要性分析 理解3DTile格式轻量化压缩的必要性&#xff0c;首先需要理解三维模型的复杂性和数据量。三维模型通常包含大量的顶点、面片和纹理信息&#xff0c;这使得其数据量非常大&#xff0c;尤其对于大规模的三维地理空间数据&#xff0c;例如城…

Python:为何成为当下最热门的编程语言?

文章目录 &#x1f34b;引言&#x1f34b;1. 简单易学&#x1f34b;2. 多领域应用&#x1f34b;3. 强大的社区支持&#x1f34b;4. 丰富的库和框架&#x1f34b;5. 跨平台兼容&#x1f34b;6. 开源和免费&#x1f34b;7. 数据科学和人工智能的崛起&#x1f34b;8. 自动化和脚本…

YOLOv3深度解析【未完待续】

概况 &#xff08;1&#xff09;YOLOv3是YOLO系列第一次引入残差连接来解决深度网络中的梯度消失问题&#xff08;是不是第一次&#xff0c;有待你后面考证&#xff09;&#xff0c;实际用的backbone是DarkNet53 &#xff08;2&#xff09;最显著的改进&#xff0c;也是对你涨…

【SpringMVC】基于 Spring 的 Web 层MVC 框架

&#x1f384;欢迎来到边境矢梦的csdn博文&#x1f384; &#x1f384;本文主要梳理SpringMVC : 基于 Spring 的 Web 层MVC 框架 &#x1f384; &#x1f308;我是边境矢梦&#xff0c;一个正在为秋招和算法竞赛做准备的学生&#x1f308; &#x1f386;喜欢的朋友可以关注一下…

五种利用ChatGPT帮助大学申请的方法

自去年末以来&#xff0c;ChatGPT和其他生成式人工智能正式进入公众视野&#xff0c;并在超多领域广泛应用。在教育领域&#xff0c;学生使用ChatGPT来写论文成了普遍现象。各教育组织和专家褒贬不一。 一些教授严厉禁止使用人工智能来写作业&#xff0c;认为是学术欺诈。著名…

Windows下SpringBoot连接Redis的正确使用姿势

1. 安装Redis 1.1通过wsl安装redis 参考官方安装文档&#xff0c;需要在wsl2上安装redis服务。 注意我们启动redis的方式&#xff1a; First way&#xff1a;采用官方文档的方式&#xff1a;sudo service redis-server start&#xff0c;关闭wsl后redis在后台仍能运行&…

堆的介绍与堆的实现和调整

个人主页&#xff1a;Lei宝啊 愿所有美好如期而遇 目录 ​​堆的介绍&#xff1a; 关于堆的实现及相关的其他问题&#xff1a; 堆的初始化&#xff1a; 堆的销毁&#xff1a; 插入建堆&#xff1a; 堆向上调整&#xff1a; 交换两个节点的值&#xff1a; 堆向下调整&a…

STM32单片机——ADC数据采集

STM32单片机——ADC数据采集 ADC相关理论概述CubeMX工程配置HAL库程序设计固件库程序设计 参考博文1&#xff1a;STM32——ADC采集参考博文2&#xff1a;2022年8月12日STM32——ADC采集 ADC相关理论概述 ADC是什么 全称&#xff1a;Analog-to-Digital Converter&#xff0c;指…

三步实现Mybatis(Mybatis-Plus)多数据源配置

前言 要实现多数据源可以采用dynamic-datasource或者mybatis-mate&#xff0c;本文就以dynamic-datasource为例 dynamic-datasource简介 springboot 快速集成多数据源的启动器 使用文档(opens new window) 支持 数据源分组 &#xff0c;适用于多种场景 纯粹多库 读写分离 一主…

springcloud3 分布式事务-seata的搭建与微服务整合3

一 seata的搭建 1.1 seata的配置 springcloud3 Seata分布式事务以及seata服务搭建1_健康平安的活着的博客-CSDN博客 二 seata微服务的配置 2.1 结构 2.2 修改配置 客户端的配置要和服务端配置一致。在seata的cofig/registry.conf文件中。 3个微服务模块均按这样的配置…

记录本地Nginx发布vue项目

一、前端&#xff1a;vue-cli-service build 二、下载Nginx&#xff0c;并创建目录&#xff0c;放置静态文件 三、在conf目录下nginx.conf文件配置代理服务 server {listen 8787;server_name localhost;location / {root app/dist; #前端dist包地址index index.html…

Vue3搭配Element Plus 实现候选搜索框效果

直接上代码 <el-col :span"14" class"ipt-col"><el-input v-model"projectName" class"w-50 m-2" input"inputChange" focus"inputFocusFn" blur"inputBlurFn" placeholder"请输入项目名…

18.SpringTask 定时任务框架

springTask是spring框架提供的任务调度工具&#xff0c;可以按照约定的时间自动执行某个代码逻辑 1.回顾cron表达式 cron分为七个域&#xff1a;秒、分钟、小时、日、月、周、年&#xff08;可选&#xff09;&#xff0c;日与周只能定义一个另外一个设为&#xff1f; cron会看…

【Pinia】Pinia的概念、优势及使用方式

学习公司的项目&#xff0c;发现用到了Pinia&#xff0c;于是上网学习了一下&#xff0c;发现了一篇比较优秀的文章&#xff0c;于是将极少部分放到此记录学习&#xff0c;原文链接在末尾。 是什么 官网解释&#xff1a; Pinia 是 Vue 的存储库&#xff0c;它允许您跨组件/页…

海外媒体宣发:海外媒体发稿6种方式方法分享

科学创新在这个时代中起着了至关重要的作用。做为科谱网络写手&#xff0c;大家要不断找到新的专用工具来提高我们自己的文章内容品质和质量。在这篇文章中&#xff0c;我们将给大家分享6个通过美联社检验的发稿神器&#xff0c;帮你的科普文章如鱼得水。 1.现状分析专用工具在…

【直播预约中】 腾讯大数据 x StarRocks|构建新一代实时湖仓

随着信息时代的兴起&#xff0c;数据已成为推动业务决策和创新的核心要素&#xff1b;结构化、半结构化等多种类型的数据呈现爆炸式增长&#xff0c;如何高效处理和分析海量数据已经成为关键挑战&#xff0c;结合传统数仓与数据湖优势的湖仓一体&#xff08;Lakehouse&#xff…

25、字符缩放显示任意大小(LCD|OLED)

在常见的显示屏中无论是 ASCII 字符还是 GB2312 的字符&#xff0c;都只能显示字库中设定的字体大小&#xff0c;例如&#xff0c;我们想显示一些像素大小为 48x48 的字符&#xff0c;那我们又得制作相应的字库&#xff0c;非常麻烦。为此在野火的基础上编写了一些函数&#xf…

数据结构 第二章作业 线性表 西安石油大学

在顺序表中插入和删除一个结点需平均移动多少个结点&#xff1f;具体的移动次数取决于 哪两个因素&#xff1f; 在顺序表中插入和删除一个结点时&#xff0c;平均移动的结点数量取决于两个因素&#xff1a;插入/删除位置和当前顺序表的长度。 插入/删除位置&#xff1a;如果要…

webservice初探

使用jdk1.8完成了一个小示例&#xff0c;记录一下。springboot使用的2.7.15版本。 服务端 domain package com.example.wsserver.domain;public class Zonecode {public Zonecode(String code, String name) {this.code code;this.name name;}private String code;private…