<The pipeline of Linux process communication>——《Linux》

Table of contents

1. Process communication

1. Introduction to interprocess communication

2. The purpose of inter-process communication

3. Inter-process communication development

4. Classification of inter-process communication

2. Pipeline

1. What is a pipeline

2. Anonymous pipes

3. Use fork to share pipeline principles

4. From the perspective of file descriptors - in-depth understanding of pipelinesEdit

5. Programming simulation to realize parent-child process reading and writing communication in the pipelineEdit

6. Process control:

6.1 Parent process controls a single child process

6.2 Parent process controls batch child processes

 6.3 Introduction to load balancing:​

7. From the perspective of the kernel - the essence of the pipeline​

 7.1 Example: Add pipeline implementation in minishell:

8. Pipeline read and write rules

9. Pipeline characteristics

10. Summary of pipeline characteristics:

11. Named Pipes

11.1 Create a named pipe

11.2 The difference between anonymous pipes and named pipes

11.3 Opening Rules for Named Pipes

11.4 Example: File Copy Using Named Pipes

11.5 Example: Implement server&client communication with named pipes (implemented in C language)

12. Use named pipes to realize server&client communication (C++ implementation)

13. Simulate the communication between two unrelated processes:

Postscript: ●Due to the limited level of the author, the article inevitably contains some errors. Readers are welcome to correct me. There are many slang words in this article. I sincerely hope for your advice!

                                                                           ——By Author: Xinxiao·Old Knowing


1. Process communication

1. Introduction to interprocess communication

Before communication, different processes (processes are independent) need to see the same resource (file, memory block, etc.)

The inter-process communication we want to learn is not to tell us how to communicate, but how two processes can see the same resource first!

Different resources determine different types of communication methods!

2. The purpose of inter-process communication

Data transfer: one process needs to send its data to another process
Resource sharing: The same resource is shared between multiple processes.
Notification event: A process needs to send a message to another process or a group of processes, informing it (they) that some event has occurred (such as notifying the parent process when the process terminates).
Process control: Some processes want to completely control the execution of another process (such as Debug process). At this time, the control process hopes to be able to intercept all traps and exceptions of another process, and to be able to know its state changes in time.

3. Inter-process communication development

pipeline
System V interprocess communication
POSIX interprocess communication

4. Classification of inter-process communication

pipeline
Anonymous pipe
named pipe
System V IPC
System V message queue
System V shared memory
System V semaphores
POSIX IPC
message queue
Shared memory
amount of signal
mutex
condition variable
read-write lock

2. Pipeline

1. What is a pipeline

Pipes are the oldest form of interprocess communication in Unix .
We call a flow of data from one process to another a "pipe"
Pipes: Provide a means of sharing resources!

2. Anonymous pipes

#include <unistd.h>
Function create an unnamed pipe
Prototype: int pipe(int fd[2]);
Parameters: fd: file descriptor array , where fd[0] represents the read end , fd[1] represents the write end
Return value : return 0 on success , error code on failure

example code

例子:从键盘读取数据,写入管道,读取管道,写到屏幕
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main( void )
{
    int fds[2];
    char buf[100];
    int len;
    if ( pipe(fds) == -1 )
        perror("make pipe"),exit(1);
    // read from stdin
    while ( fgets(buf, 100, stdin) ) {
        len = strlen(buf);
        // write into pipe
        if ( write(fds[1], buf, len) != len ) {
            perror("write to pipe");
            break;
       }
        memset(buf, 0x00, sizeof(buf));
        
        // read from pipe
        if ( (len=read(fds[0], buf, 100)) == -1 ) {
            perror("read from pipe");
            break;
       }
        // write to stdout
        if ( write(1, buf, len) != len ) {
            perror("write to stdout");
            break;
       }
   }
}

3. Use fork to share pipeline principles

4. From the perspective of file descriptors - deep understanding of pipelines

 5. Programming simulation to realize parent-child process reading and writing communication in the pipeline

mypipe:pipe.cc
	g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
	rm -f mypipe

 

#include <iostream>
#include <cstdio>
#include <unistd.h>
using namespace std;

int main()
{
    int pipefd[2] = {0};
    if (pipe(pipefd) != 0)
    {
        cerr << "pipe" << endl;
        return 1;
    }

    cout << "fd[0]: "<<pipefd[0]<<endl;
    cout << "fd[1]: "<<pipefd[1]<<endl;
    return 0;
}

 Simulate the parent-child process reading and writing communication in the pipe:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
