Android系统中 socketpair 的源码解读与应用分析小结

文章目录

    • [1. socketpair概述](#1. socketpair概述)
    • [2. Android源码中的socketpair应用](#2. Android源码中的socketpair应用)
      • [2.1 InputChannel:输入系统的通信通道](#2.1 InputChannel:输入系统的通信通道)
      • [2.2 lshal中的PipeRelay:日志重定向](#2.2 lshal中的PipeRelay:日志重定向)
      • [2.3 ParcelFileDescriptor增强:关闭事件检测](#2.3 ParcelFileDescriptor增强:关闭事件检测)
      • [2.4 Binder + socketpair混合通信](#2.4 Binder + socketpair混合通信)
    • [3. socketpair与普通socket的对比](#3. socketpair与普通socket的对比)
    • [4. 实际运用案例](#4. 实际运用案例)
      • [4.1 父子进程通信Demo](#4.1 父子进程通信Demo)
      • [4.2 同一进程内线程间通信](#4.2 同一进程内线程间通信)
    • [5. 总结](#5. 总结)

1. socketpair概述

socketpair是Linux提供的一种轻量级进程间通信(IPC)机制,用于创建一对匿名的、相互连接的套接字文件描述符。与传统的socket不同,socketpair创建时即已建立连接,无需bind、listen、connect等步骤,特别适合有亲缘关系的进程间 (父子进程)或同一进程内线程间的全双工通信。

函数原型

c 复制代码
#include <sys/types.h>
#include <sys/socket.h>

int socketpair(int domain, int type, int protocol, int sv[2]);

参数说明:

  • domain:协议族,必须为AF_UNIXAF_LOCAL
  • type:套接字类型,常用SOCK_STREAM(可靠流式)或SOCK_SEQPACKET(可靠数据报)
  • protocol:协议类型,必须为0
  • sv:输出参数,返回两个相互连接的文件描述符

2. Android源码中的socketpair应用

2.1 InputChannel:输入系统的通信通道

在Android输入系统中,socketpair被用于建立应用进程与SystemServer(InputDispatcher)之间的输入事件通道

源码路径及关键调用链

1. 应用端创建InputChannel

ViewRootImpl.setView()中,应用创建空的InputChannel对象:

java 复制代码
// frameworks/base/core/java/android/view/ViewRootImpl.java
public void setView(View view, ...) {
    synchronized (this) {
        if (mView == null) {
            // 创建客户端InputChannel对象
            if ((mWindowAttributes.inputFeatures
                    & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                mInputChannel = new InputChannel();
            }
            // 通过Binder调用WMS
            res = mWindowSession.addToDisplay(..., mInputChannel);
            
            // 创建输入事件接收器
            if (mInputChannel != null) {
                mInputEventReceiver = new WindowInputEventReceiver(
                    mInputChannel, Looper.myLooper());
            }
        }
    }
}

2. WMS端创建socketpair对

WindowManagerService收到请求后,调用openInputChannelPair创建双向通道:

java 复制代码
// frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
public int addWindow(...) {
    if (outInputChannel != null && 
        (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
        String name = win.makeInputChannelName();
        // 关键:创建InputChannel对
        InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
        win.setInputChannel(inputChannels[0]);      // 服务端通道
        inputChannels[1].transferTo(outInputChannel); // 客户端通道传回应用
        mInputManager.registerInputChannel(win.mInputChannel, win.mInputWindowHandle);
    }
}

3. Native层实现socketpair

InputChannel.openInputChannelPair()最终调用Native方法,核心是socketpair()系统调用:

cpp 复制代码
// frameworks/base/core/jni/android_view_InputChannel.cpp
static jobjectArray android_view_InputChannel_nativeOpenInputChannelPair(
    JNIEnv* env, jclass clazz, jstring nameObj) {
    
    // 调用C++层的openInputChannelPair
    status_t result = InputChannel::openInputChannelPair(name, serverChannel, clientChannel);
    // 返回两个InputChannel对象给Java层
}

// frameworks/native/libs/input/InputTransport.cpp
status_t InputChannel::openInputChannelPair(const String8& name,
        sp<InputChannel>& outServerChannel, 
        sp<InputChannel>& outClientChannel) {
    int sockets[2];
    
    // 核心:创建socketpair
    if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)) {
        return -errno;
    }
    
    // 设置缓冲区大小
    int bufferSize = SOCKET_BUFFER_SIZE;  // 32KB
    setsockopt(sockets[0], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
    setsockopt(sockets[0], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
    setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
    setsockopt(sockets[1], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
    
    // 封装为InputChannel对象
    outServerChannel = new InputChannel(name + " (server)", sockets[0]);
    outClientChannel = new InputChannel(name + " (client)", sockets[1]);
    return OK;
}

4. 通信流程

  • 服务端(InputDispatcher)持有sockets[0],负责将输入事件写入通道
  • 客户端(应用进程)持有sockets[1],通过InputEventReceiver读取事件
  • 通道支持全双工通信,应用也可通过同一通道向系统发送反馈

2.2 lshal中的PipeRelay:日志重定向

在Android的lshal工具中,socketpair被用于将HAL层的输出重定向到指定输出流:

cpp 复制代码
// frameworks/native/cmds/lshal/PipeRelay.h
namespace android {
namespace lshal {

/* Creates an AF_UNIX socketpair and spawns a thread that relays any data
 * written to the "write"-end of the pair to the specified output stream "os".
 */
struct PipeRelay {
    explicit PipeRelay(std::ostream &os);
    ~PipeRelay();
    
    int fd() const;  // 返回"写端"文件描述符
    
private:
    int mFds[2];
    sp<RelayThread> mThread;  // 独立线程转发数据
};

}  // namespace lshal
}  // namespace android

设计特点

  • 创建socketpair后,启动独立线程将一端的数据转发到ostream
  • 调用者只需获取fd()向其中写入数据,即可自动输出到目标流
  • 利用了socketpair的全双工特性实现高效的数据中转

2.3 ParcelFileDescriptor增强:关闭事件检测

Google工程师Jeff Sharkey在提交中描述了socketpair的另一个巧妙应用:检测管道或socket对端的关闭状态

"When reading from the end of a pipe or socket, there is no way to tell if the other end has finished successfully, encountered an error, or outright crashed. To solve this, we create a second socketpair() as a communication channel between the two ends..."

解决方案 :创建额外的socketpair作为"控制通道",用于传输关闭状态码。当对端通过closeWithError()发送状态信息后,读端在遇到EOF时调用checkError()即可准确判断关闭原因(正常完成、发生错误、进程崩溃等)。

2.4 Binder + socketpair混合通信

有开发者结合Binder和socketpair实现了任意进程间的双向通信:

核心思路

  1. 服务端创建socketpair,获得两个文件描述符sv[0]sv[1]
  2. 服务端通过Binder将其中一个描述符(如sv[0])传递给客户端
  3. 双方利用各自的描述符进行全双工通信

优势

  • 突破Binder单向调用的限制,实现真正的双向数据传输
  • 避免socketpair只能用于有亲缘关系进程的限制
  • 结合Binder的跨进程能力和socketpair的高效传输特性

3. socketpair与普通socket的对比

维度 socketpair 普通socket(Unix Domain Socket)
地址依赖 无需地址(匿名) 需要文件路径(如/dev/socket/name
连接方式 创建即连通,无需bind/listen/connect 需显式调用bind、listen、connect/accept
适用场景 有亲缘关系的进程/线程间通信 任意进程间通信(通过文件路径)
安全控制 依赖进程间信任 可通过文件权限和SELinux控制
多客户端支持 不支持,仅限一对一 支持多客户端连接服务端
创建开销 小(单次调用) 较大(需完整建立连接)

核心本质 :socketpair是Unix Domain Socket在有亲缘关系进程间快捷方式,省去了连接建立步骤,创建即连通。

4. 实际运用案例

4.1 父子进程通信Demo

c 复制代码
#include <sys/socket.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

int main() {
    int sv[2];
    pid_t pid;
    
    // 创建socketpair
    socketpair(AF_UNIX, SOCK_STREAM, 0, sv);
    
    pid = fork();
    if (pid > 0) {  // 父进程
        close(sv[1]);  // 关闭读端
        const char* msg = "Hello from Parent";
        write(sv[0], msg, strlen(msg));
        
        char buf[128];
        read(sv[0], buf, sizeof(buf));
        printf("Parent received: %s\n", buf);
        close(sv[0]);
    } else if (pid == 0) {  // 子进程
        close(sv[0]);  // 关闭写端
        char buf[128];
        read(sv[1], buf, sizeof(buf));
        printf("Child received: %s\n", buf);
        
        write(sv[1], "Hello from Child", 16);
        close(sv[1]);
    }
    return 0;
}

4.2 同一进程内线程间通信

c 复制代码
#include <pthread.h>
#include <sys/socket.h>
#include <unistd.h>
#include <stdio.h>

int sv[2];

void* thread_func(void* arg) {
    char buf[128];
    read(sv[1], buf, sizeof(buf));
    printf("Thread received: %s\n", buf);
    write(sv[1], "Reply from thread", 17);
    return NULL;
}

int main() {
    socketpair(AF_UNIX, SOCK_STREAM, 0, sv);
    
    pthread_t thread;
    pthread_create(&thread, NULL, thread_func, NULL);
    
    write(sv[0], "Hello from main", 15);
    char buf[128];
    read(sv[0], buf, sizeof(buf));
    printf("Main received: %s\n", buf);
    
    pthread_join(thread, NULL);
    close(sv[0]);
    close(sv[1]);
    return 0;
}

5. 总结

socketpair在Android系统中扮演着重要的角色,主要体现在:

  1. 输入系统核心通道:InputChannel通过socketpair高效传递触摸事件,避免了Binder的序列化开销

  2. 日志与调试:lshal等工具利用socketpair实现输出重定向

  3. 增强的IPC能力:结合Binder可突破socketpair仅限有亲缘关系进程的限制

  4. 状态检测:利用额外socketpair实现可靠的关闭状态通知

最佳实践建议

  • 优先用于同一应用内的父子进程通信
  • 跨应用通信应使用Binder(安全且支持远程调用)
  • 多客户端场景使用标准Unix Domain Socket
  • 配合epoll实现多路复用,避免单线程阻塞

socketpair如同"进程间的专用电话线",两个进程直接通过预分配的通道通信,无需拨号或地址,是Android Framework层实现高效本地通信的"隐藏工具"。

相关推荐
splage2 小时前
Java进阶——IO 流
java·开发语言·python
always_TT3 小时前
从Python_Java转学C语言需要注意什么?
java·c语言·python
FL4m3Y4n3 小时前
MySQL索引原理与SQL优化
android·sql·mysql
一招定胜负3 小时前
课堂教学质量综合评分系统
java·linux·前端
Hui Baby3 小时前
spring优雅释放资源
java·spring
启山智软3 小时前
【启山智软智能商城系统技术架构剖析】
java·前端·架构
一线大码3 小时前
Java 使用国密算法实现数据加密传输
java·spring boot·后端
我命由我123453 小时前
Android Gradle - Gradle 自定义插件(Build Script 自定义插件、buildSrc 自定义插件、独立项目自定义插件)
android·java·java-ee·kotlin·android studio·android-studio·android runtime