前言
相信大家对Socket
都不陌生,但是对于LocalSocket
是什么,可能就不太了解了,笔者也是孤陋寡闻,第一次听说这个,起因是在项目中用到这个东西,感觉很新奇,于是学习了一波将其记录下来。
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
出子进程的。
下面通过剖析源码来介绍下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
引用,所以不能保证时序。
原因二:多线程问题
Linux
中,fork
进程是不推荐fork
一个多线程的进程,因为如果存在锁的情况下,会导致锁异常。 而如果自身作为Binder
机制的接收者,就会创建一个额外的线程来进行处理(发送者进程是无影响的)。所以,如果使用Binder
机制,就需要去fork
一个多线程的进程。