AndroidT(13) Log 系统 -- SocketListener 帮助类详解(六)

本文解析了SocketListener的工作原理,包括初始化、启动监听、处理客户端连接请求等过程,并详细介绍了其核心方法runListener的具体实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 概览

   因为 logd 中使用 socket 技术,logd 和 client 之间的通信使用的就是socket。所以例如 logdr/klogd的实现类都会涉及到socket的监听。本章 SocketListener 就是对这一部分可共用的功能的一个提炼。实际上它属于库 libsysutils,并不是仅仅使用在logd中。只要有socket监听需求的程序都可以使用它。

2. SocketListener 初始化

  SocketListener 提供了多个构造方法,

//code 1
SocketListener::SocketListener(const char *socketName, bool listen) {
    init(socketName, -1, listen, false);
}
//code 2
SocketListener::SocketListener(int socketFd, bool listen) {
    init(nullptr, socketFd, listen, false);
}
//code 3
SocketListener::SocketListener(const char *socketName, bool listen, bool useCmdNum) {
    init(socketName, -1, listen, useCmdNum);
}

  listen 的值决定当前 socket 是否支持建立新的 socket 链接,例如

SocketListener("/dev/socket/flagstaff", true)

    上面意味着对于 flagstaff 是支持作为服务端的,并且它是支持接收客户端发送过来的连接请求的,在建立连接后新的socket也是受 SocketListener 监听的。反之,这个 SocketListener 就只支持监听传入的这个 flagstaff 句柄了,当然此时的句柄就不能是socket了,一般是个普通文件句柄。
  从上面 SocketListener 的几个构造方法实现可知,最终都是调用 init 方法进行处理的。init 的实现也是非常的简单,只是对各个入参进行记录。

void SocketListener::init(const char *socketName, int socketFd, bool listen, bool useCmdNum) {
    mListen = listen;
    mSocketName = socketName;
    mSock = socketFd;
    mUseCmdNum = useCmdNum;
}

3. SocketListener 启动

  SocketListener 的使用非常的简单,只需要提供 socket 的 fd 或者绝对路径即可。在服务端准备接收来自所监听socket的的信息后后,即可调用 startListener 接口来启动监听。

//system\core\libsysutils\src\SocketListener.cpp
int SocketListener::startListener
    return startListener(4)
        //code 1
        if (mSocketName)
            mSock = android_get_control_socket(mSocketName)
        //code 2
        if (mListen && listen(mSock, backlog) < 0) {
            SLOGE("Unable to listen on socket (%s)", strerror(errno));
            return -1;
        } else if (!mListen)
            mClients[mSock] = new SocketClient(mSock, false, mUseCmdNum);
        //code 3
        pipe2(mCtrlPipe, O_CLOEXEC)
        //code 4
        pthread_create(&mThread, nullptr, SocketListener::threadStart, this)

   code 1 mSocketName 不为空一般意味着被只提供了被监socket的路径而非句柄,那么就要先打开给定路径的socket了,然后报错到mSock中去,供后面使用。
   code 2 如果是支持作为服务端的话,那么就要通过接口 listen 设置下该socket以允许其接收客户端的连接请求。否则创立SocketClient供后面以poll的方式监听该socket本身即可。
   code 3 创建一个管道用于接收控制请求,目前只支持shutdown请求,即监听退出

//system\core\libsysutils\src\SocketListener.cpp
void SocketListener::runListener()
    ...
    if (fds[0].revents & (POLLIN | POLLERR)) {
        char c = CtrlPipe_Shutdown;
        TEMP_FAILURE_RETRY(read(mCtrlPipe[0], &c, 1));
        if (c == CtrlPipe_Shutdown) {
            break;
        }
        continue;
    }
    ...

  在用户调用 SocketListener 实例的 stopListener 接口时,会往该管道中写入 CtrlPipe_Shutdown 其值为0,以期监听停止。

//system\core\libsysutils\src\SocketListener.cpp
#define CtrlPipe_Shutdown 0
int SocketListener::stopListener()
    char c = CtrlPipe_Shutdown;
    rc = TEMP_FAILURE_RETRY(write(mCtrlPipe[1], &c, 1));

   code 4,一切准备就绪后就可以启动专用线程开始监听并处理来自 socket 的请求及信息了。

4. socket 的监听及数据处理

4.1 threadStart 的调用流程

  接收客户端的连接请求、新增需要被监听的socket以及回调子类对socket数据的处理方法,都是在新线程中做的,thread 加上 poll 的这种方式在Android系统中可谓是无处不在。
  上面提到过初始化完成后,会启动一个新线程来工作。它的处理方法就是 SocketListener::threadStart

void *SocketListener::threadStart(void *obj)
    SocketListener *me = reinterpret_cast<SocketListener *>(obj);
    me->runListener();
    pthread_exit(nullptr);

  obj 就是在启动线程时传入的 SocketListener 实例,实际上 runListener 是为私有方法,所以它的子类是无法改变它的逻辑的,其中也是该线程的执行体,只有在监听不被需要的时候才会退出,最后调用 pthread_exit 退出线程,做一些清理操作。下面重点来看看 runListener 的具体任务

