基於libevent, libuv和android Looper不斷演化socket編程

來源:互聯網
上載者:User

最近在做websocket  porting的工作中,需要實現最底層socket讀和寫,基於同步讀,libevent, libuv和android Looper都寫了一套,從中體會不少。

1)同步阻塞讀寫

最開始採用同步阻塞讀寫,主要是為了快速實現來驗證上層websocket協議的完備性。優點僅僅是實現起來簡單,缺點就是效率不高,不能很好利用線程的資源,建立串連這一塊方法都是類似的,主要的區別是在如何讀寫資料,先看幾種方法共用的一塊:

 

    int n = 0;    struct sockaddr_in serv_addr;    event_init();    if((mSockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){        //TODO error        return;    }    memset(&serv_addr, '0', sizeof(serv_addr));    serv_addr.sin_family = AF_INET;    serv_addr.sin_port = htons(url.port());    if(inet_pton(AF_INET, url.host().utf8().data(), &serv_addr.sin_addr)<=0){        return;    }    if( connect(mSockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0){        return;    }

這裡由於是client,所以比較簡單,當然缺失了DNS解析這一塊。然後,就是要監視讀資料,由於是同步阻塞讀,所以需要在迴圈裡不斷地去read/recv:

 

 

    while (1) {        ssize_t result = recv(fd, buf, sizeof(buf), 0);        if (result == 0) {            break;        } else if (result < 0) {            perror("recv");            close(fd);            return 1;        }        fwrite(buf, 1, result, stdout);    }

缺點就顯而易見,此線程需要不斷輪詢。當然,這裡是個例子程式,正式代碼中不會處理這麼草率。

 

2)libevent

對上面的改進方法就是基於非同步非阻塞的方式來處理讀資料,在linux上一般是通過epoll來做非同步事件偵聽,而libevent是一個封裝了epoll或其他平台上非同步事件的c庫,所以基於libevent來做非同步非阻塞讀寫會更簡單,也能跨平台。重構的第一個步是設定socketFD為非阻塞:

 

static int setnonblock(int fd){    int flags;    flags = fcntl(fd, F_GETFL);    if (flags < 0){        return flags;    }    flags |= O_NONBLOCK;    if (fcntl(fd, F_SETFL, flags) < 0){        return -1;    }    return 0;}

然後需要在單獨的線程中維護event loop,並添加read事件偵聽:

 

 

static void* loopListen(void *arg){    SocketStreamHandle *handle = (SocketStreamHandle *)arg;    struct event_base* base = event_base_new();    struct event ev_read;    handle->setReadEvent(&ev_read);    setnonblock(handle->getSocketFD());    event_set(&ev_read, handle->getSocketFD(), EV_READ|EV_PERSIST, onRead, handle);    event_base_set(base, &ev_read);    event_add(&ev_read, NULL);    event_base_dispatch(base);}
    pthread_t pid;    pthread_create(&pid, 0, loopListen, this);

然後在onRead方法中處理資料讀取:

 

static void onRead(int fd, short ev, void *arg){    while(true){        char *buf = new char[1024];        memset(buf, 0, 1024);        int len = read(fd, buf, 1024);        SocketStreamHandle *handle = (SocketStreamHandle *)arg;        if(len > 0){            SocketContext *context = new SocketContext;            context->buf = buf;            context->readLen = len;            context->handle = handle;            WTF::callOnMainThread(onReadMainThread, context);            if(len == 1024){                continue;            }else{                break;            }        }else{            if(errno == EAGAIN || errno == EWOULDBLOCK){                return;            }else if(errno == EINTR){                continue;            }            __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "onCloseMainThread, len:%d, errno:%d", len, errno);            WTF::callOnMainThread(onCloseMainThread, handle);            event_del(handle->getReadEvent());        }    }}

這裡比較有講究的是:

 

1)當一次buf讀不完,需要在迴圈裡再次讀一次

2)當read到0時,表示socket被關閉,這時需要刪除事件偵聽,不然會導致cpu 100%

3)當read到-1時,不完全是錯誤情況,比如errno == EAGAIN || errno == EWOULDBLOCK表示暫時不可讀,歇一會後面再讀。errno == EINTR表示被系統中斷,應重讀一遍

4)onRead是被libevent中專門做事件偵聽的線程調用的,所以有的時候需要回到主線程,比如: WTF::callOnMainThread(onReadMainThread, context);這裡就需要注意多線程間的同步問題。

3)libuv

libuv在libevent更進一步,它不但有event loop,並且把socket的各種操作也覆蓋了,所以代碼會更簡潔,比如最開始的建立串連和建立loop:

 

    uv_loop_t *loop = uv_default_loop();    uv_tcp_t client;    uv_tcp_init(loop, &client);    struct sockaddr_in req_addr = uv_ip4_addr(url.host().utf8().data(), url.port());    uv_connect_t *connect_req;    connect_req->data = this;    uv_tcp_connect(connect_req, &client, req_addr, on_connect);    uv_run(loop);

在on_connect中建立對read的監聽:

 

 

static void* on_connect(uv_connect_t *req, int status){    SocketStreamHandle *handle = (SocketStreamHandle *)arg;    uv_read_start(req->handle, alloc_buffer, on_read);}

on_read就和前面類似了。所以libuv是最強大的,極大的省略了socket相關的開發。

 

4)Android Looper

Android提供一套event loop的機制,並且可以對FD進行監聽,所以如果基於Android Looper,就可以省去對第三方lib的依賴。並且Android也是對epoll的封裝,既然如此,值得試一試用Android原生的looper來做這塊的event looper。socket串連這塊和最開始是一樣的,關鍵是在建立looper的地方:

 

static void* loopListen(void *arg){    SocketStreamHandle *handle = (SocketStreamHandle *)arg;    setnonblock(handle->getSocketFD());    Looper *looper = new Looper(true);    looper->addFd(handle->getSocketFD(), 0, ALOOPER_EVENT_INPUT, onRead, handle);    while(true){        if(looper->pollOnce(100) == ALOOPER_POLL_ERROR){            __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "ALOOPER_POLL_ERROR");            break;        }    }}

代碼比較簡單就不多說,詳細使用方法可以查看<utils/Looper.h>的API。

 

綜上所述,如果是在Android上做,可以直接基於原生的Looper,如果需要跨平台可以基於libuv。總之,要避免同步阻塞,因為這樣會導致線程設計上的複雜和低效。

在Java裡也有類似的概念,可以參見以前的博文:

 

從Jetty、Tomcat和Mina中提煉NIO構架網路伺服器的傳統模式(一)
從Jetty、Tomcat和Mina中提煉NIO構架網路伺服器的傳統模式(二)
從Jetty、Tomcat和Mina中提煉NIO構架網路伺服器的傳統模式(三)

 

 

 

 

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.