阻塞/非阻塞讀寫總結、tcp網路程式設計的本質、muduo::Buffer設計簡介
一、阻塞/非阻塞讀寫總結
1、對於read 呼叫,如果接收緩衝區中有 20位元組,請求讀 100個位元組,就會返回 20;對於 write呼叫,如果請求寫 100個位元組,而傳送緩衝區中只有 20個位元組的空閒位置,那麼 write會阻塞,直到把 100個位元組全部交給傳送緩衝區才返回。但如果 socket檔案描述符有 O_NONBLOCK標誌,則 write不阻塞,直接返回 20;此時非阻塞地read
也直接返回20。
2、read 沒有一點資料可讀或 write 沒有一點空間可以寫入,如果disable O_NONBLOCK 則會阻塞,如果enable O_NONBLOCK 則會返回-1,errno = EAGAIN | EWOULDBLOCK 錯誤。
3、阻塞模式下可以用setsockopt設定SO_RCVTIMEO(超時時間),即如果在超時時間內接收緩衝區都沒有一點資料到來,那麼返回-1,errno = EAGAIN | EWOULDBLOCK 錯誤。同理,還有SO_SNDTIMEO
選項,在超時時間內傳送緩衝區都沒有足夠記憶體存放資料,也是返回-1,errno = EAGAIN | EWOULDBLOCK 錯誤。
4、recv的第四個引數若為MSG_WAITALL,則在阻塞模式下不等到指定數目的資料不會返回,除非超時時間到。當然如果對方關閉了,即使超時時間未到,recv
也返回0。/usr/include/i386-linux-gnu/bits/socket.h MSG_WAITALL = 0x100
5、在多執行緒環境中,某個執行緒的阻塞不會引起程式的阻塞,除非程式中的所有執行緒都被阻塞。(pthread)
二、TCP網路程式設計的本質
TCP網路程式設計最本質是的處理三個半事件(來自:muduo manual.pdf)
1. 連線的建立,包括服務端接受(accept) 新連線和客戶端成功發起(connect) 連接。TCP 連線一旦建立,客戶端和服務端是平等的,可以各自收發資料。2. 連線的斷開,包括主動斷開(close 或shutdown) 和被動斷開(read(2) 返回0)。3. 訊息到達,檔案描述符可讀。這是最為重要的一個事件,對它的處理方式決定了網路程式設計的風格(阻塞還是非阻塞,如何處理分包,應用層的緩衝如何設計等等)。3.5 訊息傳送完畢,這算半個。對於低流量的服務,可以不必關心這個事件;另外,這裡“傳送完畢”是指將資料寫入作業系統的緩衝區,將由TCP 協議棧負責數據的傳送與重傳,不代表對方已經收到資料。
1、下圖是根據muduo庫中對讀寫事件的處理畫出的草圖:
2、Echoser 類圖:(muduo/example/simple/Echo.h、Echo.cc)
使用基於物件風格實現,詳見這裡。
3、什麼都不做的EventLoop
one loop per thread意思是說每個執行緒最多只能有一個EventLoop物件,這種執行緒即“reactor"(mainReactor & subReactor)。剩下一些存在於threadpool 的執行緒主要用於做計算(decode, compute, encode),並不是IO執行緒。
EventLoop物件構造的時候,會檢查當前執行緒是否已經建立了其他EventLoop物件,如果已建立,終止程式(LOG_FATAL)
EventLoop建構函式會記住本物件所屬執行緒(threadId_)。
建立了EventLoop物件的執行緒稱為IO執行緒,其功能是執行事件迴圈(EventLoop::loop)
三、muduo::Buffer設計簡介
所有muduo 中的IO 都是帶緩衝的IO (buffered IO),你不會自己去read() 或write() 某個socket,只會操作TcpConnection 的input buffer 和output buffer。更確切的說,是在onMessage()
回撥裡讀取input buffer;呼叫TcpConnection::send()來間接操作output buffer,一般不會直接操作output buffer。
TcpConnection 會有兩個Buffer 成員,input buffer 與output buffer。
• input buffer,TcpConnection 會從socket 讀取資料,然後寫入input buffer(其實這一步是用Buffer::readFd() 完成的);客戶程式碼從input buffer 讀取數據。
• output buffer,客戶程式碼會把資料寫入output buffer (其實這一步是用TcpConnection::send() 完成的);TcpConnection 從output buffer 讀取資料並寫入socket。
其實,input 和output 是針對客戶程式碼而言,客戶程式碼從input 讀,往output 寫。TcpConnection 的讀寫正好相反。
兩個indices 把vector 的內容分為三塊:prependable、readable、writable,各塊的大小是(公式一):
prependable = readIndex
readable = writeIndex - readIndex
writable = size() - writeIndex
Muduo Buffer 裡有兩個常數kCheapPrepend 和kInitialSize,定義了prependable的初始大小和writable 的初始大小,readable
的初始大小為0。在初始化之後,Buffer 的資料結構如下:括號裡的數字是該變數或常量的值。
關於Buffer::readFd():
C++ Code
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 33 34 35 36 |
// 結合棧上的空間,避免記憶體使用過大,提高記憶體使用率
// 如果有5K個連線,每個連線就分配64K+64K的緩衝區的話,將佔用640M記憶體, // 而大多數時候,這些緩衝區的使用率很低 ssize_t Buffer::readFd(int fd, int *savedErrno) { // saved an ioctl()/FIONREAD call to tell how much to read // 節省一次ioctl系統呼叫(獲取有多少可讀資料) 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; const ssize_t n = sockets::readv(fd, vec, 2); if (n < 0) { *savedErrno = errno; } else if (implicit_cast<size_t>(n) <= writable) //第一塊緩衝區足夠容納 { writerIndex_ += n; } else // 當前緩衝區,不夠容納,因而資料被接收到了第二塊緩衝區extrabuf,將其append至buffer { writerIndex_ = buffer_.size(); append(extrabuf, n - writable); } // if (n == writable + sizeof extrabuf) // { // goto line_30; // } return n; } |
具體做法是,在棧上準備一個65536 位元組的stackbuf,然後利用readv() 來讀取資料,iovec 有兩塊,第一塊指向muduo Buffer 中的writable 位元組,另一塊指向棧上的stackbuf。這樣如果讀入的資料不多,那麼全部都讀到Buffer 中去了;如果長度超過Buffer 的writable 位元組數,就會讀到棧上的stackbuf 裡,然後程式再把stackbuf 裡的資料append 到Buffer 中。
參考:
《UNP》
muduo manual.pdf
《linux 多執行緒伺服器程式設計:使用muduo c++網路庫》
相關文章
- 阻塞式程式設計和非阻塞式程式設計區別程式設計
- Java 網路程式設計 —— 非阻塞式程式設計Java程式設計
- [譯] 非同步程式設計:阻塞與非阻塞非同步程式設計
- IO模式設定網路程式設計常見問題總結—IO模式設定,阻塞與非阻塞模式程式設計
- 玩轉 PHP 網路程式設計全套阻塞與非阻塞 IOPHP程式設計
- NIO非阻塞程式設計小案例程式設計
- 網路IO之阻塞、非阻塞、同步、非同步總結非同步
- python 網路程式設計----非阻塞或非同步程式設計Python程式設計非同步
- Socket程式設計中的同步、非同步、阻塞和非阻塞(轉)程式設計非同步
- IO multiplexing 與 非阻塞網路程式設計程式設計
- 【網路程式設計】阻塞IO程式設計的坑程式設計
- Linux裝置驅動程式設計之阻塞與非阻塞(轉)Linux程式設計
- 一文徹底搞定(阻塞/非阻塞/同步/非同步)網路IO、併發程式設計模型、非同步程式設計模型的愛恨情仇非同步程式設計模型
- Java 網路程式設計 —— 實現非阻塞式的伺服器Java程式設計伺服器
- 併發程式設計之臨界區\阻塞\非阻塞\死鎖\飢餓\活鎖程式設計
- linux非阻塞式socket程式設計之select()用法Linux程式設計
- Linux核心程式設計(阻塞程式)(轉)Linux程式設計
- 【進階之路】併發程式設計(三)-非阻塞同步機制程式設計
- 同步、非同步、阻塞、非阻塞的簡單理解非同步
- 【程式設計素質】程式設計思想總結程式設計
- C++ 之Socket 程式設計 send rev 阻塞設定 阻塞超時時間C++程式設計
- 同步阻塞、同步非阻塞、多路複用的介紹
- 用fcntl()設定阻塞函式的阻塞性質函式
- 程式設計的本質程式設計
- Java併發程式設計:阻塞佇列Java程式設計佇列
- Java併發程式設計——阻塞佇列Java程式設計佇列
- 一直讓 PHP 程式設計師懵逼的同步阻塞非同步非阻塞,終於搞明白了PHP程式設計師非同步
- Linux核心模組程式設計--阻塞程式(轉)Linux程式設計
- Java網路程式設計和NIO詳解5:Java 非阻塞 IO 和非同步 IOJava程式設計非同步
- 程式設計師的本質程式設計師
- Java設計模式簡介(總結)Java設計模式
- iOS 網路程式設計(一)TCP IP協議簡介iOS程式設計TCP協議
- 結對程式設計簡介程式設計
- 同步非同步,阻塞非阻塞非同步
- 非同步、同步、阻塞、非阻塞非同步
- 同步、非同步、阻塞、非阻塞非同步
- 阻塞IO與非阻塞IO
- 《JAVA併發程式設計實戰》原子變數和非阻塞同步機制Java程式設計變數