4.2 runListener – poll 对象确认

me->runListener();
while (true) {
    //code 1
    fds.reserve(2 + mClients.size());
    //code 2
    fds.push_back({.fd = mCtrlPipe[0], .events = POLLIN});
    if (mListen) fds.push_back({.fd = mSock, .events = POLLIN});
    //code 3
    for (auto pair : mClients) {
        // NB: calling out to an other object with mClientsLock held (safe)
        const int fd = pair.second->getSocket();
        if (fd != pair.first) SLOGE("fd mismatch: %d != %d", fd, pair.first);
        fds.push_back({.fd = fd, .events = POLLIN});
    }
}

  code 1 中轮询的个数除了客户端还预留了2个,我们用数组下标的形式代表他们的含义
    fds[0] 用于监听监听线程的管道。
    fds[1] 用于监听本socket,用于处理客户端的连接请求。
  code 2 中就是填充轮询数组了,对它们的监听事件为是否有数据写入。例如监听到 mCtrlPipe[0] 有数据写入意味着控制信号的到来。
  code 3 和 code 2没有什么本质不同,只不过它是用来将监听客户端加入poll监听队列中的。在刚刚开始 mClients 是为0的,只有 mSock 接收到客户端监听请求后,并建立连接以生成新的socket后,该无序图才需要被处理。

4.3 runListener – poll 方式

  poll 的方式是 -1 ,即一直等待被监听文件句柄的写入事件到来才返回。

while (true)
    int rc = TEMP_FAILURE_RETRY(poll(fds.data(), fds.size(), -1));

4.4 runListener – 线程控制命令处理

    ...
    if (fds[0].revents & (POLLIN | POLLERR)) {
        char c = CtrlPipe_Shutdown;
        TEMP_FAILURE_RETRY(read(mCtrlPipe[0], &c, 1));
        if (c == CtrlPipe_Shutdown) {//code 1
            break;
        }
        continue;
    }

  工具上面 4.2 节,revents的值记录着对于句柄到来的具体数据,它是以位的形式记录的,下面是对于的man手册

struct pollfd {
    int   fd;         /* file descriptor */
    short events;     /* requested events */
    short revents;    /* returned events */
};
The field revents is an output parameter, filled by the kernel with the events that actually occurred.  The bits returned in revents can include any of
those  specified in events, or one of the values POLLERR, POLLHUP, or POLLNVAL.  (These three bits are meaningless in the events field, and will be set
in the revents field whenever the corresponding condition is true.)

  从代码 code 1 处可知,目前也就只接受一种 CtrlPipe_Shutdown 命令。

4.5 runListener – 新增 client 连接处理

    ...
    if (mListen && (fds[1].revents & (POLLIN | POLLERR))) {
        int c = TEMP_FAILURE_RETRY(accept4(mSock, nullptr, nullptr, SOCK_CLOEXEC));
        mClients[c] = new SocketClient(c, true, mUseCmdNum);
    }

  在 mListen 是真的条件下,如果是 fds[1] 句柄来数据了,那么说明有客户端请求连接到本服务了。那么就调用 accept4 以处理该请求,如此也就和客户端建立了一个连接通道。在和客户端建立连接后 accept4 会返回新的 socket 句柄。在 SocketListener 中会使用 SocketClient 来描述一个 socket 文件句柄。

4.6 runListener – 客户端数据处理

    //code 1
    std::vector<SocketClient*> pending;
    const int size = fds.size();
    for (int i = mListen ? 2 : 1; i < size; ++i) {
        const struct pollfd& p = fds[i];
        if (p.revents & (POLLIN | POLLERR)) {
            auto it = mClients.find(p.fd);
            if (it == mClients.end()) {
                SLOGE("fd vanished: %d", p.fd);
                continue;
            }
            SocketClient* c = it->second;
            pending.push_back(c);
        }
    }
    //code 2
    for (SocketClient* c : pending) {
        if (!onDataAvailable(c)) {
            release(c, false);
        }
    }

  code 1 识别出那些客户端已经有数据写入了,如果有那么就将其加到 pending 中去,供后面处理。
  code 2 就很简单了,调用 onDataAvailable 遍历 pending 向量以处理每一个已写入客户端的数据。
  对于 onDataAvailable,那就是子类需要实现的了,子类也之应该关心这个否者用 SocketListener 这个工具类也就没有意义了。对于子类的具体数据,SocketListener 本身是不关心的,那些数据如何被处理,完全由重写了 onDataAvailable 的子类决定。下面是 onDataAvailable 的定义

//system\core\libsysutils\include\sysutils\SocketListener.h
protected:
    virtual bool onDataAvailable(SocketClient *c) = 0;

  可见它是一个纯虚函数而且是 protected 级别的,即它只允许它的子类来调用它。

5. 总结

  1) onDataAvailable 需要用户实现。
  2) onDataAvailable 不在调用者线程中,而是在 SocketClient 所管理的线程。
  3) stopListener 可以用于请求 onDataAvailable 所在线程退出,但是如果它在处理客户端的数据则是无法响应的。例如在 onDataAvailable 中block了,那么该线程也就无法退出了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值