Android Framework 输入子系统(02)核心机制 双向通信

本章关键点总结 & 说明:

本章节主要关注 以上思维导图中基础机制部分:

socketpair和socketpair+binder部分,主要对socketpair机制进行解读,同时解读了如何和binder机制结合使用。最后解读了下 文件描述符的传递基本原理。

1 socketpair机制的解读

socketpair创建了一对无名的套接字描述符(只能在AF_UNIX域中使用),描述符存储于一个二元数组 s[2] .这对套接字可以进行双工通信,每一个描述符既可以读也可以写。这个在同一个进程中也可以进行通信,向s[0]中写入,就可以从s[1]中读取(只能从s[1]中读取),也可以在s[1]中写入,然后从s[0]中读取;但是,若没有在0端写入,而从1端读取,则1端的读取操作会阻塞,即使在1端写入,也不能从1读取,仍然阻塞;

与pipe的区别:pipe是单工通信,一端要么是读端要么是写端,而socketpair实现了双工套接字,也就没有所谓的读端和写端的区分。

2 socketpair实现案例

该案例参考了frameworks\native\libs\input\InputTransport.cpp 的实现,这里实现代码如下:

C++
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#define SOCKET_BUFFER_SIZE (32768U)

void function_thread1 (void arg)
{
int fd = (int)arg;
char buf[500];
int len;
int cnt = 0;
while (1)
{
// 向main线程发出: Hello, main thread
len = sprintf(buf, "Hello, main thread, cnt = %d", cnt++);
write(fd, buf, len);

// 读取数据(main线程发回的数据)
len = read(fd, buf, 500);
buf[len] = '\0';
printf("%s\n", buf);
sleep(5);
}
return NULL;
}

int main(int argc, char *argv)
{
int sockets[2];
socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets);

int bufferSize = SOCKET_BUFFER_SIZE;
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));

// 创建子线程1
pthread_t threadID;
pthread_create(&threadID, NULL, function_thread1, (void )sockets[1]);

char buf[500];
int len;
int cnt = 0;
int fd = sockets[0];

while(1)
{
len = read(fd, buf, 500);
buf[len] = '\0';
printf("%s\n", buf); 
len = sprintf(buf, "Hello, thread1, cnt = %d", cnt++);
write(fd, buf, len);
}
}

编译与测试:

Bash
gcc -o socketpair socketpair.c -lpthread 
./socketpair

3 socketpair与binder机制组合双向通信

3.1 组合通信模式与原理

binder作为android的核心通信机制,它的通信模式是:

客户端先发起请求

服务端收到请求,回复请求

客户端收到请求

这样的机制对于双向通信确是个弊端,因为必须客户端先发起请求,而不能服务端直接给客户端发送请求,如果想要实现双向通信就需要2个binder服务的C/S架构,而这样就过于繁琐了。因此android中使用了socketpair+binder这样的组合通信机制。原理如下:

服务端,初始化一次sokcetpair,得到两个句柄:socketfd[0],socketfd[1]。并将socketfd[1]加入到BnXXX端

客户端初始化,发送请求,将socketfd[1]通过binder传递文件描述符的方式,获取到客户端。

客户端使用fd,与服务端通信。接下来便实现 socketpair实现双向通信机制。

3.2 实现方式

这里以之前的C++层binder 为例来展开 socketpair与binder的组合使用,这里仅关注与本章节相关代码。

@1 IHelloService.h代码如下:

C++
#ifndef ANDROID_IHELLOERVICE_H
#define ANDROID_IHELLOERVICE_H

#include <utils/Errors.h> // for status_t
#include <utils/KeyedVector.h>
#include <utils/RefBase.h>
#include <utils/String8.h>
#include <binder/IInterface.h>
#include <binder/Parcel.h>

#define HELLO_SVR_CMD_SAYHELLO 1
#define HELLO_SVR_CMD_SAYHELLO_TO 2
#define HELLO_SVR_CMD_GET_FD 3

namespace android {
class IHelloService: public IInterface
{
public:
DECLARE_META_INTERFACE(HelloService);
//...
virtual int get_fd(void) = 0;
};

class BnHelloService: public BnInterface<IHelloService>
{
private:
int fd;
public:
virtual status_t onTransact( uint32_t code,
const Parcel& data,
Parcel* reply,
uint32_t flags = 0);
//...
virtual int get_fd(void);
BnHelloService(int fd);
};
}

#endif

@2 BnHelloService代码如下:

C++
#define LOG_TAG "HelloService"
#include "IHelloService.h"

namespace android {
BnHelloService::BnHelloService(int fd)
{
this->fd = fd;
}

status_t BnHelloService::onTransact( uint32_t code,
const Parcel& data,
Parcel* reply,
uint32_t flags)
{
switch (code) {
case HELLO_SVR_CMD_GET_FD: {
int fd = this->get_fd();
reply->writeInt32(0); /* no exception /

/ 参考:
* frameworks\base\core\jni\android_view_InputChannel.cpp
* android_view_InputChannel_nativeWriteToParcel
*/
reply->writeDupFileDescriptor(fd);
return NO_ERROR;
} break;


default:
return BBinder::onTransact(code, data, reply, flags);
}
}
//...
int BnHelloService::get_fd(void)
{
return fd;
}
}

