Viktor Lundström said:
Information on the web seems to be a bit scarce on how to do this.
Is it? I'm sure that I have posted at least 10 different stream buffers
to newsgroups, at least 5 of whom were perfectly applicable to your
question (ie. showed the use of the appropriate member functions). Even
though some of this stuff is pretty old by now, it is still applicable.
If I inherit streambuf, which functions do I have to override
to achieve buffered IO with arbitrary buffer size?
The absolute minimum for buffered I/O are:
- 'underflow()' for reading
- 'overflow()' and 'sync()' for writing
To improve performance you might want to also override 'xsgetn()' and
'xsputn()' but I suspect that this is not necessary for a socket
communication: it makes sense when huge blocks of data can be processed
more efficiently than smaller blocks. I don't think this is the case
for sockets although it may be possible to avoid copying the bytes one
time.
What you have to do in your stream buffer for sockets is pretty simple:
- You have to create the socket itself, probably in the constructor or
even earlier from another class.
- You have to create input and output buffers. These are probably best
allocated in the constructor. Initialization of the stream buffer to
become aware of the get buffer is a natural thing to be done in
'underflow()'. Eg.:
int socketbuf::s_bufsize const = 1024;
socketbuf::socketbuf(int fd):
m_fd(fd), m_in(new char[s_bufsize]), m_out(new char[s_bufsize])
{
// set up the output buffer to leave at least one space empty:
setp(m_out, m_out + s_bufsize - 1);
}
socketbuf::~socketbuf() { delete[] m_in; delete[] m_out; }
- 'overflow()' is called when the buffer is full and a character is
attempted to be put into the buffer. In addition, it may be called
with an argument of 'traits_type::eof()', indicating that the buffer
should be flushed. Since there is a one character space left, the
passed character is put there and 'sync()' is called:
socketbuf::int_type socketbuf:

verflow(int_type c) {
if (!traits_type::eq_int_type(traits_type::eof(), c)) {
traits_type::assign(*pptr(), traits_type::to_char_type(c));
pbump(1);
}
return sync() == 0? traits_type::not_eof(c): traits_type::eof();
}
- 'sync()' is called to bring the internal and the external stream
into synchronization. This effectively means that the output buffer
is to be flushed:
int socketbuf::sync() {
return pbase() == pptr() ||
write(m_fd, pbase(), pptr() - pbase()) == pptr() - base()? 0: 1;
}
- 'underflow()' is called when a character is to be read but none is
available. It is supposed to fill a buffer (if no buffer is used for
input, you need to implement 'uflow()', too). A real implementation
will move a few characters of the previous buffer to the front of
the new buffer to always allow put back (this is not done here for
brevity):
socketbuf::int_type socketbuf::underflow() {
int rc = read(m_fd, m_in, s_bufsize);
if (rc <= 0)
return traits_type::eof();
setg(m_in, m_in, m_in + rc);
return traits_type::to_int_type(*gptr());
}
The sequence between the first parameter to 'setg()' and the second
parameter is the "put back area" (in this case empty): if you retain
characters for put back, you start reading beginning at the second
parameter.
That's it (... and all of this I have said before, although with
different words, in articles I have posted in the past).
How do I create an iostream which uses my extended streambuf?
The stream classes and 'std::ios' have a constructor taking a pointer
to 'std::streambuf'. So you can simply use these constructors to
create an appropriate stream, eg.:
socketbuf socketbuf(open_socket_somehow());
std::istream socketin(&socketbuf);
Typically, construction of the stream buffer is encapsulated into
a class derived from 'std::istream', 'std:

stream', or
'std::iostream'. I have shown this technique in several articles I
have posted in the past...