• 欢迎浏览“String me = Creater\忠实的资深Linux玩家;”,请文明浏览,理性发言,有侵犯你的权益请邮件我(creater@vip.qq.com).
  • 把任何的失败都当作一次尝试,不要自卑;把所有的成功都想成是一种幸运,不要自傲。
  •    5年前 (2013-06-11)  C++ |   6 条评论  20 
    文章评分 0 次,平均分 0.0

    一、如何使用iostream
    TCP连接是面向流的连接,这一点与iostream 要表达的概念非常吻合。在使用阻塞Socket处理数据时,如果能借用iostream已经具备的强大的字符串流处理功能,是不是可以简化我们某些地方的程序设计呢?比如说需要在服务端和客户端之间某种类的对象,我们可以重载ostream与之的<<操作符和istream与之的>>操作符,这样使用操作符直观、方便地序列化和反序列化对象了。从某种意义上讲,iostream提供了一种简单的对象序列化的解决方案。
    众所周知,cin是istream类的一个全局变量,cout是ostream类的一个全局变量。istream、ostream默认情况下对应的是标准输入、输出设备。而iostream类的构造函数却明确需要一个streambuf(即basic_streambuf >)类的指针,这意味着iostream需要通过streambuf来定义特定的输入输出行为。
    iostream是ios的子类,ios还有两个与此相关的方法,一个是rdbuf(),返回streambuf指针,一个是rdbuf(streambuf*),重新设置streambuf。通过两个函数,我们往往可以实现一些非常巧妙的行为。
    二、其实iostream是通过streambuf来读写数据
    我们来看看streambuf类。这个类其实更像是一个接口,虽然它并没有定义成抽象类,但它本身确实是什么也做不了,这就意味它是在等着我们去继承它。与此同时,它本身也实现了部分接口提供给子类来使用。
    streambuf维持了两个buffer,即input buffer和output buffer,streambuf就是使用这两个buffer进行数据的收发。streambuf本身并不负责内存的申请和回收,所以我们要做的是在构造时申请两块一定大小的内存交给streambuf,然后在析构时回收之。

    /**
     * 用于tcp_stream的streambuf
     */
    class tcp_streambuf : public std::streambuf {
    public:
        tcp_streambuf(SOCKET socket, int buf_size) : _socket(socket), _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);
        }
        ~tcp_streambuf() {
            delete[] eback();
            delete[] pbase();
        }
    }

    每个buffer都有三个点,即首地址,当前指针,尾部地址。input buffer的三个点分别由eback(),gptr(),egptr()获得,output buffer的三个点分别由pbase(),pptr(),epptr()获得。

    • eback, a pointer to the beginning of the buffer.
    • gptr, a pointer to the next element to read.
    • egptr, a pointer just past the end of the buffer.

    Similarly, an output buffer is characterized by:

    • pbase, a pointer to the beginning of the buffer.
    • pptr, a pointer to the next element to write.
    • epptr, a pointer just past the end of the buffer.

    setg、setp方法分别用来设置两个buffer的三个点。
    streambuf的关键功能由通过underflow,overflow,sync三个virtual方法来实现,在streambuf类中这三个方法什么也没做,这正是我们需要重写的三个方法。当input buffer中数据读完了,iostream会调用streambuf的underflow来输入数据,当output buffer被填满时,iostream会调用streambuf的overflow来输出数据,当要输出endl、ends或者调用flush()方法时,iostream会调用streambuf的sync方法。
    underflow方法从输入流中提取一定量的数据到input buffer中,并返回当前位置的字符值,但并不移动当前指针的位置。如果对方关闭连接或者其他错误,返回EOF。

    /**
         * 输入缓冲区为空时被调用
         */
        virtual int_type underflow() {
            int ret = recv(_socket, eback(), _buf_size, 0);
            if (ret > 0) {
                setg(eback(), eback(), eback() + ret);
                return traits_type::to_int_type(*gptr());
            } else {
                // ret == 0 || ret == SOCKET_ERROR
                return traits_type::eof();
            }
        }

    sync方法将output buffer中的数据全部输出出去。

    /**
         * 同步输出缓冲区中的数据
         */
        virtual int sync() {
            int all = pptr() - pbase();
            int sent = 0;
            while (sent < all) {             int ret = send(_socket, pbase() + sent, all - sent, 0);             if (ret > 0) {
                    sent += ret;
                } else if (ret == SOCKET_ERROR) {
                    return -1;
                }
            }
            return 0;
        }

    overflow方法先将output buffer输出出去,再把新的字符写入output buffer。按MSDN的规定,如果这个新的字符_Meta是EOF,则返回traits_type::not_eof(_Meta)。

    /**
         * 当输出缓冲区溢出时被调用
         */
        virtual int_type over_flow(int_type _Meta = traits_type::eof()) {
            if (sync() == -1) {
                return traits_type::eof();
            } else {
                if (!traits_type::eq_int_type(_Meta, traits_type::eof())) {
                    sputc(traits_type::to_char_type(_Meta));
                }
                return traits_type::not_eof(_Meta);
            }
        }

    三、最后一步
    有了这样的tcp_streambuf,我们就可以通过它的对象指针来创建iostream了。
    tcp_streambuf sb(socket)
    iostream(&sb);
    但是,有良好的面向对象思维的C++程序员可能不满足这样的写法。不如再写个RAII类来封装streambuf,而继承iostream则是一个不错的想法。

    class tcp_stream : public std::iostream {
    public:
        tcp_stream(int socket, int buf_size = 1024);
        ~tcp_stream();
    };
    tcp_stream::tcp_stream(int socket, int buf_size /*= 1024*/) : std::iostream(new tcp_streambuf(socket, buf_size)) {
    }
    tcp_stream::~tcp_stream() {
        delete rdbuf();
    }

    好了,下面我们使用它来写一个echo client。

    #include "stdafx.h"
    #include "tcp_stream.h"
    void main() {
        using namespace std;
        using namespace boost::asio::ip;
        boost::asio::io_service io_service;
        tcp::socket socket(io_service);
        try {
            socket.connect(tcp::endpoint(address_v4::loopback(), 1127));
            tcp_stream ts(socket.native());
            while (!ts.eof()) {
                string request, response;
                cin >> request;
                ts << request << endl;             ts >> response;
                if (!response.empty()) {
                    cout << response << endl;
                }
            }
        } catch (boost::system::system_error& err) {
            cout << err.what() << endl;
        }
    }

    四、值得注意的几点
    1.iostream屏蔽了streambuf可能抛出的所有异常,所以不要指望在streambuf里运用异常机制,我这里通过EOF来表示出错。
    2.因为TCP Socket的出错或者对方连接关闭是无法提前预知的,这不像文件结尾可以直接检测。所以当前读入数据时出错后eof()才会返回true。
    3.输出数据时如果不输出endl或ends,或者调用flush()方法,数据会暂存在streambuf的output buffer里,所以如果需要实时发送数据需要注意这一点。
    4.网上还有一篇文章《用streambuf简单封装socket》,感谢这篇文章的作者给予我的启发,不过他的代码里有个BUG就是出错后往往会导致死循环,因为他的underflow返回的EOF值不正确,这个BUG在这里得到了更正。

     

    除特别注明外,本站所有文章均为String me = "Creater\忠实的资深Linux玩家";原创,转载请注明出处来自http://unix8.net/home.php/1473.html

    关于

    发表评论

    暂无评论

    切换注册

    登录

    忘记密码 ?

    切换登录

    注册

    扫一扫二维码分享