using namespace std;

int main()
{
    //1.创建管道
    int pipefd[2] = {0};
    if (pipe(pipefd) != 0)
    {
        cerr << "pipe" << endl;
        return 1;
    }

    // 2.创建子进程
    pid_t id = fork();
    if (id < 0)
    {
        cerr << "fork error" << endl;
        return 2;
    }
    else if( id == 0)
    {
        //child
        //子进程来进行读取,子进程就应该关掉写端
        close(pipefd[1]);
        #define NUM 1024
        char buffer[NUM];
        while(true)
        {
            memset(buffer,0,sizeof(buffer));
            ssize_t s = read(pipefd[0],buffer,sizeof(buffer)-1);
            if(s>0)
            {
                //读取成功
                buffer[s] = '\0';
                cout<< "子进程收到消息,内容是:"<<buffer<<endl;
            }
            else if(s == 0)
            {
                cout<< "父进程写完,子进程退出!"<<endl;
                break;
            }
            else 
            {
                //Do Nothing
            }
        }
        close(pipefd[0]);
        exit(0);

    }
    else
    {
        //parent
        //父进程来进行写入,就应该关掉读端
        //方式1
        // close(pipefd[0]);
        // string msg = "你好,子进程!我是父进程!";
        // int cnt = 0;
        // while(cnt < 5)
        // {
        //     write(pipefd[1],msg.c_str(),msg.size()); //这里无需对字符串+1,因为管道也是文件,无需对字符串的结尾\0区别
        //     sleep(1);  //这里只是为了观察打印现象明显
        //     cnt++;
        // }

        //方式2
        close(pipefd[0]);
        const char *msg = "你好子进程,我是父进程,这次发送的信息编号是";
        int cnt = 0;
        while(cnt < 5)
        {
            char sendBuffer[1024];
            sprintf(sendBuffer,"%s : %d",msg,cnt);
            write(pipefd[1],sendBuffer,strlen(sendBuffer)); //这里无需对字符串+1,因为管道也是文件,无需对字符串的结尾\0区别
            sleep(1);  //这里只是为了观察打印现象明显
            cnt++;
        }
        close(pipefd[1]);
        cout<<"父进程写完了!"<<endl;
    }
    //0——>嘴巴 ——>读
    //1——>笔  ——>写
   pid_t res = waitpid(id,nullptr,0);
   if(res > 0)
   {
    cout<<"等待子进程成功!"<<endl;
   }
    return 0;
}

(1) When the parent process does not write data, the child process is waiting! Therefore, after the parent process writes, the child process can read (will return) to the data, and the child process prints and reads data based on the rhythm of the parent process!

(2) When the parent process and the child process read and write, there is a certain order!

Here in the pipeline, the pipeline can be filled. View pipeline information command: ulimit -a

Inside the pipeline, if there is no data, the reader must block and wait (read) until the pipeline has data.

Inside the pipeline, if the data is full, the writer must block waiting (write), waiting for space in the pipeline.

The above is sequential because the internal access control mechanism of the pipe! (synchronization and mutual exclusion mechanism)

(3) When the parent and child processes we learned before printf (write to the display, and the display is also a file), will there be an order?

No. This one has no access control mechanism!

(4) The essence of blocking waiting:

Put the task_struct of the current process into the waiting queue!

6. Process control:

The control process responds according to demand.

6.1 Parent process controls a single child process

#include <iostream>
#include <cstdio>
#include <cstring>
#include <ctime>
#include <vector>
#include <cstdlib>
#include <cassert>
#include <unordered_map>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>

using namespace std;

typedef void (*functor)(); // 函数指针
vector<functor> functors;  // 方法集合
// for debug
unordered_map<uint32_t, string> info;

void f1()
{
    cout << "这是一个处理日志的任务,执行的进程 ID [" << getpid() << "]"
         << "执行时间是[" << time(nullptr) << "]" << endl;
}

void f2()
{
    cout << "这是一个备份任务,执行的进程 ID [" << getpid() << "]"
         << "执行时间是[" << time(nullptr) << "]" << endl;
}
void f3()
{
    cout << "这是一个处理网络连接的任务,执行的进程 ID [" << getpid() << "]"
         << "执行时间是[" << time(nullptr) << "]" << endl;
}

