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系统源码使用分析