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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/vviccc/article/details/91383352

系列文章解读&说明:

Android Framework 输入子系统 的 分析主要分为以下部分:

(01)核心机制 inotify和epoll

(02)核心机制 双向通信(socketpair+binder)

(03)输入系统框架

(04)InputReader解读

(05)InputDispatcher线程

(06)APP建立联系

(07)activity window decor view

(08)InputStage简介

本模块分享的内容:核心机制socketpair

本章关键点总结 & 说明:

本章节主要关注➕ 以上思维导图中基础机制部分: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 的实现,这里实现代码如下:

#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);
	}
}

编译与测试:

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

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

3.1 组合通信模式与原理

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

  1. 客户端先发起请求
  2. 服务端收到请求,回复请求
  3. 客户端收到请求

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

  1. 服务端,初始化一次sokcetpair,得到两个句柄:socketfd[0],socketfd[1]。并将socketfd[1]加入到BnXXX端
  2. 客户端初始化,发送请求,将socketfd[1]通过binder传递文件描述符的方式,获取到客户端。
  3. 客户端使用fd,与服务端通信。接下来便实现 socketpair实现双向通信机制。

3.2 实现方式

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

@1 IHelloService.h代码如下:

#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代码如下:

#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代码如下:

#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端实现代码如下:

#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代码如下:

#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结构体。

猜你喜欢

转载自blog.csdn.net/vviccc/article/details/91383352