使用文件描述符(File Descriptor)创建 C++ IO 流

图片来源:《灵笼》特别篇截图

方案一:继承 std::streambuf

详细解释查看 《C++标准库:第2版》 15.13章节

输出流(没有缓冲区)

在不考虑缓冲区的情况下,继承 std::streambuf 并重写两个函数 :

  • int_type overflow (int_type c) :单字符输出
  • std::streamsize xsputn (const char* s, std::streamsize num) :多字符输出,非必要,不重写的话会多次调用 overflow 替代
#include <iostream>
#include <streambuf>
#include <cstdio>

// for write():
#ifdef _MSC_VER
# include <io.h>
#else
# include <unistd.h>
#endif

class fdoutbuf : public std::streambuf {
  protected:
    int fd;    // file descriptor
  public:
    // constructor
    fdoutbuf (int _fd) : fd(_fd) {
    }
  protected:
    // write one character
    virtual int_type overflow (int_type c) {
        if (c != EOF) {
            char z = c;
            if (write (fd, &z, 1) != 1) {
                return EOF;
            }
        }
        return c;
    }
    // write multiple characters
    virtual
    std::streamsize xsputn (const char* s,
                            std::streamsize num) {
        return write(fd,s,num);
    }
};

class fdostream : public std::ostream {
  protected:
    fdoutbuf buf;
  public:
    fdostream (int fd) : std::ostream(0), buf(fd) {
        rdbuf(&buf);
    }
};

输出流(有缓冲区)

考虑缓冲区的话,需要:

  • 创建缓冲区,调用 void setp( char_type* pbeg, char_type* pend ) 初始化
  • 管理缓冲区的三个指针:pbase(putbase,缓冲区开始位置)、pptr(put pointer,当前写入位置)、epptr(end put pointer,指向缓冲区末尾的后一个位置)
  • 缓冲区满或者析构时主动缓冲区数据刷出、实现 int sync ()type overflow (int_type c)
#include <cstdio>
#include <streambuf>

// for write():
#ifdef _MSC_VER
# include <io.h>
#else
# include <unistd.h>
#endif

class fdoutbuf : public std::streambuf {
  protected:
    static const int bufferSize = 1024;   // size of data buffer
    char buffer[bufferSize];            // data buffer
    int fd; // file descriptor

  public:
    /* constructor
     * - initialize data buffer
     * - one character less to let the bufferSizeth character
     *    cause a call of overflow()
     */
    fdoutbuf(int _fd) : fd(_fd) {
        setp (buffer, buffer+(bufferSize-1));
    }

    /* destructor
     * - flush data buffer
     */
    virtual ~fdoutbuf() {
        sync();
    }

  protected:
    // flush the characters in the buffer
    int flushBuffer () {
        int num = pptr()-pbase();
        if (write (fd, buffer, num) != num) {
            return EOF;
        }
        pbump (-num);    // reset put pointer accordingly
        return num;
    }

    /* buffer full
     * - write c and all previous characters
     */
    virtual int_type overflow (int_type c) {
        if (c != EOF) {
            // insert character into the buffer
            *pptr() = c;
            pbump(1);
        }
        // flush the buffer
        if (flushBuffer() == EOF) {
            // ERROR
            return EOF;
        }
        return c;
    }

    /* synchronize data with file/destination
     * - flush the data in the buffer
     */
    virtual int sync () {
        if (flushBuffer() == EOF) {
            // ERROR
            return -1;
        }
        return 0;
    }
};

class fdostream : public std::ostream {
  protected:
    fdoutbuf buf;
  public:
    fdostream (int fd) : std::ostream(0), buf(fd) {
        rdbuf(&buf);
    }
};

输入流

输入流需要考虑支持回退(basic_istream& unget()),重写 virtual int_type underflow () 函数用以从文件描述符中读取数据到缓冲区,缓冲区前 4 字节保存可回退数据


