前言

信任大家对Socket都不生疏,可是对于LocalSocket是什么,可能就不太了解了,笔者也是孤陋寡闻,第一次听说这个,起因是在项目中用到这个东西,感觉很别致,所以学习了一波将其记录下来。探究进程发现Android体系中也有多处运用了它,写着写着就有了这篇较为深化的文章。

本篇博客首要介绍以下内容:

  1. LocalSocket是个啥
  2. 经过LocalSocket完成通讯进程
  3. 从源码角度分析SystemServer进程是怎么经过LocalSocket完成与Zygote进程通讯的

LocalSocket是个啥

先回忆一下Socket的概念:

Socket常翻译成套接字,它是对网络中不同主机上的运用进程之间进行双向通讯的端点的笼统。一个套接字便是网络上进程通讯的一端,提供了运用层进程运用网络协议交流数据的机制。从所在的位置来讲,套接字上联运用进程,下联网络协议栈,是运用程序经过网络协议进行通讯的接口。

那么LocalSocket是什么,笔者觉得能够翻译成“本地套接字”

Android中的LocalSocket是依据UNIX-domain Socket的,UNIX-domain Socket又是什么?整迷糊了,一个概念还没搞清楚又来一个。

UNIX-domain Socket是在Socket的基础上衍生出来的一种IPC通讯机制,它是全双工(答应数据在两个方向上一起传输)的,而且API 接口语义丰厚。

所以LocalSocket的这个Local表达的是同一台主机,它解决的是同一台主机上不同进程间相互通讯的问题。

那么问题来了,socket 不也可用于同一台主机的进程间通讯,为啥还要造个LocalSocket

其实这是因为socket自身是为网络通讯规划的,它为了解决不同主机的通讯,需要经过网路协议栈,需要做更多的操作来确保安全验证,而这些背面献身的便是功率。

UNIX domain socket 用于 IPC 更有功率:不需要经过网络协议栈,不需要打包拆包、核算校验和、保护序号和应答等,只是将运用层数据从一个进程拷贝到另一个进程。

UNIX套接字和IP套接字区别:

UNIX 套接字是一种进程间通讯机制,答应在同一台核算机上运转的进程之间进行双向数据交流。
IP 套接字(尤其是 TCP/IP 套接字)是一种答应进程之间经过网络进行通讯的机制。
在某些情况下,是能够运用 TCP/IP 套接字与同一台核算机上运转的进程进行通讯(经过运用环回接口)。但由于UNIX 域套接字知道它们在同一体系上执行,因而它们能够避免一些检查和操作(如路由)。这就使得UNIX 套接字 比 IP 套接字更快、更轻。
因而,假如您计划与同一主机上的进程进行通讯,这是比 IP 套接字更好的挑选。

LocalSocket完成通讯进程

Android上运用LocalSocket首要是经过name来区别,也便是说客户端和服务端之间衔接必须运用相同的name,而且一个name同一时刻只能有一个服务端运转,name能够只一串字符串,如“com.xxx.xx”。

下面以Java作为服务端,C作为客户端 ,经过LocalSocket完成一个通讯进程:

1.Java服务端代码

服务端会LocalServerSocket目标,并经过调用accept办法,阻塞当时线程,等待客户端的衔接

package com.example.localsocketserver;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import android.app.Activity;
import android.net.Credentials;
import android.net.LocalServerSocket;
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
public class MainActivity extends Activity {
    private static final String TAG = "MainActivity";
    private ServerThread mThread = null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        startServer();
    }
    @Override
    protected void onPause() {
        super.onPause();
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        stopServer();
    }
    private void startServer(){
        stopServer();
        mThread = new ServerThread();
        mThread.start();
    }
    private void stopServer(){
        if(mThread != null){
            mThread.exit();
            mThread = null;
        }
    }
    private class ServerThread extends Thread{
        private boolean exit = false;
        private int port = 3333;
        public void run() {  
            LocalServerSocket server = null;  
            BufferedReader mBufferedReader = null;  
            PrintWriter os = null;  
            String readString =null;  
            try {  
                server = new LocalServerSocket("com.xxx.localsocket");   
                while (!exit) {  
                    LocalSocket connect = server.accept();  
                    Credentials cre = connect.getPeerCredentials();
                    Log.i(TAG,"accept socket uid:"+cre.getUid()); 
                    new ConnectThread(connect).start();
                }     
            } catch (IOException e) {  
                e.printStackTrace();  
            }finally{
                try {  
                    mBufferedReader.close();  
                    os.close();  
                    server.close();  
                } catch (IOException e) {  
                    e.printStackTrace();  
                }  
            }  
        } 
        public void exit(){
            exit = true;
            this.interrupt();
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    class ConnectThread extends Thread{
        LocalSocket socket = null;
        BufferedReader mBufferedReader = null;  
        InputStream input = null;
        PrintWriter os = null;
        String readString =null;
        public ConnectThread(LocalSocket socket){    
            this.socket = socket;
        }
        @Override
        public void run(){
            try {
                input = socket.getInputStream();
                byte[] buffer = new byte[1024];
                int len = input.read(buffer);
                Log.d(TAG,"mBufferedReader:"+new String(buffer,0,len)); 
                os = new PrintWriter(socket.getOutputStream());  
                os.println("this is server\0");  
                os.flush();  
                os.close();
                socket.close();
                Log.d(TAG,"server send over");
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}

2.C客户端代码

c客户端代码首要调用的是Android接口:

int socket_local_server(const char *name, int namespaceId, int type)

函数读写idread id便是接纳服务端的数据,write id便是发送数据给服务端–
参数name是客户端与服务端衔接的要害namenamespaceId一般运用ANDROID_SOCKET_NAMESPACE_ABSTRACT type 运用SOCK_STREAM

#include <sys/socket.h>
#include <sys/un.h>
#include <stddef.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <cutils/sockets.h>
#define PATH "com.xxx.localsocket"
int main(int argc, char *argv[]) 
{
    int socketID;
    int ret;
    int i = 0;
    int len = 0;
    for(;i < argc ;i++){
        len = len + strlen(argv[i]);
    }
    len = len + argc ;
    char *buffer ;
    buffer = ( char *)malloc(len * sizeof( char *));
    if(buffer == NULL){
        printf("malloc failed\n");
        return 0;
    }
    strcpy(buffer, argv[0]);
    for(i=1;i<argc;i++){
        strcat(buffer, " "); 
        strcat(buffer, argv[i]); 
    }
    socketID = socket_local_client(PATH, ANDROID_SOCKET_NAMESPACE_ABSTRACT, SOCK_STREAM);
    if (socketID < 0)
    {
        return socketID;
    }
    ret = write(socketID, buffer, strlen(buffer));
    if(ret < 0){
        printf("send failed\n");
        return ret;
    }
    char buf2[512] = {0};
    ret = read(socketID,buf2,sizeof(buf2));
    if(ret < 0){
        printf("recived failed\n");
        return ret;
    }else{
        printf("c client recived from server: %s\n",buf2);
    }
    ret = close(socketID);
    if (ret < 0)
    {
        return ret;
    }
    return 0;
}

Android体系中LocalSocket的运用

Android体系源码中有许多地方都用到了LocalSocket完成跨进程通讯。

比方SystemServer进程是经过LocalSocket而非其他跨进程(比方Binder)通讯的办法发送恳求给Zygote进程以fork出子进程的。

再比方,PackageManagerServie(简称PKMS)服务负责运用的安装、卸载等相关工作,而真正干活的仍是installd进程,当看护进程installd启动完成后,上层framework便能够经过socket跟该看护进程进行通讯。这里运用socket便是LocalSocket

下面经过分析源码来介绍下SystemServer进程是怎么经过LocalSocket完成Zygote进程通讯的

1.进口

public static void main(String[] argv) {
        ......
        // ⭐️ 在类ZygoteServer的结构函数中会创立对应的LocalServerSocket
        zygoteServer = new ZygoteServer(isPrimaryZygote);
        ......
        Log.i(TAG, "Accepting command socket connections");
        // 监听客户端Socket恳求
        caller = zygoteServer.runSelectLoop(abiList);
    } catch (Throwable ex) {
        Log.e(TAG, "System zygote died with fatal exception", ex);
        throw ex;
    } finally {
        if (zygoteServer != null) {
            zygoteServer.closeServerSocket();
        }
    }
    if (caller != null) {
        caller.run();
    }
}

2.Zygote完成-服务端

2.1 ZygoteServer

  ZygoteServer(boolean isPrimaryZygote) {
      mUsapPoolEventFD = Zygote.getUsapPoolEventFD();
      // 判别当时是否是Zygote进程,该值与init.zygote64.rc中的--socket-name=zygote有关
      if (isPrimaryZygote) {
          // 创立LocalSocketServer
          mZygoteSocket = Zygote.createManagedSocketFromInitSocket(Zygote.PRIMARY_SOCKET_NAME);
          // 创立USAP进程池相关LocalServerSocket
          mUsapPoolSocket =
                  Zygote.createManagedSocketFromInitSocket(
                          Zygote.USAP_POOL_PRIMARY_SOCKET_NAME);
      } else {
          mZygoteSocket = Zygote.createManagedSocketFromInitSocket(Zygote.SECONDARY_SOCKET_NAME);
          mUsapPoolSocket =
                  Zygote.createManagedSocketFromInitSocket(
                          Zygote.USAP_POOL_SECONDARY_SOCKET_NAME);
      }
      mUsapPoolSupported = true;
      fetchUsapPoolPolicyProps();
  }

2.2 createManagedSocketFromInitSocket

createManagedSocketFromInitSocket办法会依据传递进去的socketName获取到对应的文件描绘符,接着经过创立的文件描绘目标创立LocalServerSocket目标并回来。

static LocalServerSocket createManagedSocketFromInitSocket(String socketName) {
    int fileDesc;
    // 结构完整的fullSocketName = ANDROID_SOCKET_zygote
    final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName;
    try {
        // 获取对应文件描绘符
        String env = System.getenv(fullSocketName);
        fileDesc = Integer.parseInt(env);
    } catch (RuntimeException ex) {
        throw new RuntimeException("Socket unset or invalid: " + fullSocketName, ex);
    }
    try {
        // 创立文件描绘符目标
        FileDescriptor fd = new FileDescriptor();
        fd.setInt$(fileDesc);
        // 依据文件描绘目标创立LocalServerSocket目标
        return new LocalServerSocket(fd);
    } catch (IOException ex) {
        throw new RuntimeException(
            "Error building socket from file descriptor: " + fileDesc, ex);
    }
}

2.3 等待客户端衔接

Runnable runSelectLoop(String abiList) {
    ArrayList<FileDescriptor> socketFDs = new ArrayList<>();
    ArrayList<ZygoteConnection> peers = new ArrayList<>();
    // 将与SystemServer通讯的Socket对应文件描绘符存入到list中,后续经过Os.poll监听对应文件是否产生了可读事情唤醒Zygote进程
    socketFDs.add(mZygoteSocket.getFileDescriptor());
    // ......
    while (true) {
        // ...
        // 表明达到了超时时刻或许出现了非阻塞轮询而且传递进去的文件描绘符都没有安排妥当则回来0
        if (pollReturnValue == 0) {
          // ......
        } else {
            boolean usapPoolFDRead = false;
            while (--pollIndex >= 0) {
                // 判别对应文件是否有可读事情产生
                if ((pollFDs[pollIndex].revents & POLLIN) == 0) {
                    continue;
                }
                if (pollIndex == 0) {
                    // ⭐️监听衔接恳求
                    ZygoteConnection newPeer = acceptCommandPeer(abiList);
                    peers.add(newPeer);
                    socketFDs.add(newPeer.getFileDescriptor());
                // 处理来自SystemServer端的恳求
                } else if (pollIndex < usapPoolEventFDIndex) {
                    try {
                        ZygoteConnection connection = peers.get(pollIndex);
                        //判别Zygote进程中子线程是否悉数停止
                        boolean multipleForksOK = !isUsapPoolEnabled()
                                && ZygoteHooks.isIndefiniteThreadSuspensionSafe();
                        //处理恳求
                        final Runnable command =
                                connection.processCommand(this, multipleForksOK);
                        ......
                    } catch (Exception e) {
                        ......
                    } 
                ......
                } else {
                    ......
                }
            }
            ......
        }
    }
}

2.4 accept办法等待衔接

mZygoteSocket是创立的LocalServerSocket目标,经过调用该目标的accept函数监听SystemServer进程的衔接恳求,该处也会阻塞当时线程直到客户端发来恳求。

private ZygoteConnection acceptCommandPeer(String abiList) {
    try {
        // ⭐️ accept等待客户端衔接
        return createNewConnection(mZygoteSocket.accept(), abiList);
    } catch (IOException ex) {
        throw new RuntimeException(
                "IOException during accept()", ex);
    }
}

3.SystemServer完成-客户端

3.1.进口-startViaZygote

调用函数openZygoteSocketIfNeeded以创立clientLocalSocket并尝试衔接到服务端(Zygote进程)。最终调用函数zygoteSendArgsAndGetResult将各种参数转换为String并发送给Zygote进程。

private Process.ProcessStartResult startViaZygote(......) throws ZygoteStartFailedEx {
    // ⭐️ openZygoteSocketIfNeeded(abi)
    synchronized(mLock) {
        return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi),
                                          zygotePolicyFlags,
                                          argsForZygote);
    }
}

3.2 创立LocalSocket并衔接

private ZygoteState openZygoteSocketIfNeeded(String abi) throws ZygoteStartFailedEx {
     attemptConnectionToPrimaryZygote();
}
private void attemptConnectionToPrimaryZygote() throws IOException {
    if (primaryZygoteState == null || primaryZygoteState.isClosed()) {
        primaryZygoteState =
                ZygoteState.connect(mZygoteSocketAddress,mUsapPoolSocketAddress);
        // ......
    }
}
static ZygoteState connect(@NonNull LocalSocketAddress zygoteSocketAddress,
        @Nullable LocalSocketAddress usapSocketAddress)
        throws IOException {
    DataInputStream zygoteInputStream;
    BufferedWriter zygoteOutputWriter;
    // ⭐️ 创立LocalSocket
    final LocalSocket zygoteSessionSocket = new LocalSocket();
    if (zygoteSocketAddress == null) {
        throw new IllegalArgumentException("zygoteSocketAddress can't be null");
    }
    try {
        // zygoteSocketAddress = new LocalSocketAddress(Zygote.PRIMARY_SOCKET_NAME, LocalSocketAddress.Namespace.RESERVED);
        // ⭐️ 衔接到Zygote进程中的LocalServerSocket
        zygoteSessionSocket.connect(zygoteSocketAddress);
        // 获取接纳Zygote进程回调信息I/O流
        zygoteInputStream = new DataInputStream(zygoteSessionSocket.getInputStream());
        // 获取传递参数给Zygote进程I/O流
        zygoteOutputWriter = new BufferedWriter(
                    new OutputStreamWriter(zygoteSessionSocket.getOutputStream()),
                    Zygote.SOCKET_BUFFER_SIZE);
    } catch (IOException ex) {
        try {
            zygoteSessionSocket.close();
        } catch (IOException ignore) { }
        throw ex;
    }
    //创立目标并回来
    return new ZygoteState(zygoteSocketAddress, usapSocketAddress,
                           zygoteSessionSocket, zygoteInputStream, zygoteOutputWriter,
                           getAbiList(zygoteOutputWriter, zygoteInputStream));
}

3.3 参数发送和数据接纳读取

private Process.ProcessStartResult attemptZygoteSendArgsAndGetResult(
        ZygoteState zygoteState, String msgStr) throws ZygoteStartFailedEx {
    try {
        // 获取发送参数I/O流
        final BufferedWriter zygoteWriter = zygoteState.mZygoteOutputWriter;
        // 获取数据接纳I/O流
        final DataInputStream zygoteInputStream = zygoteState.mZygoteInputStream;
        // 将参数发送给Zygote进程
        zygoteWriter.write(msgStr);
        zygoteWriter.flush();
        //接纳Zygote进程发送回来的数据
        Process.ProcessStartResult result = new Process.ProcessStartResult();
        //新建进程pid
        result.pid = zygoteInputStream.readInt();
        result.usingWrapper = zygoteInputStream.readBoolean();
        if (result.pid < 0) {
            throw new ZygoteStartFailedEx("fork() failed");
        }
        return result;
    } catch (IOException ex) {
        zygoteState.close();
        throw new ZygoteStartFailedEx(ex);
    }
}

4.问题

为什么Zygote通讯为什么用Socket,而不是Binder

原因一:先后时序问题

Binder驱动是早于init进程加载的。而init进程是安卓体系启动的第一个进程。

安卓中一般运用的Binder引证,都是保存在ServiceManager进程中的,而假如想从ServiceManager中获取到对应的Binder引证,前提是需要注册。

尽管Init进程是先创立ServiceManager,后创立Zygote进程的。尽管Zygote更晚创立,可是也不能确保Zygote进程去注册Binder的时候,ServiceManager现已初始化好了。注册时刻点无法确保,AMS无法获取到Zygotebinder引证,所以不能确保时序。

假如对Binder不了解或感兴趣的能够看看我写的相关文章,适合小白学习:

啃下Binder这块硬骨头(一)

啃下Binder这块硬骨头(二)

原因二:多线程问题

Linux中,fork进程是不引荐fork一个多线程的进程,因为假如存在锁的情况下,会导致锁异常。
而假如自身作为Binder机制的接纳者,就会创立一个额定的线程来进行处理(发送者进程是无影响的)。所以,假如运用Binder机制,就需要去fork一个多线程的进程。

参考

1.想了解一下 UNIX domain socket,能够看这篇:

www.cnblogs.com/sparkdev/p/…

2.LocalSocket 进程间通讯的运用办法,能够参考

blog.csdn.net/grandgrandp…

3.分析 Zygote 与 AMS 是怎么运用 LocalSocket 建立衔接的

devbins.github.io/post/locals…

4.Android 经过 Zygote 进程 fork 子进程进程相关源码

/post/721542…

5.Zygote通讯为什么用Socket,而不是Binder?

blog.csdn.net/cpcpcp123/a…

6.Android7.0 PackageManagerService (5) installd

blog.csdn.net/Gaugamela/a…