当前位置: 首页 > 工具软件 > PhxRPC > 使用案例 >

开源框架PhxRPC(一)之streambuf

罗昊空
2023-12-01

提要

在讲解PhxRPC的之前,介绍streambuf是必要的,本篇会带大家走一遍 steambuf继承重写流程,分别实现两套用缓冲区的流操作读入写出。

一个是文件的读入写出,一个是socket网络数据的读入写出,他们之间的流操作方式会有差异,这个差异是用来提醒流操作和缓冲区半毛线关系都没有,当你实现了缓冲区。

如果不把它和流绑定到一起,那么很不幸,你就只能跟操作一个对象一样,调用streambuf函数进行处理了。

这些实现总共用到如下几个streambuf和流的函数:

setg() // 设置输入缓冲区
setp() // 设置输出缓冲区
pbase() // 输出缓冲区的起始地址
eback() // 输入缓冲区的起始地址
gptr() // 当前输入缓冲区数据指针
pptr() // 当前输出缓冲区数据指针
pbump() // 移动输出缓冲区的指针
gbump() // 移动输入缓冲区的指针
sputc() // 将一个字节写入输出缓冲区
sync() // 调用flush函数时会调用此函数,将输出缓冲区的数据写入目标对象,同时清空缓冲区
overflow() // 当输出缓冲区溢出时,调用此函数将输出缓冲区的数据写入目标对象中
underflow() // 当输入缓冲区空了时,调用此函数将数据源中的数据源读取到缓冲区缓冲区中
flush() // 调用sync函数
good() // 判断是否调用成功

FileStreamBuf.cc文件

#include <stdio.h>
#include <string.h>
#include <iostream>
#include <streambuf>

class FileBufferStreamBuf : public std::streambuf {
public:
    FileBufferStreamBuf(FILE* fp, size_t buf_size, size_t put_back_size):
        fp_(fp),
        buf_size_(buf_size),
        put_back_size_(put_back_size) {
        char* gbuf = new char[buf_size_ + put_back_size_];
        char* pbuf = new char[buf_size_];
        // 此设计第一次调用时就会触发underflow函数,将数据读入缓冲区中
        setg(gbuf, gbuf, gbuf); // 参数(输入缓冲区起始地址,输入缓冲区指针指向,输入缓冲区的结束地址)
        setp(pbuf, pbuf + buf_size_); // 参数(输出缓冲区的起始地址,输出缓冲区的结束地址)
    }

    // 调用flush()函数刷新的时候会调用此函数
    int sync() {
        int sent = 0;
        int total = pptr() - pbase();
        while(sent < total) {
            int ret = fwrite(pbase() + sent, 1, total - sent, fp_); // 写入文件操作
            if (ret > 0) {
                sent += ret;
            } else {
                return -1;
            }
        }
        pbump(-total); // 移动缓冲区指针到起始位置
        return 0;
    }

    // 当缓冲区溢出时,调用此函数将缓冲区的数据写入目标对象中
    int overflow(int c) {
        if (-1 == sync()) {
            return EOF;
        } else {
            sputc(static_cast<char>(c));
            return c;
        }
    }

    // 当缓冲区读取完毕时,调用此函数从数据源中调用数据到缓冲区中
    int underflow() {
        char* base = eback();
        // 处理put back区域
        size_t put_back_size = put_back_size_ > gptr() - eback() ? gptr() - eback() : put_back_size_;
        memmove(base, gptr() - put_back_size, put_back_size);
        base += put_back_size;

        int ret = fread(base, 1, buf_size_, fp_);
        if (ret > 0) {
            setg(eback(), base, base + ret);
            return *gptr();
        } else {
            return EOF;
        }
    }

    ~FileBufferStreamBuf() {
        delete [] pbase();
        delete [] eback();
    }

private:
    size_t buf_size_;
    size_t put_back_size_; // 用于putback操作回退字符
    FILE* fp_;
};

