设为首页 收藏本站
查看: 797|回复: 0

[经验分享] 基于libevent, libuv和android Looper不断演进socket编程

[复制链接]

尚未签到

发表于 2017-2-28 10:16:49 | 显示全部楼层 |阅读模式
  最近在做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构架网络服务器的经典模式(三)

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-348293-1-1.html 上篇帖子: OSGi系列 下篇帖子: 新浪云部署java web程序 注意事项
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表