This time we have to analyze the muduo Buffer role, as we know, when our client to send data to the server, the server can read we send data, and then a series of processing, and then sent to the other place, here we imagine the simplest EchoServer server, the client to establish a connection, In the future, all communication between the server and client will be sent and received through this Connfd, so each ConnFD should have its own buffer. If we send data too fast and the server sends data too slowly, the server will put the data to be sent into this buffer, so this is the purpose of this class. Let’s first look at the structure of a buffer:
Here we mainly analyze the channel corresponding to Connfd. First of all, the figure above shows the initial state of buffer. The first 8 bytes represent the size of the buffer, and the initial size is 1024. When the client sends data to the server and the server receives it slowly, the writerIndex_ will start writing data to the buffer and move to the right.
In this case, the data that can be read by the buffer is writerIndex_ -readerIndex_, and the data that can be written is buffer_.size() -writerIndex_. At this time, when the server has extra resources to read, it can read data from the buffer, if the state is as follows:
These are the common states. Let’s look at some key functions:
// Convert the buffer reported by onMessage to a string
std::string retrieveAllAsString(a)
{
return retrieveAsString(readableBytes()); // The length of the data that the application can read
}
// Readable data stores data that is sent immediately
size_t readableBytes(a) const
{
return writerIndex_ - readerIndex_;
}
std::string retrieveAsString(size_t len)
{
// A char of length len is constructed as a string starting from the readable data position
std::string result(peek(), len);
retrieve(len); // The buffer must be reset
return result;
}
// Reset the length of buffer len
void retrieve(size_t len)
{
// Indicates that you have not finished reading the data
if (len < readableBytes())
{
readerIndex_ += len; ReaderIndex_ += len -> writerIndex_ = len -> writerIndex_
}
else // len == readableBytes()
{
retrieveAll();
}
}
Copy the code
The following two functions are important: one is to write data to ConnFD and one is to read data. For a TcpConnection, when data comes in, it goes back and calls the handleRead callback. We know that muduo sets the size of each read to 65536 bytes. When the size of the buffer is more than 65536 bytes, the read data is directly written to the buffer, but when the size of the buffer is less than 65536 bytes, the remaining data is written to an extra space first
ssize_t Buffer::readFd(int fd, int* saveErrno)
{
char extrabuf[65536] = {0}; // Memory space on the stack 64K
struct iovec vec[2].
// This is buffer writable data
const size_t writable = writableBytes(a); vec[0].iov_base = begin() + writerIndex_;
vec[0].iov_len = writable;
vec[1].iov_base = extrabuf;
vec[1].iov_len = sizeof extrabuf;
const int iovcnt = (writable < sizeof extrabuf) ? 2 : 1;
// Go to baidu readv
const ssize_t n = ::readv(fd, vec, iovcnt);
if (n < 0)
{
*saveErrno = errno;
}
else if (n <= writable) // The writable Buffer of the Buffer is enough to store the read data
{
writerIndex_ += n;
}
// Extrabuf also writes data
else
{
writerIndex_ = buffer_.size(a);append(extrabuf, n - writable); // writerIndex_ starts writing n-writable data
}
return n;
}
Copy the code
This makes clever use of a readv function that automatically writes to different places by size. When Extrabuf also writes data, the append function is called.
// Write len length data
void ensureWriteableBytes(size_t len)
{
if (writableBytes() < len)
{
makeSpace(len); // Expansion function}}// Add data to the buffer
void append(const char *data, size_t len)
{
ensureWriteableBytes(len);
std::copy(data, data+len, beginWrite());
writerIndex_ += len;
}
Copy the code
Notice that there is a makeSpace function with one point in it, for example, when in the following state:
The part of readerIndex_ in front of it is empty, so makeSpace takes this into account and moves readerIndex_ forward to kCheapPrepend using memory reorganization, where the free memory is placed next to each other
void makeSpace(size_t len)
{
if (writableBytes() + prependableBytes() < len + kCheapPrepend)
{
buffer_.resize(writerIndex_ + len);
}
else
{
size_t readalbe = readableBytes(a); std::copy(begin() + readerIndex_,
begin() + writerIndex_,
begin() + kCheapPrepend); readerIndex_ = kCheapPrepend; writerIndex_ = readerIndex_ + readalbe; }}Copy the code
When sending data to ConnFD, it is easier to simply send readable data to connFD
// Send data via fd
ssize_t Buffer::writeFd(int fd, int* saveErrno)
{
ssize_t n = ::write(fd, peek(), readableBytes());
if (n < 0)
{
*saveErrno = errno;
}
return n;
}
Copy the code