#include <cstdio>
#include <cstring>
#include <streambuf>

// for read():
#ifdef _MSC_VER
# include <io.h>
#else
# include <unistd.h>
#endif

class inbuf : public std::streambuf {
  protected:
    /* data buffer:
     * - at most, four characters in putback area plus
     * - at most, six characters in ordinary read buffer
     */
    static const int bufferSize = 10;    // size of the data buffer
    char buffer[bufferSize];             // data buffer

  public:
    /* constructor
     * - initialize empty data buffer
     * - no putback area
     * => force underflow()
     */
    inbuf() {
        setg (buffer+4,     // beginning of putback area
              buffer+4,     // read position
              buffer+4);    // end position
    }

  protected:
    // insert new characters into the buffer
    virtual int_type underflow () {

        // is read position before end of buffer?
        if (gptr() < egptr()) {
            return traits_type::to_int_type(*gptr());
        }

        /* process size of putback area
         * - use number of characters read
         * - but at most four
         */
        int numPutback;
        numPutback = gptr() - eback();
        if (numPutback > 4) {
            numPutback = 4;
        }

        /* copy up to four characters previously read into
         * the putback buffer (area of first four characters)
         */
        std::memmove (buffer+(4-numPutback), gptr()-numPutback,
                      numPutback);

        // read new characters
        int num;
        num = read (0, buffer+4, bufferSize-4);
        if (num <= 0) {
            // ERROR or EOF
            return EOF;
        }

        // reset buffer pointers
        setg (buffer+(4-numPutback),   // beginning of putback area
              buffer+4,                // read position
              buffer+4+num);           // end of buffer

        // return next character
        return traits_type::to_int_type(*gptr());
    }
};

方案二:使用 __gnu_cxx::stdio_filebuf

stdio_filebuf 支持使用文件描述符或者C文件指针(std::__c_file *FILE *)构造,部分构造函数:

stdio_filebuf (int __fd, std::ios_base::openmode __mode, size_t __size=static_cast< size_t >(BUFSIZ))
stdio_filebuf (std::__c_file *__f, std::ios_base::openmode __mode, size_t __size=static_cast< size_t >(BUFSIZ))

使用示例:

#include <iostream>
#include <unistd.h>
#include <ext/stdio_filebuf.h>
    
int main(int, char**) {
    int fd = STDOUT_FILENO;
    __gnu_cxx::stdio_filebuf<char> buf(fd, std::ios_base::out);
    std::ostream out(&buf);
    out << "test"
    return 0;
}

注意:对于文件描述符,stdio_filebuf 在析构时会主动调用 close ;但对于 FILE * 则不会主动关闭,需要手动调用 fclose

方案三:使用 boost::iostreams::file_descriptor_source 和 boost::iostreams::file_descriptor_sink

boost 的 file_descriptor_sourcefile_descriptor_sink 可以使用文件描述符来构造,继而构造出 boost::iostreams::stream ,部分构造函数:

enum file_descriptor_flags {
    never_close_handle,
    close_handle
};

file_descriptor_source( int fd, file_descriptor_flags );
file_descriptor_sink( int fd, file_descriptor_flags );

其中 file_descriptor_source 只能从文件描述符读取数据,file_descriptor_sink 则只能写入数据,file_descriptor_flags 用以控制析构时是否自动关闭文件描述符

#include <boost/iostreams/device/file_descriptor.hpp>
#include <boost/iostreams/stream.hpp>

namespace io = boost::iostreams;

int main(int, char**) {
    int fd = STDOUT_FILENO;
    io::file_descriptor_sink fds(fd,io::never_close_handle);
    io::stream<io::file_descriptor_sink> out(fds); 
    out << "test";
    return 0;
}

参考:

《C++标准库:第2版》

__gnu_cxx::stdio_filebuf< _CharT, _Traits >(3) —— man page

File Descriptors —— boost

知识共享许可协议
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注