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

93 篇文章 58 订阅
42 篇文章 20 订阅

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操作

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
千里马8年Android系统及应用开发经验,曾担任过美国unokiwi公司移动端技术总监兼架构师,对系统开发,性能优化,应用高级开发有深入的研究,Android开源定制ROM Lineage的贡献者之一,国内首家线下开辟培训Android Framework课程,拥有2年的Android系统培训经验。成为腾讯课堂专业负责android framework课程分享第一人,致力于提高国内android Framework水平Android Framework领域内是国内各大手机终端科技公司需要的人才,应用开发者都对Android系统充满着好奇,其中的binder是重中之重,都说无binder无Android,binde是Android系统的任督二脉。课程水平循序渐进,由中级再到高级,满足各个层次水平的android开发者。1、灵活使用binder跨进程通信,在app端对它的任何api方法等使用自如2、可以单独分析android系统源码中任何binder部分,分析再也没有难度3、掌握binder驱动本质原理,及对应binder驱动怎么进行跨进程通信,及内存等拷贝方式数据等4、对binder从上层的java app端一直到最底层的内核binder驱动,都可以顺利理通5、针对系统开发过程中遇到的binder报错等分析方法,及binder bug案例学习6、针对面试官任何的binder问题都可以对答自如7、socket这种跨进程通信实战使用8、针对android源码使用socket源码轻松掌握9、android系统源码中最常见的socketpair中双向跨进程通信10、使用socket实现一个可以让app执行shell命令的程序

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

千里马学框架

帮助你了,就请我喝杯咖啡

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值