你可能没听过的LocalSocket

前言

相信大家对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_ABSTRACTtype 使用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引用,所以不能保证时序。

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
img
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓

PS:群里还设有ChatGPT机器人,可以解答大家在工作上或者是技术上的问题

图片

猜你喜欢

转载自blog.csdn.net/weixin_43440181/article/details/132814222
今日推荐