@3 BpHelloService.cpp代码如下:

C++
#include "IHelloService.h"
namespace android {
class BpHelloService: public BpInterface<IHelloService>
{
public:
BpHelloService(const sp<IBinder>& impl)
: BpInterface<IHelloService>(impl)
{
}
//...
int get_fd(void)
{
/* 构造/发送数据 */
Parcel data, reply;
int exception;

data.writeInt32(0);
data.writeString16(String16("IHelloService"));

remote()->transact(HELLO_SVR_CMD_GET_FD, data, &reply);

exception = reply.readInt32();
if (exception)
return -1;
else
{
int rawFd = reply.readFileDescriptor();
return dup(rawFd);
}
}
};
IMPLEMENT_META_INTERFACE(HelloService, "android.media.IHelloService");
}

@4 test_server端实现代码如下:

C++
#define LOG_TAG "TestService"
#include <fcntl.h>
#include <sys/prctl.h>
#include <sys/wait.h>
#include <binder/IPCThreadState.h>
#include <binder/ProcessState.h>
#include <binder/IServiceManager.h>
#include <cutils/properties.h>
#include <utils/Log.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/socket.h>

#include "IHelloService.h"
#define SOCKET_BUFFER_SIZE (32768U)
using namespace android;

class MyThread: public Thread { 
private:
int fd;
public: 
MyThread() {}
MyThread(int fd) { this->fd = fd; }

//如果返回true,循环调用此函数,返回false下一次不会再调用此函数 
bool threadLoop()
{
char buf[500];
int len;
int cnt = 0;

while(1)
{
/* 读数据: test_client发出的数据 */
len = read(fd, buf, 500);
buf[len] = '\0';
ALOGI("%s\n", buf);

/ 向 test_client 发出: Hello, test_client /
len = sprintf(buf, "Hello, test_client, cnt = %d", cnt++);
write(fd, buf, len);
}

return true; 
}

}; 

int main(void)
{

int sockets[2];
socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets);
int bufferSize = SOCKET_BUFFER_SIZE;
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));

/* 创建一个线程, 用于跟test_client使用socketpiar通信 */
sp<MyThread> th = new MyThread(sockets[0]);
th->run(); 

sp<ProcessState> proc(ProcessState::self());
sp<IServiceManager> sm = defaultServiceManager();
sm->addService(String16("hello"), new BnHelloService(sockets[1]));
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
return 0;
}

@5 test_client代码如下:

C++
#define LOG_TAG "TestService"
#include <fcntl.h>
#include <sys/prctl.h>
#include <sys/wait.h>
#include <binder/IPCThreadState.h>
#include <binder/ProcessState.h>
#include <binder/IServiceManager.h>
#include <cutils/properties.h>
#include <utils/Log.h>
#include <unistd.h>

#include "IHelloService.h"
using namespace android;

// ./test_client <readfile>
int main(int argc, char *argv)
{
int cnt;

if (argc < 2){
ALOGI("Usage:\n");
ALOGI("%s <readfile>\n", argv[0]);
return -1;
}

sp<ProcessState> proc(ProcessState::self());
sp<IServiceManager> sm = defaultServiceManager();

if (strcmp(argv[1], "hello") == 0)
{
//...
}
else if (strcmp(argv[1], "readfile") == 0)
{

sp<IBinder> binder = sm->getService(String16("hello"));

if (binder == 0)
{
ALOGI("can't get hello service\n");
return -1;
}
sp<IHelloService> service = interface_cast<IHelloService>(binder);
int fd = service->get_fd();
ALOGI("client call get_fd = %d", fd);

char buf[500];
int len;
int cnt = 0;
while (1)
{
/* 向 test_server 进程发出: Hello, test_server */
len = sprintf(buf, "Hello, test_server, cnt = %d", cnt++);
write(fd, buf, len);

/* 读取数据(test_server进程发回的数据) */
len = read(fd, buf, 500);
buf[len] = '\0';
ALOGI("%s\n", buf);
sleep(5);
}
}
return 0;
}

这里整个实现模式就是参考之前的binder C++层实现方式。binder通信机制在这里更多的是传递 socket 文件句柄的作用,再配合socketpair机制,就完成了 两个并无之间关系的进程间通过socketpair进行双向通信了。

4 简述 binder的fd 传递原理

这里的原理是:传递 文件描述符时,并不是直接传递文件描述符的值;而是先获取 对应的task_struct的files_struct结构体,之后再在客户端创建一个新的文件描述符指向同一个task_struct的files_struct结构体。

原文链接:,转发请注明来源!