文章目录
-
- [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_UNIX或AF_LOCALtype:套接字类型,常用SOCK_STREAM(可靠流式)或SOCK_SEQPACKET(可靠数据报)protocol:协议类型,必须为0sv:输出参数,返回两个相互连接的文件描述符
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实现了任意进程间的双向通信:
核心思路:
- 服务端创建socketpair,获得两个文件描述符
sv[0]和sv[1] - 服务端通过Binder将其中一个描述符(如
sv[0])传递给客户端 - 双方利用各自的描述符进行全双工通信
优势:
- 突破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系统中扮演着重要的角色,主要体现在:
-
输入系统核心通道:InputChannel通过socketpair高效传递触摸事件,避免了Binder的序列化开销
-
日志与调试:lshal等工具利用socketpair实现输出重定向
-
增强的IPC能力:结合Binder可突破socketpair仅限有亲缘关系进程的限制
-
状态检测:利用额外socketpair实现可靠的关闭状态通知
最佳实践建议:
- 优先用于同一应用内的父子进程通信
- 跨应用通信应使用Binder(安全且支持远程调用)
- 多客户端场景使用标准Unix Domain Socket
- 配合epoll实现多路复用,避免单线程阻塞
socketpair如同"进程间的专用电话线",两个进程直接通过预分配的通道通信,无需拨号或地址,是Android Framework层实现高效本地通信的"隐藏工具"。