void loadFunctor()
{
    info.insert({functors.size(), "处理日志的任务"});
    functors.push_back(f1);

    info.insert({functors.size(), "备份数据任务"});
    functors.push_back(f2);

    info.insert({functors.size(), "处理网络连接的任务"});
    functors.push_back(f3);
}

int main()
{
    // 0.加载任务列表
    loadFunctor();

    // 1.创建管道
    int pipefd[2] = {0};
    if (pipe(pipefd) != 0)
    {
        cerr << "pipe error" << endl;
        return 1;
    }

    // 2.创建子进程
    pid_t id = fork();
    if (id < 0)
    {
        cerr << "fork error" << endl;
        return 2;
    }
    else if (id == 0)
    {
        // 3.关闭不需要的文件fd
        //  child,read
        close(pipefd[1]);
        // 4.业务处理
        while (true)
        {
            uint32_t operatorType = 0;
            // 如果有数据就读取。如果没有数据,就阻塞等待,等待任务的到来
            ssize_t s = read(pipefd[0], &operatorType, sizeof(uint32_t));
            if(s == 0)
            {
                cout<<"需求已完成,我即将退出服务状态!"<<endl;
                break;
            }
            assert(s == sizeof(uint32_t));
            (void)s;
            // assert断言,是编译有效debug模式。release模式,断言就没有了。
            // 一旦断言没有了,s变量就是只被定义了,没有被使用。release模式中,可能会有warning

            
            if (operatorType < functors.size())
            {
                functors[operatorType]();
            }
            else
            {
                cerr << "bug? operatorType = " << operatorType << endl;
            }
        }
        close(pipefd[0]);
        exit(0);
    }
    else
    {
        srand((long long)time(nullptr));
        // parent,write-操作
        // 关闭不需要的文件fd
        close(pipefd[0]);
        // 指派任务
        int num = functors.size();
        int cnt = 10; // 设定10个任务
        while (cnt--)
        {
            // 形成任务码
            uint32_t commandCode = rand() % num;
            cout << "父进程指派任务完成,任务是:" << info[commandCode] << "任务的编号是:" << cnt << endl;
            // 向指定的进程下达执行任务的操作
            write(pipefd[1], &commandCode, sizeof(uint32_t));
            sleep(1);
        }
        close(pipefd[1]);
        pid_t res = waitpid(id, nullptr, 0);
        if (res)
        {
            cout << "Wait Success!" << endl;
        }
    }
    return 0;
}

6.2 Parent process controls batch child processes

How does the process control the batch process?

Here is the process pool mechanism. So which process will be assigned? What tasks are assigned to it? By what assignment? (assigned by pipe)

Shell script instructions:

 while :; do ps axj | head -1; ps ajx |grep mypipe | grep -v grep; sleep 1; done

 6.3 Introduction to load balancing:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <ctime>
#include <vector>
#include <cstdlib>
#include <cassert>
#include <unordered_map>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>

using namespace std;

typedef void (*functor)(); // 函数指针
vector<functor> functors;  // 方法集合
// for debug
unordered_map<uint32_t, string> info;

void f1()
{
    cout << "这是一个处理日志的任务,执行的进程 ID [" << getpid() << "]"
         << "执行时间是[" << time(nullptr) << "]\n" << endl;
}

void f2()
{
    cout << "这是一个备份任务,执行的进程 ID [" << getpid() << "]"
         << "执行时间是[" << time(nullptr) << "]\n" << endl;
}
void f3()
{
    cout << "这是一个处理网络连接的任务,执行的进程 ID [" << getpid() << "]"
         << "执行时间是[" << time(nullptr) << "]\n" << endl;
}

void loadFunctor()
{
    info.insert({functors.size(), "处理日志的任务"});
    functors.push_back(f1);

    info.insert({functors.size(), "备份数据任务"});
    functors.push_back(f2);

    info.insert({functors.size(), "处理网络连接的任务"});
    functors.push_back(f3);
}

typedef pair<int32_t,int32_t> elem;
//第一个int32_t:进程pid,第二个int32_t:该进程对应的管道写端fd
vector<elem> assignMap;
int processNum = 5;

