Linux系统之----命名管道模拟实现客户端、服务器

一、Makefile的编写

这里的Makefile文件的编写与前面不太一样,我们先看正确的代码:

复制代码
.PHONY:all
all:server client

server:server.cpp
	g++ -o $@ $^ -std=c++11

client:client.cpp	
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -f client
	rm -f server

此处的all不可以省略,如果省略了你之后在make时只会编译server.cpp文件,client.cpp不会编译,不信可以试一下~

讲的专业一点就是,在这个 Makefile 中,all 是一个 PHONY 目标,它依赖于 serverclient 两个目标。这意味着当你运行 make 命令时,如果没有指定其他目标,make 将默认执行 all 目标,从而编译 server.cppclient.cpp 文件。

二、项目本体的编写

由于客户端和服务端都是通过命名管道来写的,所以二者基本逻辑相同,故项目组成由三部分:

client.cpp server.cpp NamePipe.hpp,但是为了简化NamePipe.hpp中的代码,我们在额外设计一个common.hpp来辅助减轻一下其代码量,主要用于存放头文件以及一些全局变量啥的~

三、NamedPipe.hpp的编写

在此文件中,我们要构造出命名管道的模样,即命名管道的模拟实现,包括类的初始化,析构以及各种库函数的实现。我们主要分享一下方法函数实现部分内容~

方法函数主要包括管道的创建,读,写,移除,关闭,以及只读打开还有只写打开

复制代码
#pragma once
#include "common.hpp"
const int defaultfd = -1; // 检查错误用的
class NamedPipe
{
public:
    NamedPipe(const string &name)
        : _name(name), _fd(defaultfd)
    {
    }
    ~NamedPipe()
    {
    }
    bool Create()
    {
        // 1.创建管道文件
        int n = mkfifo(fifoname.c_str(), mode);
        if (n == 0)
        {
            cout << "mkfifo success" << endl;
        }
        else
        {
            perror("mkfifo");
            return false;
        }
        return true;
    }
    void Close()
    {
        if (_fd == defaultfd) // 这里如果open那里没打开文件,_fd就为-1,这里直接返回
        {
            return;
        }
        else
            close(_fd);
    }
    bool OpenForRead()
    {
        _fd = open(fifoname.c_str(), O_RDONLY);
        if (_fd < 0)
        {
            // 打开失败
            perror("open");
            return false;
        }
        cout << "open file success" << endl;
        return true;
    }
    bool OpenForWrite()
    {
        _fd = open(fifoname.c_str(), O_WRONLY);
        if (_fd < 0)
        {
            perror("open");
            return false;
        }
        return true;
    }

    // 输入参数:const &
    // 输出参数:*
    // 输入输出参数:&

    bool Read(string *out)
    {
        char buffer[SIZE]={0};
        ssize_t num = read(_fd,buffer,sizeof(buffer)-1);
        if(num>0)
        {
            //读取成功
            buffer[num]=0;
            *out =buffer;
        }
        else
        {
            return false;
        }
        return true;
    }
    void Write(const string &in)
    {
        write(_fd,in.c_str(),in.size());
    }
    void Remove()
    {
        int m=unlink(fifoname.c_str());
        (void)m;
    }
private:
    string _name; // path+name
    int _fd;      // who
};

我们还要定义一些变量,不如再创一个文件,就叫commom.hpp吧

复制代码
#ifndef __COMMON_HPP__
#define __COMMON_HPP__

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

using namespace std;
const string fifoname="fifo";
mode_t mode=0666;
#define SIZE 128

#endif

四、client.cc和server.cc的编写

这是client.cc

复制代码
#include"NamedPipe.hpp"

int main()
{
    NamedPipe namedpipe(fifoname);
    namedpipe.OpenForWrite();
    while(true)
    {
        cout<<"Please Enter# ";
        string line;
        getline(cin,line);
        namedpipe.Write(line);
    }
    namedpipe.Close();
    return 0;
}

这是server.cc

复制代码
#include "NamedPipe.hpp"

int main()
{
    NamedPipe namedpipe(fifoname);
    namedpipe.Create();
    namedpipe.OpenForRead();
    // 2. 打开文件,就和普通文件没有区别
    // 命名管道操作特点: 打开一端,在另一端没打开的时候,open会阻塞

    // 3. 通信
    string message;
    while(true)
    {
        bool res=namedpipe.Read(&message);
        if(!res)
            break;      
        cout<<"client say@"<<message<<endl;
    }
    // 4. 归还资源
    cout<<"对方已将你删除!!!"<<endl;
    namedpipe.Close();
    namedpipe.Remove();
    return 0;
}

五、运行结果

结果解释:client端发送的消息可以在server端接收到,在使用时,先连接./server,再连接./client

(ps:作者小趴菜不是舔gou哈~)

好了,这个小项目基本到这了,感兴趣的可以给尝试改造为双端的,本文实现的仅仅是单端通信~