Android Framework实战开发-socketpair介绍及它在android系统源码使用分析

Posted Android高级知识分享官

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android Framework实战开发-socketpair介绍及它在android系统源码使用分析相关的知识,希望对你有一定的参考价值。

csdn在线学习课程,课程咨询答疑和新课信息:QQ交流群:422901085进行课程讨论

android跨进程通信实战视频课程(加群获取优惠)
Android Framework实战开发-socketpair介绍及它在android系统源码使用分析

1.socketpair介绍

在linux下,使用socketpair函数能够创建一对套节字进行进程间通信(IPC)。

函数原形:
#include <sys/types.h>
#include <sys/socket.h>

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

参数1(domain):表示协议族,在Linux下只能为AF_LOCAL或者AF_UNIX。(自从Linux 2.6.27后也支持SOCK_NONBLOCK和SOCK_CLOEXEC)
参数2(type):表示协议,可以是SOCK_STREAM或者SOCK_DGRAM。SOCK_STREAM是基于TCP的,而SOCK_DGRAM是基于UDP的
参数3(protocol):表示类型,只能为0
参数4(sv[2]):套节字柄对,该两个句柄作用相同,均能进行读写双向操作
返回结果: 0为创建成功,-1为创建失败,并且errno来表明特定的错误号,具体错误号如下所述:

EAFNOSUPPORT:本机上不支持指定的address。

EFAULT: 地址sv无法指向有效的进程地址空间内。

EMFILE: 已经达到了系统限制文件描述符,或者该进程使用过量的描述符。

EOPNOTSUPP:指定的协议不支持创建套接字对。

EPROTONOSUPPORT:本机不支持指定的协议。

2、出现背景及与socket对比优势

大家明显原来的socket是一种c/s模型,需要客户端与服务端进行连接,连接还需要知道服务端的ip,unix socket就是对应的unix的路径。连接后当然也完全可以实现服务端和客户端的双向通信,这种情况一般比较适合于很多个客户端对应一个服务的情况。但是如果说本身就只是单独2个进程进行相互通信的需求socketpair这个东西出现就显示更加简单一些,不要有客户端和服务端进行connect过程,也没有服务端的一个ip和unix socket路径的概念,它完全是匿名的,在创建socketpair初就已经确定了对应的2个socket的fd,两端直接与对应fd读写既可以。

3、实战demo代码开发

#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#define SOCKET_BUFFER_SIZE      (32768U)
int main ()

    int fd[2];
    int bufferSize = SOCKET_BUFFER_SIZE;
    int r = socketpair( AF_UNIX, SOCK_STREAM, 0, fd );
    if ( r < 0 ) 
        perror( "socketpair()" );
        exit( 1 );
    
 /*设置socket描述符的选项*/
    setsockopt(fd[0], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
    setsockopt(fd[0], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
    setsockopt(fd[1], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
    setsockopt(fd[1], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));

    if ( fork() ) 
        /* Parent process: echo client */
        int val = 0;
        close( fd[1] );
        while ( 1 ) 
            sleep( 1 );
            ++val;
            printf( "parent Sending data: %d\\n", val );
            write( fd[0], &val, sizeof(val) );
            read( fd[0], &val, sizeof(val) );
            printf( "parent Data received: %d\\n", val );

        
    
    else 
        /* Child process: echo server */
        int val ;
        close( fd[0] );
        while ( 1 ) 
            read( fd[1], &val, sizeof(val) );
            printf( "son Data received: %d\\n", val );
            ++val;
            write( fd[1], &val, sizeof(val) );
            printf( "son send received: %d\\n", val );
        
    

编译: gcc socketpair_2_process.c -o socketpair_2_process
执行打印结果:

注意: socketpair创建的只适用于父子进程或者线程间通信,不能用于两个进程之间通信。如果要实现两个进程之间的双向通信,则需要将socketpair创建的一个描述符fd发送给另一个进程。

4、android源码中对它的使用(这里我们不对某个模块进行详细分析,只是对使用到的socket相关的代码进行讲解)

首先来了解一下android触摸事件是怎么到我们app的,先看app的ViewRootImpl类:

  public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) 
        synchronized (this) 
            if (mView == null) 
                。。省略
                if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) 
                    mInputChannel = new InputChannel();
                
                。。省略
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
                 catch (RemoteException e) 
                   。。省略
               
    

这里我们省略绝大部分代码,这里可以看出,app层面new InputChannel后然后调用mWindowSession.addToDisplay方法,注意哦这里方法是个跨进程通信方法:

这里可以看出我们传递了一个InputChannel实际上是由服务端来进行修改哦,客户端app这边只是刚开始搞了一个空壳。。,这里就是我们binder的中级部分的知识,是不是学过binder后看起这个代码来就瞬间秒懂
接下来我们看服务端的addToDisplay实现:

   @Override
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
            Rect outOutsets, InputChannel outInputChannel) 
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
                outContentInsets, outStableInsets, outOutsets, outInputChannel);
    

这里其实调用是WindowManagerService的addWindow

public int addWindow(Session session, IWindow client, int seq,
            WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
            Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
            InputChannel outInputChannel) 
       。。省略
            final WindowState win = new WindowState(this, session, client, token, parentWindow,
                    appOp[0], seq, attrs, viewVisibility, session.mUid,
                    session.mCanAddInternalSystemWindow);
            

            if  (openInputChannels) 
                win.openInputChannel(outInputChannel);
            
