Turn from: http://blog.csdn.net/shreck66/article/details/49618331
1. Why application layer buffer is necessary in non-blocking network programming
The central idea of non-blocking I/O (which is understood here as non-blocking I/O plus t/o multiplexing) is to avoid blocking the current thread on I/O system calls, so that our I/O threads block only on I/O multiplexing functions (epoll_wait or Poll,select) Enable it to serve more connection sockets so how do you do that? Here we need to implement the application layer of input and output buffer (1) Under what circumstances to use output buffer?
Let's say we're going to send 100k of data over a TCP connection, and when we execute write (), the operating system is affected by some factors that only accept 70k data, so what do we do with 20k left? is to wait for the kernel to make room to accept the remaining 20k. What if you wait a long time? Are our events going to do nothing in your waiting time? In view of the above problem we can use a write buffer of the application layer to solve, the temporary kernel can not accept the data first exist in the buffer, and then register the Pollout event, a single socket can be written, we will write the data in the cache, If you do not finish the next time, then continue to register Pollout event to continue to write, if the data in the buffer is not finished, the program came to the data, then the data should be directly appended to the buffer data tail. With the application layer of write buffer after our program does not care about whether the data can be sent out at once, these have network library to worry about (2) when to use the input buffer?
We handle the socket readable event, we must read the socket data at a time, otherwise it will repeatedly trigger the Pollin event (described here for the LT Model of Epoll), causing the main loop busy-loop, but then again, if we read the socket data , you cannot guarantee the integrity of a particular message. Then our network library should be miscellaneous do. Our network library due to the content of the first read in the input buffer, and so on input buffer has the complete message, in the notification business procedures, so you can improve the speed of 2.buffer function design (1) Muduo Design Essentials of Buffer
The external performance is a continuous memory
. Its size can automatically grow to accommodate the growing message that it cannot be a fixed sized buffer
. Internal Std::vector to save the data and provide the corresponding access function
. Input buffer, The program reads the data from the socket and writes input buffer, and the client code reads the data from the input buffer
. Output buffer, the client code writes the data into output buffer and then from output Read data in buffer and write to socket
Main data member of Muduo Buffer class
Vectors are used to hold data
std::vector<char> buffer_;
The subscript size_t of the reading position is
readerindex_;
The subscript size_t writerindex_ of the writing position
;
1 2 3 4 5 6
The above data member action annotation is also quite clear, the only thing worth noting is that the read-write subscript is set to the size_t type because if you set them to a pointer type, it is possible for the vector to fail when inserting the deletion.
Main external interface of Muduo buffer class
The number of readable bytes in the current buffer
readablebytes ();
Add data to Buffer
append ();
Exchange two buffer;
Swap ();
Reading data from sockets to buffer
READFD ();
1 2 3 4 5 6 7 8
(2) How to set the initial value of the buffer ?
Design such a buffer how many initial values should we give it? On the one hand, we want to reduce system calls, read more data the better, in this case our buffer should give the greater the better. On the other hand we hope that we can minimize the memory footprint, if the buffer set is very large, when the number of connections, will occupy a lot of space, so for this situation, we design the buffer of course the smaller the better?
So how do we find a good compromise between the two conflicting demands?
Muduo Library of the initial size of the buffer can be said to use a very clever method, its specific code is the READ_FD function, specifically as follows
In the stack to open a 65536-byte extrabuf, and then use READV this system to read the data, Readv Iovec have two blocks, the first point to the buffer (buffer initial size 1024) in the writable byte segment, Another point on the stack on the extrabuf, according to the characteristics of the READV, when the read data is less than the buffer of the writing section, the data will all exist in the buffer, otherwise the buffer is full, extrabuf save the rest of the part, Then in the data append into the buffer (increase the buffer size), after the READFD function end, Extrabuf stack space is released
You may read that there will be a certain doubt, extrabuf data to be added to the buffer, this and directly put the buffer set big and what is the difference. You can think that we read the socket when we do not know the size of the data, so we can not set the value of the buffer size, so we requisitioned the stack (zero space) to help us complete the socket read operation after we know exactly how much data, So in need of how much space we give buffer add how much space, then the stack of zero space will be destroyed with the end of the READFD function.
PS: Must seize buffer is heap space, and extrabuf only temporary existence of the stack space to understand the design idea here
The READFD function reads as follows
ssize_t buffer::readfd (int fd,int *savederrno) {//stack extra space, used to read from sockets, when the buffer is temporarily insufficient to temporarily save data, wait for buffer to redistribute enough space,
In the exchange of data to buffer char extrabuf[65536];
struct Iovec vec[2];
Const size_t writable = Writablebytes ();
Vec[0].iov_base = Begin () +:: writerindex_;
Vec[0].iov_len = writable;
Vec[1].iov_base = Extrabuf;
Vec[1].iov_len = sizeof (EXTRABUF); When vector is enough use no stack space const int IOVCNT = (Writable < sizeof (EXTRABUF))?
2:1;
Const ssize_t n = sockets::readv (fd,vec,iovcnt);
if (n < 0) {*savederrno = errno;
else if (implicit_cast<size_t> (n) <= writable) {writerindex_ + = n;
else {writerindex_ = Buffer_.size ();
Add a portion of the extra space to the buffer to append (extrabuf,n-writable);
} return N; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
3.buffer of data structure
(1) The meaning and simple operation of variables in buffer
The Muduo buffer structure is initially shown as follows
Photo source with Chen Yu Blog
As shown in the figure above, in the initialized 1024 space, the No. 0 to 8th byte (the area shown in kcheapprepend) is reserved space, which allows us to add a few bytes to the front of the program at a very low cost, and the remainder is read-write area,
Where Readindex points to the starting position of the readable region, Writeindex points to the starting position of the writable region, both of which are in the initial position after the space is 8, when we add 200 data to the buffer, the following figure
From the above figure is easy to see the readable position is still 8, as a result of the new increase of 200 data, the writable position will be corresponding to move 200 locations, such as the above figure Writeindex = 208
When we take 50 of the data from the buffer in the image above, the following figure shows
Since 50 data has been taken away, so the position of the Readindex will be moved 50 places, the reservation space increased by 50 bytes
According to the above specific operation, we can not sum up, prependable (storage space), readable (readable space), writable (writable space) of the definition
prependable = Readindex
readable = Writeindex-readindex
writable = Size ()-Writeindex
1 2 3
(2) automatic growth of buffer
The buffer in the Muduo is indefinite, it can grow automatically, as follows
If the initial buffer is as follows
When we add 1000 data to the buffer, the buffer becomes the following image
The specific change process code is as follows
void MakeSpace (size_t len)//Increase vector {if (writablebytes () + prependablebytes () < Len + KC
Heapprepend//writable space and the amount of space vacated in the move is less than the length of the write length and space and {//description cache space is not enough.
Buffer_.resize (writerindex_ + len);
else {assert (Kcheapprepend < readerindex);
size_t readable = readablebytes (); Move existing data to kcheapprepend position std::copy (begin () + readerindex_, begin () + WR
Iterindex_, Begin () + kcheapprepend);
Readerindex_ = Kcheapprepend;
Writerindex_ = readerindex_ + readable;
assert (Readable = = Readablebytes ()); }
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20-21
The specific process of increasing the buffer above is as follows:
First, when the reserved space is less than the length to be written and the basic reservation length is kcheapprepend, the current buffer does not have space, Now I need to call resize to increase the size of the vector space. Otherwise, the description is currently only writable space is not enough to write length, but the total size of the writable space and reserve space than the above judgment Len + kcheapprepend, the reason for the performance of space is not enough, just reserved space is not used, so we just need to put the buffer in the data region ( Readindex to Writerindex) move to a basic reservation (at 8th position in buffer)