void work(int blockFd)
{
    cout << "进程[" << getpid() << "]" << "开始工作" << endl;
    // 子进程核心工作的代码
    while (true)
    {
        // a.阻塞等待  b.获取任务
        uint32_t operatorCode = 0;
        ssize_t s = read(blockFd, &operatorCode, sizeof(uint32_t));
        if (s == 0)
            break;
        assert(s == sizeof(uint32_t));
        (void)s;

        // c.处理任务
        if (operatorCode < functors.size())
            functors[operatorCode]();
    }
    cout<<"进程["<<getpid()<<"]"<<"结束工作"<<endl;
}
//[子进程的pid,子进程的管道fd]
void balancesendTask(const vector<elem> &processFds)
{
    srand((long long)time(nullptr));
    while(true)
    {
        sleep(1);
        //选择一个进程,选择进程是随机的,没有压着一个进程给任务
        //较为均匀的将任务给所有的子进程——负载均衡
        uint32_t pick = rand()% processFds.size();

        //选择一个任务
        uint32_t task = rand()%functors.size();

        //把任务给一个指定的进程
        write(processFds[pick].second,&task,sizeof(task));

        //打印对应的提示信息
        cout << "父进程指派任务——>" << info[task] << "给进程:"
             << processFds[pick].first << "编号:" << pick << endl;
    }
}
int main()
{
    loadFunctor();
    vector<elem> assignMap;
    // 创建processNum个进程
    for(int  i =0; i< processNum;i++)
    {
        //定义保存管道上fd的对象
        int pipefd[2] = {0};
        //创建管道
        pipe(pipefd);
        //创建子进程
        pid_t id = fork();
        if( id == 0)
        {
            //子进程读取
            close(pipefd[1]);
            //子进程执行
            work(pipefd[0]);
            close(pipefd[0]);
            exit(0);
        }
        // 父进程做的事情
        close(pipefd[0]);
        elem e(id, pipefd[1]);
        assignMap.push_back(e);
    }
    cout<<"Create All Process Success!"<<endl;
    //父进程,派发任务
    balancesendTask(assignMap);
    //回收资源
    for (int i = 0; i < processNum; i++)
    {
        if (waitpid(assignMap[i].first, nullptr, 0) > 0)
        {
            cout << "Wait for: pid=" << assignMap[i].first << "Wait Success!"
                 << "number:" << i << endl;
        }
    }
}

7. From the perspective of the kernel - the essence of the pipeline

So, treat pipelines like you treat files! The use of pipes is consistent with files, which caters to the "Linux everything is a file idea " .

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define ERR_EXIT(m) \
do \
{ \
    perror(m); \
    exit(EXIT_FAILURE); \
} while(0)
int main(int argc, char *argv[])
{
    int pipefd[2];
    if (pipe(pipefd) == -1)
    ERR_EXIT("pipe error");
    pid_t pid;
    pid = fork();
    if (pid == -1)
   ERR_EXIT("fork error");
    if (pid == 0) {
        close(pipefd[0]);
        write(pipefd[1], "hello", 5);
        close(pipefd[1]);
        exit(EXIT_SUCCESS);
   }
 close(pipefd[1]);
    char buf[10] = {0};
    read(pipefd[0], buf, 10);
    printf("buf=%s\n", buf);
    return 0;
}

 7.1 Example: Add pipeline implementation in minishell:

