博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

asio streambuf 【重点】

Posted on 2015-10-19 17:55  bw_0927  阅读(2026)  评论(0编辑  收藏  举报

http://www.godebug.org/index.php/archives/105/

https://mmoaay.gitbooks.io/boost-asio-cpp-network-programming-chinese/content/Chapter6.html

http://www.cnblogs.com/my_life/articles/4792188.html

 

https://www.boost.org/doc/libs/1_59_0/doc/html/boost_asio/overview/core/buffers.html     mutable_buffer, const_buffer, streambuf的由来和使用

 

代码取自\include\boost\asio\basic_streambuf.hpp 

/// Move the start of the put area by the specified number of characters.
void commit(std::size_t n)                //数据填充之后调用commit,移动写指针
{
  if (pptr() + n > epptr())
    n = epptr() - pptr();
  pbump(static_cast<int>(n));        //Increase put pointer(pptr)
}

/// Move the start of the get area by the specified number of characters.
void consume(std::size_t n)           //数据读取之后调用consume,移动读指针
{
  if (gptr() + n > pptr())
    n = pptr() - gptr();
  gbump(static_cast<int>(n));    //Increase get pointer(gptr)
}

 

/// Get a list of buffers that represents the get area.
const_buffers_type data() const
{
  return boost::asio::buffer(boost::asio::const_buffer(gptr(),
    (pptr() - gptr()) * sizeof(char_type)));
}

/// Get a list of buffers that represents the put area, with the given size.
mutable_buffers_type prepare(std::size_t size)
{
  reserve(size);
  return boost::asio::buffer(boost::asio::mutable_buffer(
    pptr(), size * sizeof(char_type)));
}

 

data()和prepare()都是返回的子buffer,需要自行处理读写指针

 

=================================================

直接这么写是不行的:

boost::asio::write(sock,streambuf);
boost::asio::read(sock,streambuf);

这样会一直阻塞在函数里面不会返回,除非发生错误或者连接中断。异步的版本也不行,一直不会调用回调函数。这就郁闷了啊,到底该怎样写入确定字节数的数据,写完就返回呢?

这时候就轮到asio::transfer_exactly上场了,给read和write加个参数,指定要写入的字节数就可以了,于是这样boost::serialization就和asio完美结合起来了!!示例如下:

boost::asio::write(sock,streambuf,boost::asio::transfer_exactly(streambuf.size()));
boost::asio::read(sock,streambuf,boost::asio::transfer_exactly(streambuf.size()));

异步版本也一样

或者使用

async_read_until()函数,指定一个结束条件

 

http://stackoverflow.com/questions/13244287/boostasiostreambuf-shrink-to-fit

streambuf一旦被撑大了,就不会再被缩小。

The boost::asio::streambuf size() will keep on increasing until consume() is called.
Even after consume() is called, the memory used by the underlying buffer will never be released.

For example: the following code first created a streambuf without specifying the max_size. Then it dump 14Mb data into the streambuf. Then it consume all those 14MB data. At point 2000, the streambuf.size() is 0, but "top" shows the process still take 14MB ram.

 

#include <boost/asio.hpp>
#include <iostream>
#include <string>

int main()
{
    {
        boost::asio::streambuf b;
        std::ostream os(&b);

        for(int i= 0; i<1000000; ++i)
        {
            os << "Hello, World!\n";
        }
        std::cout<<"point 1000"<<std::endl;
        std::cout<<"size="<<b.size()<<std::endl;
        // at this point, the streambuf's size is close to 14MB.


        b.consume(b.size());
        std::cout<<"point 2000"<<std::endl;
        std::cout<<"size="<<b.size()<<std::endl;
        // at this point, the streambuf.size() is 0
        // but the streambuf's underlying buffer, which is a vector I assume,
        // still hold that 14MB space.
        // "top" shows the process size is 14M


    }

    // at this point, "top" showed the process size shrinks to almost zero
    std::cout<<"point 4000"<<std::endl;
    return 0;
}

  


 

 

Automatically resizable buffer class based on std::streambuf.

Typedef for the typical usage of basic_streambuf.

typedef basic_streambuf streambuf;

const_buffers_type

The type used to represent the input sequence as a list of buffers.

输入序列(>>),从里面读出东西,内容是const

mutable_buffers_type

The type used to represent the output sequence as a list of buffers.

输出序列(<<), 向里面写东西,所以是mutable





The basic_streambuf class is derived from std::streambuf to associate the streambuf's input and output sequences with one or more character arrays.

Characters written to the output sequence of a basic_streambuf object are appended to the input sequence of the same object.   向输出序列(<<,向里面写入内容)写入的字符会被追加到输入序列(>>,从中读取内容)。 因为一旦我向里面写入了东西,我就能再从里面把数据读出来。

The constructor for basic_streambuf accepts a size_t argument specifying the maximum of the sum of the sizes of the input sequence and output sequence.

 

basic_streambuf::basic_streambuf 构造函数

Constructs a streambuf with the specified maximum size. The initial size of the streambuf's input sequence is 0,因为你还没向里面写东西.

basic_streambuf::prepare:  Get a list of buffers that represents the output sequence, with the given size.

basic_streambuf::size: Get the size of the input sequence.   获得已写入的数据的大小。

commit: Move characters from the output sequence to the input sequence向里面写入数据后,把数据提交,使得后续的读操作能读到数据.   把输出序列(<<, 读进去的内容)里的内容移到输入队列(>>, 准备输出的内容)里。

consume: 从中读出数据后,把数据消耗掉,Remove characters from the input sequence. 

data: Get a list of buffers that represents the input sequence.


Writing directly from an streambuf to a socket:
Examples
The thing to remember is that the basic_[io]stream classes handle formatting, nothing else. In particular, they break up on whitespace. The actual reading, writing, and storing of data is handled by the basic_streambuf family.

 

boost::asio::streambuf b;    //streambuf用来读写存储数据
std::ostream os(&b);           //ostream用来格式化数据
os << "Hello, World!\n";

// try sending some data in input sequence
size_t n = sock.send(b.data());

//内容被发出去了,如果不想被循环发生同一份内容,就得把输入序列里的该内容消化删除掉 b.consume(n); // sent data is removed from input sequence

Reading from a socket directly into a streambuf:

boost::asio::streambuf b;    //用来读写存储数据

// reserve 512 bytes in output sequence
boost::asio::streambuf::mutable_buffers_type bufs = b.prepare(512);

size_t n = sock.receive(bufs);

// received data is "committed" from output sequence to input sequence
b.commit(n);

std::istream is(&b);   //istream用来格式化
std::string s;
is >> s;


asio::streambuf则是提供了一个流类型的buffer,它自身是能申请内存的。它的好处是可以通过stl的stream相关函数实现缓冲区操作,处理起来更加方便。

异步IO操作时往往会申请动态内存,使用完后就释放掉;在IO密集型的场景中,频繁的申请释放内存对性能会有较大影响。为了避免这个问题,asio提供了一个内存池式的模型 asio_handler_allocate 和 asio_handler_deallocate 来复用内存。

例子我就不写了,可以参看boost官方文档示例,或者网上的这篇文章

就我个人而言,并不赞成在项目的前期就使用上这个allocator,毕竟这样带来了很大的代码复杂度。而是作为一个性能优化点,在后期性能优化的时候再试试用它有没有效果。并且内存池的也有很多不同的方案,google的google-perftools也值得一试。

 

 

https://mmoaay.gitbooks.io/boost-asio-cpp-network-programming-chinese/content/Chapter6.html

streambuf类

  • prepare(n):这个方法返回一个buffer,用来容纳连续的n个字符。它可以用来读取或者写入。方法返回的结果可以在任何Boost.Asio处理read/write的自由函数中使用,而不仅仅是那些用来处理streambuf对象的方法。
    • streambuf([max_size,][allocator]):这个方法构造了一个streambuf对象。你可以选择指定一个最大的buffer大小和一个分配器,分配器用来在需要的时候分配/释放内存。
    • prepare(n):这个方法返回一个子buffer,用来容纳连续的n个字符。它可以用来读取或者写入。方法返回的结果可以在任何Boost.Asio处理read/write的自由函数中使用,而不仅仅是那些用来处理streambuf对象的方法。
    • data():这个方法以连续的字符串形式返回整个buffer然后用来写入。方法返回的结果可以在任何Boost.Asio处理写入的自由函数中使用,而不仅仅是那些用来处理streambuf对象的方法。
    • comsume(n):在这个方法中,数据从输入队列中被移除(从read操作).相当于移动读指针。
    • commit(n):在这个方法中,数据从输出队列中被移除(从write操作)然后加入到输入队列中(为read操作准备)。相当于移动写指针
    • size():这个方法以字节为单位返回整个streambuf对象的大小。
    • max_size():这个方法返回最多能保存的字节数。

大部分时间你会把streambuf以参数的方式传递给read/write自由函数,就像下面的代码片段展示的一样:

read_until(sock, buf, "\n"); // 读取到buf中,显示指明条件,否则会读爆内存
write(sock, buf); // 从buf写入

如果你像之前的代码片段展示的一样整个buffer都传递到一个自由函数中,方法会保证把buffer的输入输出指针指向的位置进行增加。也就是说,如果有数据需要读,你就能读到它。

比如:

read_until(sock, buf, '\n');     //完整buffer,buf整体作为参数使用
std::cout << &buf << std::endl;

上述代码会把你刚从socket写入的东西输出。而下面的代码不会输出任何东西:

read(sock, buf.prepare(16), transfer_exactly(16) );    //buf中16个字节长的子buffer作为参数,需要手动修改读写指针

//我是把数据读进去了,但读指针并没有前进,所以从buf里读不到数据
std::cout << &buf << std::endl; 

字节被读取了,但是输入指针没有移动,你需要自己移动它,就像下面的代码片段所展示的:

read(sock, buf.prepare(16), transfer_exactly(16) );   
buf.commit(16);
std::cout << &buf << std::endl;

commit(n)
:在这个方法中,数据从输出队列中被移除(从write操作)然后加入到输入队列中(为read操作准备)。 所以上面的代码如果没有调用commit的话,数据是被写到了输出队列里了,但写指针没动、所以对它进行读的时候,读不到东西出来。
cout << &buf;    //cout是输出流,代表标准输出; buf是输入流。       如果没有调用commit,输入流buf的输入指针没动,所以标准输出为空。
相当于 &buf >> cout;

第二种情况的个人解释:
socket >> streambuf;
>>是流数据, socket相对于流是输入, streambuf相对于流是输出序列; 把从网络读到的数据写入streambuf的输出序列。
当想把读到的数据打印出来(写到标准输出流)时, cout << streambuf; 等同于 streambuf >> cout;
此时streambuf作为流数据的输入源,但当没调用commit时,此时streambuf的输入序列指针没动,指向空,输出为空


同样的,假设你需要从streambuf对象中写入,如果你使用了write自由函数,则需要像下面一样:

streambuf buf;
std::ostream out(&buf);
out << "hi there" << std::endl;
write(sock, buf);   完整buf作为参数,它会自动处理好输入输出指针参数

下面的代码会把hi there发送三次:

streambuf buf;
std::ostream out(&buf);
out << "hi there" << std::endl;
for ( int i = 0; i < 3; ++i)
    write(sock, buf.data());  子buf作为参数,需要自己进行consume() commit()

发生的原因是因为buffer从来没有被消耗过,因为数据还在。如果你想消耗它,使用下面的代码片段:

streambuf buf;
std::ostream out(&buf);
out << "hi there" << std::endl;
write(sock, buf.data());
buf.consume(9);

总的来说你最好选择一次处理整个streambuf实例,而非通过prepare()或data()取得的子buf。如果需要调整则使用上述的方法。

尽管你可以在读和写操作时使用同一个streambuf,你仍然建议你分开使用两个,一个读另外一个写,它会让事情变的简单,清晰,同时你也会减少很多导致bug的可能。

 

//streambuf类似于muduo中的buffer设计(但不一样,boost::streambuf底层可能会是由多个子buffer组成的),同一块内存既用作写又用作读,这是通过读写指针完成的。

 

streambuf的使用原则,当把streambuf对象作为一个整体进行IO操作时,应用程序无需关心输入输出指针

否则当你对streambuf的子序列(prepare(), data()等函数返回的子序列)进行IO操作时,应用程序需要自己移动读写指针:

1,当你向子序列写数据完成后,记得commit(),移动写指针,除非你希望下一次的写操作从上一次的可写地址处进行覆盖写

2,当你从子序列里读出数据后,记得consume(),移动读指针,除非你希望下一次的读操作继续从上一次的读地址处重复读

 

Boost.Asio和STL stream

Boost.Asio在集成STL stream和网络方面做了很棒的工作。也就是说,如果你已经在使用STL扩展,你肯定就已经拥有了大量重载了操作符<<和>>的类。从socket读或者写入它们就好像在公园漫步一样简单。

假设你有下面的代码片段:

struct person {
    std::string first_name, last_name;
    int age;
};
std::ostream& operator<<(std::ostream & out, const person & p) {
    return out << p.first_name << " " << p.last_name << " " << p.age;
}
std::istream& operator>>(std::istream & in, person & p) {
    return in >> p.first_name >> p.last_name >> p.age;
}

通过网络发送这个person就像下面的代码片段这么简单(都是完整buf作为参数):

streambuf buf;
std::ostream out(&buf);
person p;
// … 初始化p
out << p << std::endl;
write(sock, buf);

另外一个部分也可以非常简单的读取:

read_until(sock, buf, "\n");
std::istream in(&buf);
person p;
in >> p;

最后要给出的是一个非常著名,非常酷的诀窍,使用下面的代码片段把streambuf的内容输出到console中

streambuf buf;
...
std::cout << &buf << std::endl; //把所有内容输出到console中

同样的,使用下面的代码片段来把它的内容转换为一个string

std::string to_string(streambuf &buf) {
    std::ostringstream out;
    out << &buf;
    return out.str();
}

 

streambuf继承自std::streambuf。就像std::streambuf本身,它不能拷贝构造。

当使用read_until时会有个难点:你需要记住你已经读取的字节数,因为下层的buffer可能多读取了一些字节(不像使用read()时)。比如:

std::cout << &buf << std::endl;

上述代码输出的字节可能比read_until读取到的多。

 

 



 /home/baiwei/boost/streambuf6.cpp


#include <boost/asio.hpp>
#include <boost/asio/buffer.hpp>
#include <iostream>
#include <string>
#include <string.h>
using namespace std;

int main()
{
        boost::asio::streambuf b;
        cout << "max_size(): " << b.max_size() << endl;

        boost::asio::mutable_buffers_1 mb1 = b.prepare(4);
        char* p = boost::asio::buffer_cast<char*>(mb1);
        memcpy(p, "1234", 4);

        b.commit(4);  //移动写指针,使能读到数据

        //do read
        char tmp[256] = { 0 };
        memcpy(tmp, boost::asio::buffer_cast<const void*>(b.data()), b.size());

        b.consume(4);  //移动读指针

        ///尝试再读,此时应该为空
        memset(tmp, 0, 4);
        memcpy(tmp, boost::asio::buffer_cast<const void*>(b.data()), b.size());
        //此刻,读指针==写指针

        //==========================
        mb1 = b.prepare(6);   //再次prepare时,从当前写指针继续写,以前的内容1,2,3,4依旧保存在streambuf b中
        p = boost::asio::buffer_cast<char*>(mb1);
        memcpy(p, "abcdef", 6);

        b.commit(6);  //移动写指针,使能读到数据

        //do read
        memcpy(tmp, boost::asio::buffer_cast<const void*>(b.data()), b.size());

        b.consume(b.size());  //移动读指针

        ///尝试再读,此时应该为空
        memset(tmp, 0, 4);
        memcpy(tmp, boost::asio::buffer_cast<const void*>(b.data()), b.size());

        //==========================
        cout << "max_size(): " << b.max_size() << endl;  //非常巨大
        //所以下面的写法,只有写没有读,有可能会写爆内存
        //要想内存不被撑爆,正确的做法是:有读有写
        const int count = 128;   //streambuf的默认缓冲是128字节
//      b.reset();   //重置读写指针;新版boost已废弃该接口

        boost::asio::streambuf b1;
        std::ostream os(&b1);

        for(int i = 0; i < count; i++)   //写满128字节后,继续写,会自动扩大内存
        {
                os << "x";
        }

        //写满后再写,在max_size()范围以内自动扩内存,以前的内容保留
        mb1 = b1.prepare(6);
        p = boost::asio::buffer_cast<char*>(mb1);
        memcpy(p, "abcdef", 6);

        b1.commit(6);  //移动写指针,使能读到数据
        cout << "test point" << endl;

        { //边写边读,内存问题好点儿
        boost::asio::streambuf b1;
        std::ostream os(&b1);

                char c = 'x';
        int j = 260;
        for(int i = 0; i < j; i++)   //写满128字节后,继续写,会自动扩大内存
        {
            os << c;
            if(i < 250)
            {
                b1.consume(1);   //边写边读,写指针达到128时,会触发overflow进行marshal
            }
            else
            {  //一旦不再进行读,写指针到底尾部时,overflow会进行内存扩容,所以正确的做法是显示指定一个max_size
                c = 'Y';
                cout << "not consume" << endl;
            }
        }
        //写满后再写,在max_size()范围以内自动扩内存,以前的内容保留
        mb1 = b1.prepare(6);
        p = boost::asio::buffer_cast<char*>(mb1);
        memcpy(p, "abcdef", 6);

        b1.commit(6);  //移动写指针,使能读到数据

        cout << "test point" << endl;
    }

        //=============显示指定一个最大值=============
        boost::asio::streambuf b2(128);
        cout << "max_size(): " << b2.max_size() << endl;

        std::ostream os2(&b2);

        for(int i = 0; i < count; i++)   //写满128字节后
        {
                os2 << "x";
        }

        try
        {
                //显示指定max_size,写满后再写,失败
                mb1 = b2.prepare(6);
                p = boost::asio::buffer_cast<char*>(mb1);
                memcpy(p, "abcdef", 6);

                b2.commit(6);  //移动写指针,使能读到数据
        }
        catch(...)
        {
                cout << "fal" << endl;
        }


        return 0;
}