这里解决一下看完上面代码后可能会有的疑惑:

  1. 什么是put-back区域?put-back区域有什么作用?

    可能有人用过cincin.putback(ch),将输入流中的字符ch返回到输入流中。

    put-back区域的作用就在这里,它允许你将已经读取的数据再重新放回去,它是缓冲区的开头一部分多出来的连续空间,用于你进行回退操作。

  2. syncunderflowovewflow三个函数都必须写吗?

    并不是,需要根据你的需求,必要情况下你可能还会要重写其他的虚函数,PhxRPC只用到了这几个函数,所以这里只说了这几个。

  3. 可否将fp文件句柄的生成过程写在这个类里面呢?

    可以,不过不建议,你可以再封装一层类,这样可以各执其职。

TestFileSTreamBuf.cc

#include "FileStreamBuf.cc"
#include <stdio.h>
#include <iostream>
#include <istream>
#include <ostream>
#include <string>

using namespace std;
int main() {
    FILE* write_fd = fopen("test_write.txt", "w+");
    FILE* read_fd = fopen("test_read.txt", "r+");
    FileBufferStreamBuf write_file_buffer(write_fd, 16, 10);
    FileBufferStreamBuf read_file_buffer(read_fd, 16, 10);
    // 将流与缓冲区绑定
    ostream os(&write_file_buffer);
    istream is(&read_file_buffer);

    os << "const double eps = 1e-8;" << endl;
    os << "int n;" << endl;
    os << "struct Point {" << endl;
    os << "    double x, y;" << endl;
    os << "    Point() {}" << endl;
    os << "    Point(double x, double y): x(x), y(y) {}" << endl;
    os << "    Point operator + (const Point &p) const {" << endl;
    os << "        return Point(x + p.x, y + p.y);" << endl;
    os << "    }" << endl;
    os << "    Point operator - (const Point &p) const {" << endl;
    os << "        return Point(x - p.x, y - p.y);" << endl;
    os << "    }" << endl;
    os << "}" << endl;

    // 将数据写入目标对象,同时刷新输出缓冲区,只影响输出缓冲区
    if (!os.flush().good()) {
        cout << "os Error!" << endl;
    }

    // 以下代码很挫,只简单说一下实现
    // 就是读取一个字符,就回退两个字符,再重新读取三个字符
    // 这样做的原因是为了验证put-back区域是否真实有效
    string in_x;
    char ch1, ch2, ch;
    while(is.get(ch)) {
        if (ch == 's') {
            cout << ch;
            ch1 = ch;
            is.get(ch2);
            cout << ch2;
        } else {
            cout << ch;
            is.putback(ch);
            is.putback(ch2);
            is.putback(ch1);
            is.get(ch);
            is.get(ch1);
            is.get(ch2);
        }
    }
    fclose(write_fd);
    fclose(read_fd);
}

SocketStreamBuf.cc文件

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <iostream>
#include <streambuf>

class SocketBufferStreamBuf : public std::streambuf {
public:
    SocketBufferStreamBuf(int socketfd, size_t buf_size):
        socketfd_(socketfd),
        buf_size_(buf_size) {
        char* gbuf = new char[buf_size_];
        char* pbuf = new char[buf_size_];

        setg(gbuf, gbuf, gbuf);
        setp(pbuf, pbuf + buf_size_);
    }

    // 调用flush()函数刷新的时候会调用此函数
    int sync() {
        int sent = 0;
        int total = pptr() - pbase();
        while(sent < total) {

            int ret = send(socketfd_, pbase() + sent, total - sent, 0);
            if (ret > 0) {
                sent += ret;
            } else {
                return -1;
            }
        }
        pbump(-sent);
        return 0;
    }

    // 当缓冲区溢出时,调用此函数将缓冲区的数据写入目标对象中
    int overflow(int c) {
        if (-1 == sync()) {
            return EOF;
        } else {
            sputc(static_cast<char>(c));
            return c;
        }
    }