# include <stdio.h>
# include <stdlib.h>
# include <unistd.h>
# include <string.h>
# include <fcntl.h>
# define MAX_CMD 1024
char command[MAX_CMD];
int do_face()
{
    memset(command, 0x00, MAX_CMD);
    printf("minishell$ ");
    fflush(stdout);
    if (scanf("%[^\n]%*c", command) == 0) {
        getchar();
        return -1; 
   }   
    return 0;
}
char **do_parse(char *buff)
{
    int argc = 0;
    static char *argv[32];
    char *ptr = buff;
    while(*ptr != '\0') {
        if (!isspace(*ptr)) {
            argv[argc++] = ptr;
            while((!isspace(*ptr)) && (*ptr) != '\0') {
                ptr++;
           }
            continue;
       }
        *ptr = '\0';
        ptr++;
   }
    argv[argc] = NULL;
    return argv;
}
int do_redirect(char *buff)
{
    char *ptr = buff, *file = NULL;
    int type = 0, fd, redirect_type = -1;
 while(*ptr != '\0') {
        if (*ptr == '>') {
            *ptr++ = '\0';
            redirect_type++;
            if (*ptr == '>') {
                *ptr++ = '\0';
                redirect_type++;
           }
            while(isspace(*ptr)) {
                ptr++;
           }
            file = ptr;
            while((!isspace(*ptr)) && *ptr != '\0') {
                ptr++;
           }
            *ptr = '\0';
            if (redirect_type == 0) {
                fd = open(file, O_CREAT|O_TRUNC|O_WRONLY, 0664);
           }else {
                fd = open(file, O_CREAT|O_APPEND|O_WRONLY, 0664);
           }
            dup2(fd, 1);
       }
        ptr++;
   }
    return 0;
}
int do_command(char *buff)
{
    int pipe_num = 0, i;
    char *ptr = buff;
    int pipefd[32][2] = {
       
       {-1}};
    int pid = -1;
    pipe_command[pipe_num] = ptr;
    while(*ptr != '\0') {
        if (*ptr == '|') {
            pipe_num++;
            *ptr++ = '\0';
            pipe_command[pipe_num] = ptr;
            continue;
       }
        ptr++;
   }
    pipe_command[pipe_num + 1] = NULL;
    return pipe_num;
}
int do_pipe(int pipe_num)
{
    int pid = 0, i;
    int pipefd[10][2] = {
       
       {0}};
    char **argv = {NULL};
 for (i = 0; i <= pipe_num; i++) {
        pipe(pipefd[i]);
   }
    for (i = 0; i <= pipe_num; i++) {
        pid = fork();
        if (pid == 0) {
            do_redirect(pipe_command[i]);
            argv = do_parse(pipe_command[i]);
            if (i != 0) {
                close(pipefd[i][1]);
                dup2(pipefd[i][0], 0);
           }
            if (i != pipe_num) {
                close(pipefd[i + 1][0]);
                dup2(pipefd[i + 1][1], 1);
           }
            execvp(argv[0], argv);
       }else {
            close(pipefd[i][0]);
            close(pipefd[i][1]);
            waitpid(pid, NULL, 0);
       }
   }
    return 0;
}
int main(int argc, char *argv[])
{           
    int num = 0; 
    while(1) {  
        if (do_face() < 0)
            continue;
        num = do_command(command);
        do_pipe(num);
   }
    return 0;
}

shell script:

ps ajx | head -1 && ps ajx | grep sleep

Summarize:

The so-called "|" in the command line is an anonymous pipe!

8. Pipeline read and write rules

when there is no data to read
  • O_NONBLOCK disable : The read call is blocked, that is, the process suspends execution and waits until data arrives.
  • O_NONBLOCK enable : The read call returns -1 , and the errno value is EAGAIN .
when the pipe is full
  • O_NONBLOCK disable : The write call is blocked until a process reads the data
  • O_NONBLOCK enable : The call returns -1 , and the errno value is EAGAIN
  • If all the file descriptors corresponding to the write end of the pipe are closed, read returns 0
  • If the file descriptors corresponding to all pipe read ends are closed, the write operation will generate a signal SIGPIPE, which may cause the write process to exit
  • When the amount of data to be written is not greater than PIPE_BUF , linux will guarantee the atomicity of writing.
  • When the amount of data to be written is greater than PIPE_BUF , linux will no longer guarantee the atomicity of writing.

9. Pipeline characteristics

It can only be used to communicate between processes with a common ancestor (processes with kinship); usually, a pipe is created by a process, and then the process calls fork, and the pipe can be used between the parent and child processes.
Pipelines provide streaming services
  • Generally speaking, when the process exits, the pipeline is released, so the life cycle of the pipeline varies with the process
  • In general, the kernel will synchronize and mutex pipeline operations
The pipeline is half-duplex, and data can only flow in one direction; when two parties need to communicate, two pipelines need to be established

10. Summary of pipeline characteristics:

1. Pipes can only be used for inter-process communication between processes with blood relationship, and are often used for parent-child process communication.

2. The pipe can only communicate in one direction (determined by the kernel implementation) (a special case of half-duplex).

3. The pipeline has its own synchronization mechanism (when the pipe is full, the writer waits. When the pipe is empty, the reader waits) - comes with access control.

4. The pipeline is byte-oriented. The character written first must be read first, there is no format boundary, and the user needs to define the boundary to distinguish the content, for example: [sizeof(uint32_t)], etc. This involves knowledge of the network TCP protocol.

5. The life cycle of the pipeline follows the process: the pipeline is a file. After the process exits, the file that was opened will also exit.

So can only communication between parent and child processes (or blood relationship) processes? Is it okay for unrelated processes to communicate?

Answer: Yes. Use named pipes.

11. Named Pipes

