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

2023-11-07

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



本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

基于libevent, libuv和android Looper不断演进socket编程 的相关文章

  • Buck电路基础知识

    版权声明 本文为博主原创文章 遵循 CC 4 0 BY SA 版权协议 转载请附上原文出处链接和本声明 本文链接 https blog csdn net weixin 42005993 article details 120144144 这
  • Windows下libmodbus库的编译和使用

    一 前言 最近要搞一个PC端Qt上位机控制机械手的移动 需要用到串口io卡 控制的话需要使用libmodbus库 就想着自己编译一下libmodbus库 过程如下 二 编译过程 2 1 libmodbus的下载和安装 下载地址 https
  • pytorch7-可视化训练过程(过程中显示)

    import torch import torch nn as nn import torchvision import torchvision utils as vutils from torch optim import SGD imp
  • 安全连载——CSDN区块链大本营出品

    史上杀伤力最大的溢出型漏洞到底是什么 看这一篇就够了 第1期 4月发生的BEC事件以及SMT事件已经沉淀一段时间了 具体的情况也被多方媒体所报道 相关的漏洞根源问题也有很多大神团队的分析和指正 近日 有安全团队将各种已经发生或可能发生的类似

随机推荐

  • Marshaller和Unmarshaller用法示例

    import java io FileNotFoundException import java io FileOutputStream import java io OutputStream import javax xml bind J
  • Qt之QDialog禁用右上角关闭按钮

    setWindowFlags windowFlags Qt WindowCloseButtonHint
  • 【深度学习】笔记12:win10下的VS2013编辑代码的时候,非常卡顿,怎么样解决?

    给新电脑连续配置了三天环境 双系统下的caffe和NVIDIA环境配置好之后 终于可以看代码了 结果在vs2013下对代码进行注释的时候 代码编辑器用起来非常卡顿 这个问题的解决方法如下所示 1 首先确定是不是硬件和系统的问题 据说win8
  • 2.4.1 C# 和 F# 中的类型推断

    2 4 1 C 和 F 中的类型推断 大多数的类型有简称 例如 int 或 Random 只有很少一部分需要类型推断 因为手写类型名称并不困难 C 2 0 支持泛型 因此 可以构造更复杂的类型 在函数语言中的类型 像 F 是相当复杂的 尤其
  • R语言legend函数参数详解

    legend x y NULL legend fill NULL col par col border black lty lwd pch angle 45 density NULL bty o bg par bg box lwd par
  • Scala学习系列(二)——环境安装配置

    Scala下载地址 https www scala lang org download 一 安装JDK 首先 因为Scala是运行在JVM平台上的 所以安装Scala之前要安装JDK 二 二进制安装方式 我们可以直接用二进制安装Scala
  • 合并BPL包图文教程

    Delphi IDE 本身就是一个插件模式的工具 插件的好处不用多说 运行包的BPL 其实就是众多单元的集合 因此可以再次重新组合 只要你将各个BPL包用到的单元再组合一次 本文以 http code google com p tangra
  • libuv源码分析(1)事件循环分析

    前言 libuv总是报出一些让人难以理解的错误 作为一个C的项目 不具有Java JavaScript php那样的人气 很难百度到一些问题的答案 甚至google也不行 为了用好libuv 也为了学习吧 我开始看libuv的源码 不知道自
  • 【正点原子STM32连载】 第三十一章 睡眠模式实验 摘自【正点原子】APM32F407最小系统板使用指南

    1 实验平台 正点原子stm32f103战舰开发板V4 2 平台购买地址 https detail tmall com item htm id 609294757420 3 全套实验源码 手册 视频下载地址 http www openedv
  • 《机器学习》周志华(西瓜书)学习笔记 第二章 模型评估与选择

    机器学习 周志华 学习笔记 总目录 世上只有一种投资是只赚不赔的 那就是学习 当你的的能力还驾驭不了你的目标时 就应该沉下心来历练 当你的才华撑不起你的野心时 就应该静下心来学习 第二章 模型评估与选择 2 1 经验误差与过拟合 错误率 E
  • 11 【标准库之JSON对象 JSON5】

    13 JSON 对象 13 1 JSON 格式 JSON 格式 JavaScript Object Notation 的缩写 是一种用于数据交换的文本格式 2001年由 Douglas Crockford 提出 目的是取代繁琐笨重的 XML
  • 递归视角下

    def listSum numbers if not numbers return 0 else f rest numbers return f listSum rest myList 1 2 3 4 None total listSum
  • 在存储过程中IN传入参数无效的解决方法

    列 SELECT count COUNT sysid FROM Student WHERE Id IN Sysids 想要得到count 但是在in的时候 传入的参数无效 得到的结果只能为0 这时候可以将其转化成一条变量语句 然后去执行这条
  • websocket即时通讯

    目录 一 websocket简介 二 背景 三 优点 1 控制开销 2 实时性更强 3 保持连接状态 4 更好的二进制支持 5 支持扩展和更好的实现压缩效果 四 原理 1 客户端 服务器建立TCP连接 三次握手 2 TCP连接成功后 客户端
  • PANet[详解]

    一 Abstract摘要 Introduction介绍 Abstract 信息在神经网络中的传播方式非常重要 本文提出了一种基于提议的实例分割框架下的路径聚合网络Path Aggregation Network PANet 旨在促进信息的流
  • SQL Server 2019 Express的安装

    SQL Server 2019 Express的安装 下载安装程序 SQL Server 2019 Express 安装SQL Server 双击运行SQL2019 SSEI Expr exe 运行下载程序 选择自定义 点击安装 稍等几分钟
  • 小程序消息推送配置 Token校验失败,请检查确认

    一 小程序消息推送配置 Token校验失败 请检查确认 添加配置失败 原因 只要是没有echostr原样返回 就会报错 注意 返回的事echostr的内容 而不是key value格式 示例如下 return Content reqData
  • 大数据数据湖技术Hudi0.12.0版本源码编译

    0 介绍 Apache Hudi Hadoop Upserts Delete and Incremental 是下一代流数据湖平台 Apache Hudi将核心仓库和数据库功能直接引入数据湖 Hudi提供了表 事务 高效的upserts d
  • api的封装

    这是以 cnode中文社区的api 为例 值得一提的是有些请求可能要先进行url的编码 这是简易版的 use strict api 路径 get topics 主题首页 get topic id 主题详情 post accesstoken
  • 基于libevent, libuv和android Looper不断演进socket编程

    最近在做websocket porting的工作中 需要实现最底层socket读和写 基于同步读 libevent libuv和android Looper都写了一套 从中体会不少 1 同步阻塞读写 最开始采用同步阻塞读写 主要是为了快速实