1. 程式人生 > >Epoll-ET模式下非阻塞讀寫之Buffer的封裝

Epoll-ET模式下非阻塞讀寫之Buffer的封裝

先說說Epoll的ET模式

epoll預設的模式是LT,要說ET不得不提到LT,LT與ET的區別可以用一句話概括:
LT模式下只要socket處於可讀狀態(新增EPOLLIN事件時)或可寫狀態(新增EPOLLOUT事件時),就會一直返回其socket。
ET模式下在第一次返回socket後,只有當socket由不可寫到可寫(新增EPOLLIN事件時)或由不可讀到可讀(新增EPOLLOUT事件時),才會返回其socket。

進一步捋一下

由上可得,我們對ET操作必須要做到以下條件:
如果讀:必須要將緩衝區的內容全部讀出,即讀到緩衝區空為止
如果寫:一直寫,知道需寫的資料寫完或是緩衝區滿為止。
要做到以上要求,我們必須使用非阻塞套接字,否則socket會常常處在阻塞情況下,從而導致其他套接字餓死的情況發生。

(何謂餓死,程式阻塞在當前套接字的操作上,而其他套接字根本沒有機會進行操作)

為什麼我們需要Buffer

TCP 是一個無邊界的位元組流協議,接收方必須要處理“收到的資料尚不構成一條完整的訊息”和“一次收到兩條訊息的資料”等等情況。
如果是阻塞模式下,上面的情況根本不需要考慮,因為只要你recv,資料最終一定會過來,但代價就是你會一直阻塞在那裡,我們編寫伺服器當然要儘可能的避免這種情況的出現,所以我們使用非阻塞套接字。
然而,非阻塞模式下,我們就必須要解決上面的情況。
那麼,想想,如果給你一個記憶體空間,你讀取資料時,無論取多少,都先放進這個空間,而要處理時從這個空間取資料即可。


有了這個空間,無論是訊息不完整還是多個訊息,我們都得以解決。
寫也是一樣的,當你向socket傳送資料時,如果傳送了一部分,緩衝區滿了,此時我們該怎麼辦呢,只好一直阻塞,直至socket變回可寫,再將剩餘的資料全部寫入。顯然,代價還是阻塞,如果我們不想阻塞,仍然需要一個類似上面的空間。
這個空間,就是我們說的buffer,也可以叫做使用者緩衝區。

Buffer的實現思路

首先,我們希望保證任何時候向buffer裡面新增資料,都可以新增成功,也就是說,我們的buffer的空間需要足夠大,但我們又不能確定一個固定的數值,因為如果數字設定的小了,還是會出現新增失敗的情況,但如果設定的大了,又會導致大量空間的浪費。
所以我們將空間設定為可變的,用vector來儲存,因為vector空間增長是以2的冪的形式擴充套件,很高效。
用兩個指標或是變數來作為讀標誌和寫標誌,如下圖所示。

圖片摘自Muduo 設計與實現之一:Buffer 類的設計)

這張圖畫的相當的清晰明瞭,一目瞭然,就不再具體描述了。
另外有兩點優化,第一點是當Buffer內沒有資料的時候(也就是readindex=writeindex時),要將兩個標誌全部歸零,以免空間一直無限制增長下去,前面的空間反而浪費了。
第二點是,要新增資料時,如果剩餘的空間不夠(writeable),而加上前面空閒的空間(prependable+writeable)能夠放下的話,將資料移動到buffer起始位置,以避免一次空間的增長。

程式碼

//
//  Buffer.h
//  QuoridorServer
//
//  Created by shiyi on 2016/12/2.
//  Copyright © 2016年 shiyi. All rights reserved.
//

#ifndef Buffer_H
#define Buffer_H

#include <stdio.h>
#include <iostream>
#include <vector>

using namespace std;

class Buffer
{
public:
    Buffer() : m_widx(0), m_ridx(0)
    {}

    ~Buffer(){}

    void init()
    {
        m_widx = m_ridx = 0;
        m_buf.clear();
    }