A limitation of pipes is that they can only communicate between processes that share a common ancestor (affinity).
If we want to exchange data between unrelated processes, we can use FIFO files to do the job, which are often called named pipes.
Named pipes are a special type of file

11.1 Create a named pipe

Named pipes can be created from the command line. The command line method is to use the following command:
Named pipes can also be created from the program, the related functions are:
Create a named pipe :
$ mkfifo filename
int mkfifo(const char *filename,mode_t mode);
int main(int argc, char *argv[])
{
 mkfifo("p2", 0644);
 return 0;
}

11.2 The difference between anonymous pipes and named pipes

Anonymous pipes are created and opened by the pipe function.
Named pipes are created by the mkfififo function and opened with open
The only difference between FIFOs (named pipes) and pipes (anonymous pipes) is how they are created and opened, but once the work is done, they have the same semantics.

11.3 Opening Rules for Named Pipes

If the current open operation is opening the FIFO for reading
  • O_NONBLOCK disable : Block until a corresponding process opens the FIFO for writing
  • O_NONBLOCK enable : return success immediately
If the current open operation is opening the FIFO for writing
  • O_NONBLOCK disable : Block until a corresponding process opens the FIFO for reading
  • O_NONBLOCK enable : Immediately return failure, the error code is ENXIO

11.4 Example: File Copy Using Named Pipes

Read a file, write to a named pipe :
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define ERR_EXIT(m) \
do \
{ \
    perror(m); \
    exit(EXIT_FAILURE); \
} while(0)
int main(int argc, char *argv[])
{
    mkfifo("tp", 0644);
    int infd;
    infd = open("abc", O_RDONLY);
    if (infd == -1) ERR_EXIT("open");
    int outfd;
    outfd = open("tp", O_WRONLY);
    if (outfd == -1) ERR_EXIT("open");
 char buf[1024];
    int n;
    while ((n=read(infd, buf, 1024))>0)
   {
   write(outfd, buf, n);
   }
    close(infd);
    close(outfd);
    return 0;
}
Read from a pipe, write to a target file :
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define ERR_EXIT(m) \
 do \
 { \
 perror(m); \
 exit(EXIT_FAILURE); \
 } while(0)
 
int main(int argc, char *argv[])
{
 int outfd;
 outfd = open("abc.bak", O_WRONLY | O_CREAT | O_TRUNC, 0644);
 if (outfd == -1) ERR_EXIT("open");
 
    int infd;
    infd = open("tp", O_RDONLY);
    if (outfd == -1)
        ERR_EXIT("open");
    char buf[1024];
 int n;
 while ((n=read(infd, buf, 1024))>0)
 {
 write(outfd, buf, n);
 }
 close(infd);
 close(outfd);
 unlink("tp");
 return 0;
}

11.5 Example: Implement server&client communication with named pipes (implemented in C language)

# ll
total 12
-rw-r--r--. 1 root root 46 Sep 18 22:37 clientPipe.c
-rw-r--r--. 1 root root 164 Sep 18 22:37 Makefile
-rw-r--r--. 1 root root 46 Sep 18 22:38 serverPipe.c
# cat Makefile
.PHONY:all
all:clientPipe serverPipe
clientPipe:clientPipe.c
 gcc -o $@ $^
serverPipe:serverPipe.c
 gcc -o $@ $^
.PHONY:clean
clean:
 rm -f clientPipe serverPipe
serverPipe.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#define ERR_EXIT(m) \
do{\
 perror(m);\
 exit(EXIT_FAILURE);\
}while(0)
int main()
{
 umask(0);
 if(mkfifo("mypipe", 0644) < 0){
 ERR_EXIT("mkfifo");
 }
 int rfd = open("mypipe", O_RDONLY);
 if(rfd < 0){
 ERR_EXIT("open");
 }
 char buf[1024];
 while(1){
 buf[0] = 0;
 printf("Please wait...\n");
 ssize_t s = read(rfd, buf, sizeof(buf)-1);
 if(s > 0 ){
 buf[s-1] = 0;
 printf("client say# %s\n", buf);
 }else if(s == 0){
 printf("client quit, exit now!\n");
exit(EXIT_SUCCESS);
 }else{
 ERR_EXIT("read");
 }
 }
 close(rfd);
 return 0;
}
clientPipe.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#define ERR_EXIT(m) \
do{\
 perror(m);\
 exit(EXIT_FAILURE);\
}while(0)
int main()
{
 int wfd = open("mypipe", O_WRONLY);
 if(wfd < 0){
 ERR_EXIT("open");
 }
 char buf[1024];
 while(1){
 buf[0] = 0;
 printf("Please Enter# ");
 fflush(stdout);
 ssize_t s = read(0, buf, sizeof(buf)-1);
 if(s > 0 ){
 buf[s] = 0;
 write(wfd, buf, strlen(buf));
 }else if(s <= 0){
 ERR_EXIT("read");
 }
 }
 close(wfd);
 return 0;
}
result :

