基于libevent, libuv和android Looper不断演进socket编程

原创 2013年05月24日 17:33:57

最近在做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构架网络服务器的经典模式(三)



版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

基于libuv的TCP设计

本人一直在寻找一个跨平台的网络库,

网络库libevent、libev、libuv对比

Libevent、libev、libuv三个网络库,都是c语言实现的异步事件库Asynchronousevent library)。 异步事件库本质上是提供异步事件通知(Asynchronous E...

关于网络通信模型的剖析:libevent libev libuv asio

libevent libev libuv node.js   工具库和框架之间的区别,asio是被设计成一套工具库而不是框架。 什么是框架? 框架就是一套固定了编程结构的库,任何用户使用它...

libuv 与 libev 的对比

libuv是异步的,libev是同步的多路IO复用。 libev 是系统IO复用的简单封装,基本上来说,它解决了 epoll ,kqueuq 与 select 之间 API 不同的问题。保证...

从Jetty、Tomcat和Mina中提炼NIO构架网络服务器的经典模式(一)

如何正确使用NIO来构架网络服务器一直是最近思考的一个问题,于是乎分析了一下Jetty、Tomcat和Mina有关NIO的源码,发现大伙都基于类似的方式,我感觉这应该算是NIO构架网络服务器的经典模式...

探索WebKit内核(四)------ Inspector

最近在做WebOS的远程调试功能,效果如同Chrome for android和Safari for ios一样,具体可见:chrome: https://developers.google.com/...

开发者应当了解的WebKit知识

对一些开发者而言,WebKit就是一个黑盒子。丢进去HTML、CSS、JS等一连串的东西,而WebKit就能变魔术一般显示出一个很棒的网页出来。实际上,正我的同事IlyaGroriks提到的:   ...

值得推荐的C/C++框架和库 (真的很强大)

值得学习的C语言开源项目 - 1. Webbench Webbench是一个在linux下使用的非常简单的网站压测工具。它使用fork()模拟多个客户端同时访问我们设定的URL,测试网站在压力下工作...

走向???之路

家庭的原因搞得我近半年都处于比较浮躁的状态,技术博客一直没怎么更新,技术积累也没原来那么积极,很怀恋以前那种专注纯粹的学习状态。我现在遇到的问题估计是大众问题,从学校出来时,积极向上,做啥都激情满满,...

最近忙的头脑有点儿乱

从二月初到现在一直忙忙碌碌的,出差再出差,文档再文档,没有时间静下心来搞搞技术,不过这段时间抽空回了一趟学校,又体味了一下美好的校园时光,也算是一种补偿吧。这次北京这个项目真的好烦,原本计划做一个月的...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)