前言
信任大家对Socket
都不生疏,可是对于LocalSocket
是什么,可能就不太了解了,笔者也是孤陋寡闻,第一次听说这个,起因是在项目中用到这个东西,感觉很别致,所以学习了一波将其记录下来。探究进程发现Android
体系中也有多处运用了它,写着写着就有了这篇较为深化的文章。
本篇博客首要介绍以下内容:
- LocalSocket是个啥
- 经过LocalSocket完成通讯进程
- 从源码角度分析
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)
函数读写id
,read id
便是接纳服务端的数据,write id
便是发送数据给服务端–
参数name是客户端与服务端衔接的要害name
,namespaceId
一般运用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
进程是经过LocalSocke
t而非其他跨进程(比方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
以创立client
端LocalSocket
并尝试衔接到服务端(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
无法获取到Zygote
的binder
引证,所以不能确保时序。
假如对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…