12. Use named pipes to realize server&client communication (C++ implementation)

 

 

We have learned before that two in-memory processes open the same file on disk. This is actually the use of named pipes.

We know that the essence of inter-process communication is that different processes need to see the same resource.

Anonymous pipe: the child process inherits the parent process and finds the same resource!

Named pipes: through a fifo file -> path -> uniqueness -> find the same resource through the path! 

13. Simulate the communication between two unrelated processes:

Makefile:

.PHONY:all
all:clientFifo serverFifo

clientFifo:clientFifo.cpp
	g++ -o $@ $^ -std=c++11
serverFifo:serverFifo.cpp
	g++ -o $@ $^ -std=c++11
	
.PHONY:clean
clean:
	rm  -rf clientFifo serverFifo

serverFifo: There is a space between lines at the receiving end because of the entel key. 

comm.h:

 

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <cerrno>
#include <cstdio>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define IPC_PATH "./.fifo"

 makefile:

 

.PHONY:all
all:clientFifo serverFifo

clientFifo:clientFifo.cpp
	g++ -Wall -o $@ $^ -std=c++11
serverFifo:serverFifo.cpp
	g++ -Wall -o $@ $^ -std=c++11
	
.PHONY:clean
clean:
	rm  -rf clientFifo serverFifo .fifo

serverFifo.cpp:

 

#include "comm.h"
using namespace std;

// 读取
int main()
{
    // extern int errno;
    umask(0);
    if (mkfifo(IPC_PATH, 0600) != 0)
    {
        cerr << "mkfifo error" << endl;
        return 1;
    }
    // cout<<"hello,server"<<endl;
    int pipeFd = open(IPC_PATH, O_RDONLY);
    if (pipeFd < 0)
    {
        cerr << "Open fifo error" << endl;
        return 2;
    }

// 正常的通信过程
#define NUM 1024
    char buffer[NUM];
    while (true)
    {
        ssize_t s = read(pipeFd, buffer, sizeof(buffer) - 1);
        if (s > 0)
        {
            buffer[s] = '\0';
            cout << "客户端——>服务器#" << buffer << endl;
        }
        else if (s == 0)
        {
            cout << "客户退出,服务器也将退出!";
            break;
        }
        else
        {
            cout << "read:" << strerror(errno) << endl;
        }
    }
    close(pipeFd);
    cout << "服务端退出!" << endl;
    unlink(IPC_PATH); // 删除创建的管道文件 .fifo
    return 0;
}

clientFifo.cpp:

 

#include "comm.h"
using namespace std;

// 写入
int main()
{
    int pipeFd = open(IPC_PATH, O_WRONLY);
    if (pipeFd < 0)
    {
        cerr << "open:" << strerror(errno) << endl;
        return 1;
    }
    // cout<<"hello,client"<<endl;

#define NUM 1024
    char line[NUM];
    while (true)
    {
        cout << "请输入你的消息#";
        fflush(stdout);
        memset(line, 0, sizeof(line));
        // 1.serverFifo:接收端行间距有空格是因为entel键。 
        //  if(fgets(line,sizeof(line),stdin) != nullptr)
        //  {
        //      write(pipeFd,line,strlen(line));
        //  }
        // 2.去除行间距
        if (fgets(line, sizeof(line), stdin) != nullptr)
        {
            // fgets是C语言的接口,line后自动添加\0
            // abcd\n\0
            // 处理
            line[strlen(line) - 1] = '\0';
            write(pipeFd, line, strlen(line));
        }
        else
        {
            break;
        }
    }
    close(pipeFd);
    cout << "客户端退出!" << endl;
    return 0;
}

Postscript:
●Due to the limited level of the author, there are inevitable errors in the article. Readers are welcome to correct the article, and the article is full of slang, and I sincerely hope to give advice!

                                                                           ——By Author: Xinxiao·Old Knowing

 

Guess you like

Origin blog.csdn.net/m0_57859086/article/details/128232772