    //增加內容
    void PutData(char *data, int len)
    {
        //如果調整空間後足夠存放,則進行調整
        int capa = m_buf.capacity();
        if(capa < m_widx + len && capa > len + m_widx - m_ridx)
            adjust();

        for(int i = 0; i < len; i++)
            m_buf.push_back(data[i]);

        m_widx += len;
    }

    //返回獲取的包的大小,資料不完整返回-1
    int GetData(char* data, int len)
    {
        len = min(m_widx-m_ridx, len);

        for(int i = 0; i < len; i++)
        {
            if(m_ridx >= m_widx)
                break;
            data[i] = m_buf[m_ridx++];
        }

        if(m_ridx >= m_widx)
        {
            m_ridx = m_widx = 0;
            m_buf.clear();
        }
        return len;
    }

private:

    //將資料移至容器頭部,充分利用空間
    void adjust()
    {
        vector<char> t(m_buf.begin()+m_ridx, m_buf.begin()+m_widx);
        m_widx -= m_ridx;
        m_ridx = 0;

        m_buf.clear();

        for(int i=0; i<m_widx; i++)
            m_buf.push_back(t[i]);
    }

private:

    int m_ridx;
    int m_widx;
    std::vector<char> m_buf;
};

#endif /* Buffer_H */

相關推薦

Epoll-ET模式阻塞Buffer封裝

先說說Epoll的ET模式 epoll預設的模式是LT,要說ET不得不提到LT,LT與ET的區別可以用一句話概括: LT模式下只要socket處於可讀狀態(新增EPOLLIN事件時)或可寫

epoll阻塞規則

在linux的網路程式設計中,很長的時間都在使用select來做事件觸發。在linux新的核心中,有了一種替換它的機制,就是epoll。 相比於select,epoll最大的好處在於它不會隨著監聽fd數目的增長而降低效率。因為在核心中的select實現中,它是採用輪詢來處理的,輪詢的fd數目越多,自然耗時越

epoll:EPOLLET模式的正確方式