    // 当缓冲区读取完毕时,调用此函数从数据源中调用数据到缓冲区中
    int underflow() {
        int ret = recv(socketfd_, eback(), buf_size_, 0);
        if (ret > 0) {
            setg(eback(), eback(), eback() + ret);
            return *gptr();
        } else {
            return EOF;
        }
    }
    ~SocketBufferStreamBuf() {
        delete[] eback();
        delete[] pbase();
    }
private:
    size_t buf_size_;
    int socketfd_;
};

FileStreamBuf.cc文件的内容

TestSocketStreamBufClient.cc文件

#include "SocketStreamBuf.cc"
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <string.h>
#include <iostream>
#include <istream>
#include <ostream>
#include <string>

using namespace std;

int main() {
    int client_socketfd = socket(AF_INET, SOCK_STREAM, 0);
    int flags = 1;
    if (setsockopt(client_socketfd, SOL_SOCKET, SO_REUSEADDR, (char*)&flags, sizeof(flags)) < 0) {
        cout << "client reuseaddr failed!" << endl;
    }

    struct sockaddr_in client_addr;
    memset(&client_addr, 0, sizeof(client_addr));
    client_addr.sin_family = AF_INET;
    client_addr.sin_port = htons(7777);
    client_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

    int res = connect(client_socketfd, (struct sockaddr*)&client_addr, sizeof(client_addr));
    if (res == -1) {
        cout << "client connect failed!" << endl;
        return -1;
    }

    SocketBufferStreamBuf SocketSb(client_socketfd, 16);

    iostream ios(&SocketSb);

    ios << "start" << endl;
    ios << "client: tmqtan hello!" << endl;
    ios << "client: what?" << endl;
    ios << "client: WTF" << endl;
    ios << "end" << endl;

    if (!ios.flush().good()) {
        cout << "client send error" << endl;
        return -1;
    }

    string temp;
    while(getline(ios, temp)) {
        cout << temp << endl;
        if (strcmp(temp.c_str(), "end") == 0) {
            break;
        }
    }
}

TestSocketStreamBufServer.cc文件

#include "SocketStreamBuf.cc"
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <istream>
#include <ostream>
#include <string>

using namespace std;

int main() {
    // 服务器设置
    int server_socketfd = socket(AF_INET, SOCK_STREAM, 0);
    int flags = 1;
    if (setsockopt(server_socketfd, SOL_SOCKET, SO_REUSEADDR, (char*)&flags, sizeof(flags)) < 0) {
        cout << "server reuseaddr failed!" << endl;
    }

    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(7777);
    server_addr.sin_addr.s_addr = INADDR_ANY;
    if (bind(server_socketfd, (struct sockaddr*) &server_addr, sizeof(server_addr)) < 0) {
        cout << "bind error!" << endl;
        return -1;
    }
    if (listen(server_socketfd, 1024) < 0) {
        cout << "listen error!" << endl;
        return -2;
    }

    int connect_fd;
    while(1) {
        if((connect_fd = accept(server_socketfd, (struct sockaddr*) NULL, NULL)) == -1) {
            continue;
        }
        break;
    }
    SocketBufferStreamBuf socketSb(connect_fd, 16);

    iostream ios(&socketSb);
    ios << "server start:" << endl;
    ios << "server: tmqtan hello!" << endl;
    ios << "server: what?" << endl;
    ios << "server: WTF" << endl;
    ios << "server: Test Socket" << endl;
    ios << "end" << endl;

    if (!ios.flush().good()) {
        cout << "socket write error!" << endl;
        return -1;
    }

    string temp;
    cout << "server recv:" << endl;
    while(getline(ios, temp)) {
        cout << temp << endl;
        if (strcmp(temp.c_str(), "end") == 0) {
            break;
        }
    }

    return 0;
}

可以注意到,FileBufferStreamBufSocketBufferStreamBuf这两个类分别用了两个不同的流进行绑定FileBufferStreamBuf 用的是ostreamistream,而SocketBufferStreamBuf用的是iostream。可以稍微体验一下两者处理的不同之处。

当不同的流绑定缓冲区时可能相应的操作会有一些变化。

**

如果想了解更多关于streambuf的知识

http://www.cplusplus.com/reference/streambuf

 类似资料: