[Linux] Named pipes in the pipeline of the stand-alone version of QQ

Remember the anonymous pipeline in the previous article?

Article Directory


foreword

What are 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.

1. Named pipes

Before learning named pipes, let's take a look at the function mkfifo that creates named pipes on the command line:

 Fifo means first in first out, which means first in first out. For example, we create a named pipe file directly in the directory:

The first P in the permission part of the file represents the pipeline file. Let's talk about the principle of named pipes:

There are also two processes, the upper one is the parent process and the lower one is the child process. The address of a file is recorded in a No. 3 file descriptor table of the parent process, which is also the file opened by the parent process. When we create a child process, If you want the child process to open the same file as the parent process, the operating system will not recreate the same file. The operating system will query whether the file to be opened by the child process has been opened. If it finds this The opened file will fill in the address of the file in the file descriptor table of the child process, so that the child process will point to the file opened by the parent process, and there will be variables like ret in the file. When we have a file When the descriptor points to this file, this variable will be ++, which is the reference count, and it will be -- after closing the file. So how to ensure that two unrelated processes see the same file and open it? In fact, it is very simple, because the uniqueness of the file is represented by the path. As long as different processes see the same file through the file path + file name and open it, they will see the same resource, which is the prerequisite for inter-process communication. .

 Next, we use code to demonstrate named pipes. First, we need to create two files client.cc to write client code, and serve.cc to write server code. Because we want to implement two executable programs this time, we need to generate them in the makefile Two executable programs, the makefile code is as follows:

 Adding all after .PHONY means that my target file is all, and we let all have only dependencies and no dependent methods, so that we will find the dependencies of server and client, and generate two executable programs. Let's formally write the code below. Remember the mkfifo function we just started talking about? The parameters of this function need the path and options (the mkfifo function in the C library is described below). For the path, we directly create a const string type string to save the path, because the server and the client need to open the same file, so we Create another public hpp header file and put the path we just defined into it:

Let's take a look at the mkfifo function description in the C library:

 We can see that this function has two parameters, the first parameter is the path, the second parameter is mode, what is mode? What is the mode_t type? In fact, mode_t is an unsigned integer. We mentioned it when we talked about files. It is a kind of permission that allows you to control the initial permissions of the files to be created. We set the permissions to 0666 by default.

Next, we write the code of the server. I don’t know if you still remember what I said before. The permission we gave is 0666, but the influence of the permission mask will become other, so if we don’t want to be affected by the permission mask, just Set the default permission mask to 0.

 Because we may fail when creating the pipeline file, we use the if statement to judge. If the return value of the function is equal to 0, it means success, otherwise it means failure. If it fails, we will print the corresponding error and return 1. The next step is to let the server Open the pipeline file, and then you can communicate normally after opening:

int main()
{
    //1.创建管道文件,我们今天只需要一次创建
    umask(0);   //这个设置并不影响系统的默认设置,只会影响当前进程
    int n = mkfifo(fifoname.c_str(),mode);
    if (n!=0)
    {
        std::cout<<errno<<" : "<<strerror(errno)<<std::endl;
        return 1;
    }
    std::cout<<"creat fifo file sucess"<<std::endl;
    // 2.让服务端直接开启管道文件
    int rfd = open(fifoname.c_str(),O_RDONLY);
    if (rfd<=0)
    {
        std::cout<<errno<<" : "<<strerror(errno)<<std::endl;
        return 2;
    }
    std::cout<<"open fifo success , begin ipc"<<std::endl;
    return 0;
}

 Opening the pipeline file is very simple, that is, to open the pipeline file we created, here we only need to open it in read-only mode. It is also necessary to judge the opening failure. After success, we will print the success of opening the pipeline file. Below we implement the code to start normal communication:

int main()
{
    //1.创建管道文件,我们今天只需要一次创建
    umask(0);   //这个设置并不影响系统的默认设置,只会影响当前进程
    int n = mkfifo(fifoname.c_str(),mode);
    if (n!=0)
    {
        std::cout<<errno<<" : "<<strerror(errno)<<std::endl;
        return 1;
    }
    std::cout<<"creat fifo file sucess"<<std::endl;
    // 2.让服务端直接开启管道文件
    int rfd = open(fifoname.c_str(),O_RDONLY);
    if (rfd<=0)
    {
        std::cout<<errno<<" : "<<strerror(errno)<<std::endl;
        return 2;
    }
    std::cout<<"open fifo success , begin ipc"<<std::endl;
    //3.正常通信
    char buffer[NUM];
    while (true)
    {
        buffer[0] = 0;
        ssize_t n = read(rfd,buffer,sizeof(buffer)-1);
        if (n>0)
        {
            buffer[n] = 0;
            std::cout<<"client# "<<buffer<<std::endl;
        }
        else if (n==0)
        {
            std::cout<<"client quit,me to"<<std::endl;
            break;
        }
        else 
        {
            std::cout<<errno<<" : "<<strerror(errno)<<std::endl;
            break;
        }

    }

    //关闭不要的fd
    close(rfd);
    return 0;
}

When we communicate normally, we need to read data from the buffer, so create a buffer first, set the buffer size as a macro and put it in the public header file, because we treat the read data as a string, so when calling the read function At this time, let sizeof-1 not read \0, and then initialize the buffer. For C language, if you put \0 directly in the 0 position, it will be considered as an empty string. Then we judge the return value of the function. If we have read the end of the data, we put a \0 in the last position, because we print the string according to the C language standard, and the C language string must end with \0. Because the server receives the message from the client, the name of the client is added in front of the print string. When the return value is equal to 0, it means that the client is no longer writing, and the client has already exited. If the client has exited, the server will also exit. Else means that the reading failed, and the reason for the failure can be printed. After the communication is over, we can close the pipeline file. Below we implement the client:

#include <iostream>
#include <cerrno>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include "comm.hpp"
#include <assert.h>
int main()
{
    //1.不需要创建管道文件,只需要打开对应的文件即可
    int wfd = open(fifoname.c_str(),O_WRONLY);
    if (wfd<0)
    {
        std::cerr<<errno<<":"<<strerror(errno)<<std::endl;
        return 1;
    }

    close(wfd);
    return 0;
}

The client does not need to create a pipeline file, because the server has already been created, so we can open it like the server. After opening, because our client wants to write messages, we open it in write-only mode. When the return value of the open function is less than 0 Print the error message directly, and then we implement the communication method:

int main()
{
    //1.不需要创建管道文件,只需要打开对应的文件即可
    int wfd = open(fifoname.c_str(),O_WRONLY);
    if (wfd<0)
    {
        std::cerr<<errno<<":"<<strerror(errno)<<std::endl;
        return 1;
    }

    //可以进行常规通信了
    char buffer[NUM];
    while (true)
    {
        std::cout<<"请输入你的消息# ";
        char* msg = fgets(buffer,sizeof(buffer),stdin);
        assert(msg);
        (void)msg;
        buffer[strlen(buffer)-1] = 0;
        if (strcasecmp(buffer,"quit")==0)
        {
            break;
        }
        ssize_t n = write(wfd,buffer,strlen(buffer));
        assert(n>=0);
        (void)n;
    }
    close(wfd);
    return 0;
}

 Similarly, we need to create a buffer first, then write the message directly in a loop, then put the written message into our buffer, and use a pointer to receive the string input by the customer. If it succeeds, the pointer will save the string We do not need to consider \0 when receiving strings, because fgets is a C language function that will automatically add \0, assert a null pointer, and then force the pointer to prevent it from appearing in release A case where a version variable is defined but not used. What does buffer(len-1) mean? This is actually because our client will press Enter after entering the string, and the carriage return will also be put into the buffer. In this way, the server will have an extra line of blank when printing, so we delete the carriage return. No problem anymore. Next, we let the client exit the program when it enters quit, because what we set on the server is that as long as the client exits the server, it will also exit. Then we write data to the file, and write the data in our buffer to the file. There is also no need to consider \0 here, because only the C language stipulates that \0 must be added after the string, and write is a system interface that does not consider \0. of. After completing this step, we assert the return value of the function, and not letting the return value be greater than or equal to 0 means that if it is an empty string or an error, it will not be written. Then also force the return value n just now. Finally, we can close the file, let's try to run it:

When running, we must open two windows. After creating two executable programs, we run the server program first. At this time, the program will be stuck, because the server has to wait for the client to open the same file, so we are in Run the client:

 In this way, we have completed the communication of the named pipe. There is another problem here. When we run the program once, there will be a pipe file. The next time we run the program, there will be an error that the file already exists, so we can cancel it directly after closing the file on the server side. Link this file:

 In this way, every time we run the program, we don't need to delete the pipeline file before running it.

Next, we will change this named pipe to every time the user enters a character, the server will output a corresponding character. You can understand that it is like what we do when we cast the mobile phone screen to the computer and operate the computer screen on the mobile phone.

Because our previous implementation needs to enter a carriage return every time the server will display the message. Now we need a function to write the content without entering a carriage return. Here we use the getch function. Since getch needs to use the ncurses library, we first Install this library:

(According to the method of the ncurses library, we have not solved it. If you have a small partner who has solved it, you can private message me~ The successful method is reminded in red below, and you can find it directly and use the system method)

 If you are an ordinary user, remember to add sudo right before. After installation, we add the header file of this library to the code just now:

 Because we are completing this operation on the client side, we only need to include this header file in the client.cc file. Then we modify the code in the client.cc file:

 Considering that you may not be familiar with the getch function, let's find out the documentation of this function:

 After the modification is complete, let's run the code below:

 Since the compilation failed because we did not introduce the client library, let's introduce it:

 After importing, we regenerate the executable file:

 After running, we found that the server could not print normally. In fact, the reason lies in the return value of the function. We just saw that the return value of the document is int type. As a result, we received it with char type, so there was an error. Let’s modify the code below. :

Since we don't know what the return value is, let's print the return value directly in the code:

 By printing we found that the return value is -1:

 Then we modify the code so that when the return value is -1, we continue to read characters, but during the running process, due to some errors in this method, it still fails, so we directly use the system method instead of the library method :

 Then we don't print the previous client name when receiving on the server side, and just print what we receive (remember to refresh the buffer here):

 Then we run it below:

 In this way, we have realized the function at the beginning. Through the study of the above pipelines, I believe that everyone can understand how to create named pipelines and use them. The system method we used above is also searched from the Internet. Please forgive me if there are any mistakes.

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.

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


Summarize

Named pipes can be used for any inter-process communication on the same host, and the essence of pipe communication is time data transmission through a buffer (memory) in the kernel, while the pipe file of a named pipe is just an identifier for allowing multiple processes to Access the same buffer, and the pipeline is a half-duplex communication, which is a one-way communication that can choose a direction. Named pipes and anonymous pipes have one thing in common, that is, they are essentially a buffer in the kernel. At the same time, add that the life cycle of the pipeline follows the process. It is essentially a buffer in the kernel. The named pipe file is just an identifier, which is used to allow multiple processes to find the same buffer. After deletion, the process that has opened the pipeline before can still communication.

Guess you like

Origin blog.csdn.net/Sxy_wspsby/article/details/130414958