。。省略
        return res;
    

这里调用的openInputChannel

void openInputChannel(InputChannel outInputChannel) 
        if (mInputChannel != null) 
            throw new IllegalStateException("Window already has an input channel.");
        
        String name = getName();
        InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
        mInputChannel = inputChannels[0];
        mClientChannel = inputChannels[1];
        mInputWindowHandle.inputChannel = inputChannels[0];
        if (outInputChannel != null) 
            mClientChannel.transferTo(outInputChannel);
            mClientChannel.dispose();
            mClientChannel = null;
         else 。。省略
        
        mService.mInputManager.registerInputChannel(mInputChannel, mInputWindowHandle);
    

这里重点优势调用了InputChannel这个静态方法的openInputChannelPair,最后会调用到native端nativeOpenInputChannelPair:

static jobjectArray android_view_InputChannel_nativeOpenInputChannelPair(JNIEnv* env,
        jclass clazz, jstring nameObj) 
。。省略
    status_t result = InputChannel::openInputChannelPair(name, serverChannel, clientChannel);
。。省略
    return channelPair;

这里又调用到了InputChannel::openInputChannelPair,但这个是native的c++代码,openInputChannelPair代码实现如下:

status_t InputChannel::openInputChannelPair(const String8& name,
        sp<InputChannel>& outServerChannel, sp<InputChannel>& outClientChannel) 
    int sockets[2];
    if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)) 
        status_t result = -errno;
        ALOGE("channel '%s' ~ Could not create socket pair.  errno=%d",
                name.string(), errno);
        outServerChannel.clear();
        outClientChannel.clear();
        return result;
    

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

    String8 serverChannelName = name;
    serverChannelName.append(" (server)");
    outServerChannel = new InputChannel(serverChannelName, sockets[0]);

    String8 clientChannelName = name;
    clientChannelName.append(" (client)");
    outClientChannel = new InputChannel(clientChannelName, sockets[1]);
    return OK;

看到这里大家是不很熟悉??这个不就是socketpair么。。
这里就相当于创建socketpair第一个fd给服务端InputDispather,另一个呢给客户端,当然这里因为给客户端是直接binder跨进程通信的,就是前面开始我们看到的那个addToDisplay方法里面的最后一个参数

那么接下来客户端干了什么呢?
在setView中会创建对应WindowInputEventReceiver:

  if (mInputChannel != null) 
              。。省略
                    mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
                            Looper.myLooper());
                

这里把我们wms返回的mInputChannel传递给了WindowInputEventReceiver,它的构造方法会调用super的构造:

   public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) 
            super(inputChannel, looper);
        

super对应构造如下:

 public InputEventReceiver(InputChannel inputChannel, Looper looper) 
      。。省略
        mInputChannel = inputChannel;
        mMessageQueue = looper.getQueue();
        mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),
                inputChannel, mMessageQueue);

        mCloseGuard.open("dispose");
    

这里会jni调用nativeInit:

static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak,
        jobject inputChannelObj, jobject messageQueueObj) 
  。。省略
    sp<NativeInputEventReceiver> receiver = new NativeInputEventReceiver(env,
            receiverWeak, inputChannel, messageQueue);
    status_t status = receiver->initialize();
。。省略
    return reinterpret_cast<jlong>(receiver.get());

这里构造NativeInputEventReceiver对象,然后调用initialize:

status_t NativeInputEventReceiver::initialize() 
    setFdEvents(ALOOPER_EVENT_INPUT);
    return OK;

这里就是调用setFdEvents:

void NativeInputEventReceiver::setFdEvents(int events) 
    if (mFdEvents != events) 
        mFdEvents = events;
        int fd = mInputConsumer.getChannel()->getFd();
        if (events) 
            mMessageQueue->getLooper()->addFd(fd, 0, events, this, NULL);
         else 
            mMessageQueue->getLooper()->removeFd(fd);
        
    

注意啦,这里获取了InputChannel的socketpair的fd,然后调用Looper的addFd方法:

Looper的addFd实现在system/core目录寻找,它不在framework下面:

./libutils/Looper.cpp:430: return addFd(fd, ident, events, callback ? new SimpleLooperCallback(callback) : NULL, data);

int Looper::addFd(int fd, int ident, int events, const sp<LooperCallback>& callback, void* data) 
..省略

     
    ..省略
        if (requestIndex < 0) 
            int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, & eventItem);
            ..省略
            mRequests.add(fd, request);
         else 
            int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_MOD, fd, & eventItem);
            if (epollResult < 0) 
                if (errno == ENOENT) 
      			..省略
                    epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, & eventItem);
                   ..省略
            
            mRequests.replaceValueAt(requestIndex, request);
        
     // release lock
    return 1;


大家是不是看到了,其实Loop本质也是用了epoll来实现的,这里的addFd其实就是epoll的add操作

以上是关于Android Framework实战开发-socketpair介绍及它在android系统源码使用分析的主要内容,如果未能解决你的问题,请参考以下文章

千里马Android Framework实战开发-native程序之间binder通信实战案例分析

千里马Android Framework实战开发-native程序之间binder通信实战案例分析

千里马Android Framework实战开发-native程序之间binder通信实战案例分析

Android Framework实战开发-socketpair介绍及它在android系统源码使用分析

Android Framework实战开发-socketpair介绍及它在android系统源码使用分析

Android Framework实战开发-socketpair介绍及它在android系统源码使用分析