1.EPOLLLT和EPOLLET最大的區別在於事件的通知機制,看這個文章EPOLLLT和EPOLLET的區別 2.EPOLLET模式下並不意味著要迴圈讀取完緩衝區的所有資料,貼出一段讀取程式碼: n = 0; while ((nread = read(fd, buf

Epoll在LT和ET模式方式和區別

LT模式:epoll就是一個快速版poll,可讀可寫就緒條件和傳統poll一致 ET模式:為了避免Starvation,建議           1)檔案描述符設定為非阻塞           2)只在read或write返回EAGAIN後,才能呼叫下一次epoll

epoll在LT和ET模式方式

在一個非阻塞的socket上呼叫read/write函式, 返回EAGAIN或者EWOULDBLOCK(注: EAGAIN就是EWOULDBLOCK) 從字面上看, 意思是: * EAGAIN: 再試一次 * EWOULDBLOCK: 如果這是一個阻塞socket

epoll反應堆及ET模式的EPOLLOUT學習總結

學習epoll反應堆發現網上的epoll反應堆都是同一份程式碼框架… 自己理解、梳理一遍,思路在註釋裡 #include <stdlib.h> #include <stdio.h> #include <stdio.h>

阻塞模式阻塞模式send、sendto、recv、recvfrom的表現

首先socket在預設情況下是阻塞狀態的(未指非同步操作以及其它一些特殊用途下,直接預設為非阻塞),這就使得傳送以及接收操作處於阻塞的狀態,即呼叫不會立即返回,而是進入睡眠等待操作完成。下面把討論點分為傳送以及接收。  一.傳送選用send(這裡特指TCP)以及sendto(

EPOLLET模式會被觸發多次麼?

前幾天和同學一起討論EPOLLONESHOT的作用,它的功能是這樣的: 對於註冊了EPOLLONESHOT事件的檔案描述符,作業系統最多觸發其上註冊的包括可讀,可寫,錯誤中的一個,且只觸發一次 剛一看感覺EPOLLONESHOT咋麼就是ET模式相對於LT模

epoll用法說明,ET模式的邊緣觸發處理同時多事件

#include <deque> #include <map> #include <vector> #include <pthread.h> #include <semaphore.h> #in

FIFO 阻塞+阻塞+延時迴圈的一種方法

用mkfifo在當前目錄下建立一個myfifo的有名管道 只執行非阻塞寫的程式 open引數為O_WRONLY | O_NONBLOCK write失敗,這是man手冊裡面說明了的情況 如果open引數為O_RDWR | O_NONBLOCK 寫程式則可以立即返回 但是當執

epoll學習筆記(ET模式事件觸發原理和資料收發存在的問題)

這篇文章所講的例子和情況可以結合《epoll的LT模式和ET模式 》這篇看。 epoll有兩種模式,Edge Triggered(簡稱ET) 和 Level Triggered(簡稱LT).在採用這兩種模式時要注意的是,如果採用ET模式,那麼僅當狀態發生變化時才會通知,而採

Linux測試磁碟速度

 1.測/目錄所在磁碟的純寫速度: time dd if=/dev/zero bs=1024 count=1000000 of=/1Gb.file 2.測/目錄所在磁碟的純讀速度: time dd if=/1Gb.file bs=64k |dd of=/dev/null

Golang通道的無阻塞的方法示例

無論是無緩衝通道,還是有緩衝通道,都存在阻塞的情況,但其實有些情況,我們並不想讀資料或者寫資料阻塞在那裡,有1個唯一的解決辦法,那就是使用select結構。 這篇文章會介紹,哪些情況會存在阻塞,以及如何使用select解決阻塞。 阻塞場景 阻塞場景共4個,有快取和無緩衝各2個。 無緩

windowsRedis 主從分離部署

1.可直接下載window下的執行檔案(下面這個連結) 也可以瀏覽github 檢視相應的版本說明文件 https://github.com/ServiceStack/redis-windows   檔案下載後解壓後文件如圖 本次搭建環境為同一臺機器上。多臺快取伺服器需要設定自己的IP地址,分別建立主從資

Linux Epoll ET模式EPOLLOUT和EPOLLIN觸發時刻

ET模式稱為邊緣觸發模式,顧名思義,不到邊緣情況,是死都不會觸發的。 EPOLLOUT事件: EPOLLOUT事件只有在連線時觸發一次,表示可寫,其他時候想要觸發,那你要先準備好下面條件: 1.某次write,寫滿了傳送緩衝區,返回錯誤碼為EAGAIN。 2.對端讀取了

嵌入式Linux網路程式設計,I/O多路複用,阻塞I/O模式阻塞I/O模式fcntl()/ioctl(),多路複用I/O select()/pselect()/poll(),訊號驅動I/O

文章目錄 1,I/O模型 2,阻塞I/O 模式 2.1,讀阻塞(以read函式為例) 2.2,寫阻塞 3,非阻塞模式I/O 3.1,非阻塞模式的實現(fcntl()函式、ioctl() 函式)

linux訪問並windows端共享資料夾

Windows端(win8.1): 右鍵點選資料夾-->共享-->特定使用者-->everyone-->新增-->許可權改為“讀取/寫入”-->點選共享 linux端(ubuntu16.04): 簡介:Windows共享資料夾使用的協議是SMB/CIFS。因

[Unity基礎]移動平臺的檔案

參考連結: http://www.cnblogs.com/murongxiaopifu/p/4199541.html?utm_source=tuicool#autoid-3-2-0 http://zhaolongchn.blog.163.com/blog/static/19

Socket的阻塞模式阻塞模式

來源:http://blog.csdn.net/VCSockets/阻塞模式   Windows套接字在阻塞和非阻塞兩種模式下執行I/O操作。在阻塞模式下,在I/O操作完成前,執行的操作函式一直等候而不會立即返回,該函式所在的執行緒會阻塞在這裡。相反,在非阻塞模式下,套接字函式會立即返回,而不管I/O是否完

Qtcsv的封裝

概述 csv檔案作為簡單的格式化文字檔案,隨著資料探勘和python的普及突然就又火起來了,工作中發現許多資料互動由原來的xml又改為通過csv檔案進行互動,csv檔案有個最大的缺點是單個單元格里不能出現換行,如果是單純的資料互動,csv的確是最簡單的格式化方