muduo原始碼分析之Buffer
這一次我們來分析下muduo
中Buffer
的作用,我們知道,當我們客戶端向伺服器傳送資料時候,伺服器就會讀取我們傳送的資料,然後進行一系列處理,然後再發送到其他地方,在這裡我們想象一下最簡單的EchoServer
伺服器,客戶端建立一個連線,以後伺服器和客戶端之間的通訊都是通過這個connfd
傳送和接受資料,於是每一個connfd
都應該有一個自己buffer
,當我們傳送資料太快,伺服器傳送的太慢,則伺服器會將待發送的資料這個buffer
中,所以這就是這個類的作用。我們先看下buffer
的結構是什麼:
我們這裡主要針對connfd
這個對應的channel
進行分析,首先上圖是buffer
的初始狀態,前面8
buffer
的大小,初始大小為1024
。當客戶端傳送資料給伺服器,同時若伺服器接受緩慢,則會向buffer
中開始寫資料,則writerIndex_
會向右移動,假如此時移動到如下形式:
則此時緩衝區可以讀的資料為writerIndex_ - readerIndex_
,可以寫的資料為buffer_.size() - writerIndex_
。這時候當伺服器有多餘資源進行讀操作,就可以去緩衝區讀資料了,假如這時候的狀態為如下:
這就是常見的幾個狀態,下面我們去看幾個重點的函式:
// 把onMessage函式上報的buffer內容轉為string std::string retrieveAllAsString() { return retrieveAsString(readableBytes()); // 應用可讀取資料的長度 } // 可讀的資料 就是存放的是即傳送的資料 size_t readableBytes() const { return writerIndex_ - readerIndex_; } std::string retrieveAsString(size_t len) { // 從可讀資料開始位置,長度為len的char構造為一個string std::string result(peek(), len); retrieve(len); // 上面一句把緩衝區中可讀的資料,已經讀取出來,這裡肯定要對緩衝區進行復位操作 return result; } // 將緩衝區len的長度進行復位 void retrieve(size_t len) { // 表示還沒有讀完資料 if (len < readableBytes()) { readerIndex_ += len; // 應用只讀取了刻度緩衝區資料的一部分,就是len,還剩下readerIndex_ += len -> writerIndex_ } else // len == readableBytes() { retrieveAll(); } }
以上是基本的操作,下面的2個函式很重要,一個是向connfd
寫資料,一個是讀資料,對於一個TcpConnection
而言,當有資料來的時候,回去呼叫handleRead
回撥函式。我們知道muduo
設定的每次讀取的大小為65536
位元組,當緩衝區可寫的資料大小大於65536
,就會直接將讀到的資料寫入到緩衝區中,但當緩衝區的可寫資料大小小於65536
的時候,就會將剩餘資料先寫到一個額外的空間
ssize_t Buffer::readFd(int fd, int* saveErrno) { char extrabuf[65536] = {0}; // 棧上的記憶體空間 64K struct iovec vec[2]; // 這是buffer可寫的資料 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; const int iovcnt = (writable < sizeof extrabuf) ? 2 : 1; // 去百度下readv const ssize_t n = ::readv(fd, vec, iovcnt); if (n < 0) { *saveErrno = errno; } else if (n <= writable) // Buffer的可寫緩衝區已經夠儲存讀出來的資料了 { writerIndex_ += n; } // extrabuf 也寫了資料 else { writerIndex_ = buffer_.size(); append(extrabuf, n - writable); // writerIndex_開始寫 n - writable大小的資料 } return n; }
這裡巧妙的使用了一個readv
函式,可以通過按照大小自動寫到不同的地方。其中當extrabuf
也寫了資料,就會呼叫append
函式。
// 要寫len長度的資料
void ensureWriteableBytes(size_t len)
{
if (writableBytes() < len)
{
makeSpace(len); // 擴容函式
}
}
// 向緩衝區新增資料
void append(const char *data, size_t len)
{
ensureWriteableBytes(len);
std::copy(data, data+len, beginWrite());
writerIndex_ += len;
}
注意到有一個makeSpace
函式,其中有一個注意點,比如當如下這種狀態的時候:
此時readerIndex_
前面有一部分其實已經被讀完了,是空的資料,所以makeSpace
函式考慮了這一點,採用記憶體重組的方式,將readerIndex_
向前移動到kCheapPrepend
處,然後就可以讓空餘的記憶體挨在一起
void makeSpace(size_t len)
{
if (writableBytes() + prependableBytes() < len + kCheapPrepend)
{
buffer_.resize(writerIndex_ + len);
}
else
{
size_t readalbe = readableBytes();
std::copy(begin() + readerIndex_,
begin() + writerIndex_,
begin() + kCheapPrepend);
readerIndex_ = kCheapPrepend;
writerIndex_ = readerIndex_ + readalbe;
}
}
當向connfd
傳送資料的時候就比較簡單了,直接將可讀的資料傳送給出去就行
// 通過fd傳送資料
ssize_t Buffer::writeFd(int fd, int* saveErrno)
{
ssize_t n = ::write(fd, peek(), readableBytes());
if (n < 0)
{
*saveErrno = errno;
}
return n;
}
自己的網址:www.shicoder.top
歡迎加群聊天 452380935
本文由部落格一文多發平臺 OpenWrite 釋出!