sendfd 利用的是 UNIX 提供的文件描述符共享机制,它可以为本机其它进程提供当前进程打开的文件描述符句柄建立分支句柄,但这也导致了全新的问题,多个进程读写单个句柄会导致进程间同步问题,当然我们可以实现进程间的 “互斥量/事件同步对象” 来协同处理,但是效率上会造成严重影响,通常它只能用于 “主从架构” 的应用程序使用。
sendfd 在 Android 平台高权限应用向低权限应用 send-fd 过去,低权限应用可能无法正确接收被共享的代理文件描述句柄,这涉及到 Android 令人烦恼的权限控制,不过操作得当是不会出现疑难问题的。
标注:C/C++ 子进程与 Java 宿主进程UNIX_URL指向文件路径是有一定讲究的,不要随便的使用路径否则是没办法共享FD的,有些放到SD卡里面某些机型是可以的,但是某些又不可以,目前来说以下两个目录不会有太大问题:
1、cacheDir 本机缓存目录
2、noBackupDir 本地不备份目录
public static @Nullable String getNoBackupFilesDir(@NotNull Context context) {
return context.getNoBackupFilesDir().getPath();
}
public static @NotNull String getCacheDir(@NotNull Context context) {
return getCacheDir(context, false);
}
@SuppressLint("SdCardPath")
public static @NotNull String getCacheDir(@NotNull Context context, boolean rlink) {
if (rlink) {
String cacheDir = "/data/data/";
cacheDir += APK.getPackageName(context);
cacheDir += "/cache";
return cacheDir;
} else {
File cacheDir = context.getCacheDir();
return cacheDir.getPath();
}
}
sendfd 可以是由调用C/C++ JNI层实现也可以是使用原生 Android Jar 层库提供的 LocalSocket 来实现,如:
C/C++
int Socket::ProtectFiledescriptor(const char* unix_path, int fd, int milliSecondsTimeout, bool sync, char& r) {
r = 0;
if (NULL == unix_path || milliSecondsTimeout <= 0) {
return -1001;
}
int sock = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock < 0) {
return -1002;
}
struct timeval tv;
tv.tv_sec = (milliSecondsTimeout / 1000);
tv.tv_usec = (milliSecondsTimeout % 1000) * 1000;
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char*)&tv, sizeof(struct timeval));
setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char*)&tv, sizeof(struct timeval));
struct sockaddr_un addr;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, unix_path, sizeof(addr.sun_path) - 1);
if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
close(sock);
return -1003;
}
if (ancil_send_fd(sock, fd)) {
close(sock);
return -1004;
}
char err = 0;
if (recv(sock, &err, 1, MSG_NOSIGNAL) < 0) {
close(sock);
return -1005;
}
if (sync) {
r = err;
if (err) {
close(sock);
return err;
}
if (recv(sock, &err, 1, MSG_NOSIGNAL) < 0) {
close(sock);
return -1006;
}
}
r = err;
close(sock);
return err;
}
Java:(Android)
private void sendfd(int fd) throws RuntimeException {
int success = 0;
for (int i = 0; i < MAX_SENDFD_COUNT; i++) {
if (!Process.isAlive(this._process)) {
break;
}
LocalSocket socket = new LocalSocket(LocalSocket.SOCKET_STREAM);
FileDescriptor descriptor = FILE.newFileDescriptor(fd);
try {
socket.connect(new LocalSocketAddress(this.UNIX_INTERFACE_URI, LocalSocketAddress.Namespace.FILESYSTEM));
if (socket.isConnected()) {
socket.setFileDescriptorsForSend(new FileDescriptor[]{descriptor});
socket.getOutputStream().write(42);
success++;
break;
}
} catch (Throwable e) {
e.printStackTrace();
} finally {
try {
Thread.sleep(100);
} catch (Throwable ignored) {
}
try {
socket.close();
} catch (Throwable e) {
e.printStackTrace();
}
}
}
if (success < 1) {
throw new RuntimeException("Repeated attempts to retry sendfd with the sub-process are always wrong and fails.");
}
}
通常博主本人推荐使用 “Java” 方式发送文件描述符,但是前提是不存在多线程同步的情况,否则建议使用第一种,并且手动控制粒度。
举个例子你向某个UNIX_URL建立连接并且发送文件描述符过去,但你需要等待它那边正确处理,而不是仅仅只是等待判定内核通知的 1byte 错误代码,那么就需要实现等待对面处理正确并且应答的ACK,基于STREAM形式的UNIX_SOCKET实现这样的机制是很容易的。
LocalSocket 存在其缺陷,它的仿生类型为 LocalServerSocket 是不支持 FileSystem(映射共享内存文件)类型的URL的,仅支持绑定带 “@” 格式的 “Namespace” URL地址,这也导致从 C/C++ 进程访问由 Android Java 应用程式建立绑定监听的 LocalServerSocket,难以被正确访问,一个好的建议是不要在这种没有意义的问题上面捣鼓,由C/C++ JNI实现一组用来阻塞监听的 UNIX_SOCKET 服务就好,都统一使用 “FileSystem” 就可以,至于某些人搞得秀到飞起,多重 LocalSocket 转来转去,不知道意义何在。
以下为借助 C/C++ JNI 故而实现 create_unix_socket 函数的实现:
首先定义一个导出 DLL 函数的宏声明模板,假设名为:libcor32
#ifndef __LIBCOR32__
#define __LIBCOR32__(JNIType) extern "C" JNIEXPORT __unused JNIType JNICALL
#endif
构建一个监听类型的 UNIX_SOCKET,并且以 “FileSystem” 类型绑定UNIX_URL的被映射的共享文件,需要注意的是 bind 之前需要确保删除URL指向文件,否则会导致无法被绑定导致错误。
__LIBCOR32__(jintArray) Java_com_android_sendfddemo_c_libcor32_createUnixSocket(
JNIEnv *env,
jobject this_,
jstring unix_path_,
jint back_log_) {
jintArray result = env->NewIntArray(2);
if (NULL == result) {
return result;
}
const char *unix_path = NULL;
if (NULL != unix_path_) {
unix_path = env->GetStringUTFChars(unix_path_, NULL);
}
if (NULL == unix_path) {
jint *p = env->GetIntArrayElements(result, NULL);
*p = -1011;
env->ReleaseIntArrayElements(result, p, 0);
return result;
}
int sock = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock < 0) {
jint *p = env->GetIntArrayElements(result, NULL);
*p = -1012;
env->ReleaseIntArrayElements(result, p, 0);
return result;
}
int fl = fcntl(sock, F_GETFL, 0);
if (fcntl(sock, F_SETFL, fl & ~O_NONBLOCK) < 0) {
close(sock);
jint *p = env->GetIntArrayElements(result, NULL);
*p = -1013;
env->ReleaseIntArrayElements(result, p, 0);
}
unlink(unix_path);
struct sockaddr_un addr;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, unix_path, sizeof(addr.sun_path) - 1);
if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
close(sock);
jint *p = env->GetIntArrayElements(result, NULL);
*p = -1014;
env->ReleaseIntArrayElements(result, p, 0);
return result;
}
if (back_log_ < 1) {
back_log_ = 1;
}
if (listen(sock, back_log_) < 0) {
close(sock);
jint *p = env->GetIntArrayElements(result, NULL);
*p = -1014;
env->ReleaseIntArrayElements(result, p, 0);
return result;
}
jint *p = env->GetIntArrayElements(result, NULL);
p[0] = 0;
p[1] = sock;
env->ReleaseIntArrayElements(result, p, 0);
return result;
}
OK,回到正题我们已经实现了创建SOCKET的函数,那么接下来就需要实现一个普普通通的关闭SOCKET函数
__LIBCOR32__(void) Java_com_android_sendfddemo_c_libcor32_closeSocket(
JNIEnv *env,
jobject this_,
jint fd_) {
int handle = fd_;
if (handle != -1) {
close(handle);
}
}
OK,以下实现 recvfd + sendack 两个函数,这是什么原因呢?上面提到了是为了确保发送客户端与接收服务端之间的同步,当然你大可以不需要,但这需要看情况,某些程序某相对需要的地方也不处理,导致接收端去处理各种报错,这是典型反面教材,可惜吧,第一个开源东西的错了,后面抄代码搞新东西的跟着错,真·有意思。
__LIBCOR32__(jintArray) Java_com_android_sendfddemo_c_libcor32_recvFd(
JNIEnv *env,
jobject this_,
jint fd_) {
jintArray result = env->NewIntArray(3);
if (NULL == result) {
return result;
}
int sock = fd_;
if (sock == -1) {
jint *p = env->GetIntArrayElements(result, NULL);
*p = -1021;
env->ReleaseIntArrayElements(result, p, 0);
return result;
}
struct sockaddr_un remoteEP;
memset(&remoteEP, 0, sizeof(remoteEP));
socklen_t size = sizeof(remoteEP);
for (;;) {
int connection = accept(sock, (struct sockaddr *) &remoteEP, &size);
if (connection < 0) {
jint *p = env->GetIntArrayElements(result, NULL);
*p = -1022;
env->ReleaseIntArrayElements(result, p, 0);
return result;
}
int fd;
if (ancil_recv_fd(connection, &fd)) {
close(connection);
close(sock);
continue;
}
jint *p = env->GetIntArrayElements(result, NULL);
p[0] = 0;
p[1] = connection;
p[2] = fd;
env->ReleaseIntArrayElements(result, p, 0);
return result;
}
}
__LIBCOR32__(jint) Java_com_android_sendfddemo_c_libcor32_sendAck(
JNIEnv *env,
jobject this_,
jint fd_) {
int connection = fd_;
if (connection == -1) {
return -1031;
}
char err = 0;
if (send(connection, &err, 1, MSG_NOSIGNAL) < 0) {
close(connection);
return -1033;
}
close(connection);
return 0;
}
但是我想明确说一个新的问题是,Android 平台上面各种厂商系统及版本的疑难兼容问题非常难以解决,正如结合 sendfd+recvfd 来实现 “主从进程” 架构,但是你架不住这帮 LJ厂商魔法系统的各种套路,是的某些非常底层重要的程式使用这样的架构,最容易遭遇的问题就是莫名其妙的 “进程” 各种被杀死,强杀各种网络连接,美曰其名叫电源控制及优化,实际上无论你关闭多少优化,它都无法解决会在 Android 上面被莫名强杀的问题,它可以是杀APK也可以是直接绕过APK直接强杀你的 C/C++ 子进程,所以如果你驾驭不住,我建议你最好跟着国外大厂弄JNI,直接换入代码到 JAVA 层的内存空间,这样你只需要想办法解决 JAVA 层会遇到的疑难问题即可。
补充你需要使用经过我本人修改的调用 “libancillary” 库的实现,这是因为源 “libancillary” 实现虽然可以在 Android 上面正常工作,但是前提是正常 Android,但是国内厂商的 Android 可是瞎吉儿乱搞的,根本不按接口原有规范来,所以我这里修改做了这方面的代码兼容适配,具体我是怎么找问题就不多说了,总之吧,调试依赖通过错误异常 Crash C/C++ 程式看PC及计数器来推断问题出现在哪里,总之是,非常不容易。
ancillary.h
/***************************************************************************
* libancillary - black magic on Unix domain sockets
* (C) Nicolas George
* ancillary.c - public header
***************************************************************************/
/*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef ANCILLARY_H__
#define ANCILLARY_H__
/***************************************************************************
* Start of the readable part.
***************************************************************************/
#define ANCIL_MAX_N_FDS 960
/*
* Maximum number of fds that can be sent or received using the "esay"
* functions; this is so that all can fit in one page.
*/
extern int
ancil_send_fds_with_buffer(int, const int *, unsigned, void *);
/*
* ancil_send_fds_with_buffer(sock, n_fds, fds, buffer)
*
* Sends the file descriptors in the array pointed by fds, of length n_fds
* on the socket sock.
* buffer is a writeable memory area large enough to hold the required data
* structures.
* Returns: -1 and errno in case of error, 0 in case of success.
*/
extern int
ancil_recv_fds_with_buffer(int, int *, unsigned, void *);
/*
* ancil_recv_fds_with_buffer(sock, n_fds, fds, buffer)
*
* Receives *n_fds file descriptors into the array pointed by fds
* from the socket sock.
* buffer is a writeable memory area large enough to hold the required data
* structures.
* Returns: -1 and errno in case of error, the actual number of received fd
* in case of success
*/
#define ANCIL_FD_BUFFER(n) \
struct \
{ \
struct cmsghdr h; \
int fd[n]; \
}
/* ANCIL_FD_BUFFER(n)
*
* A structure type suitable to be used as buffer for n file descriptors.
* Requires <sys/socket.h>.
* Example:
* ANCIL_FD_BUFFER(42) buffer;
* ancil_recv_fds_with_buffer(sock, 42, my_fds, &buffer);
*/
extern int
ancil_send_fds(int, const int *, unsigned);
/*
* ancil_send_fds(sock, n_fds, fds)
*
* Sends the file descriptors in the array pointed by fds, of length n_fds
* on the socket sock.
* n_fds must not be greater than ANCIL_MAX_N_FDS.
* Returns: -1 and errno in case of error, 0 in case of success.
*/
extern int
ancil_recv_fds(int, int *, unsigned);
/*
* ancil_recv_fds(sock, n_fds, fds)
*
* Receives *n_fds file descriptors into the array pointed by fds
* from the socket sock.
* *n_fds must not be greater than ANCIL_MAX_N_FDS.
* Returns: -1 and errno in case of error, the actual number of received fd
* in case of success.
*/
extern int
ancil_send_fd(int, int);
/* ancil_recv_fd(sock, fd);
*
* Sends the file descriptor fd on the socket sock.
* Returns : -1 and errno in case of error, 0 in case of success.
*/
extern int
ancil_recv_fd(int, int *);
/* ancil_send_fd(sock, &fd);
*
* Receives the file descriptor fd from the socket sock.
* Returns : -1 and errno in case of error, 0 in case of success.
*/
#endif /* ANCILLARY_H__ */
fd_recv.cpp
/***************************************************************************
* libancillary - black magic on Unix domain sockets
* (C) Nicolas George
* fd_send.c - receiving file descriptors
***************************************************************************/
/*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef _XPG4_2 /* Solaris sucks */
#define _XPG4_2
#endif
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <assert.h>
#if defined(__FreeBSD__)
#include <sys/param.h> /* FreeBSD sucks */
#endif
#include "ancillary.h"
int ancil_recv_fds_with_buffer(int sock, int *fds, unsigned n_fds, void *buffer)
{
struct msghdr msghdr;
char nothing;
struct iovec nothing_ptr;
struct cmsghdr *cmsg;
unsigned i;
nothing_ptr.iov_base = ¬hing;
nothing_ptr.iov_len = 1;
msghdr.msg_name = NULL;
msghdr.msg_namelen = 0;
msghdr.msg_iov = ¬hing_ptr;
msghdr.msg_iovlen = 1;
msghdr.msg_flags = 0;
msghdr.msg_control = buffer;
msghdr.msg_controllen = sizeof(struct cmsghdr) + sizeof(int) * n_fds;
cmsg = CMSG_FIRSTHDR(&msghdr);
cmsg->cmsg_len = msghdr.msg_controllen;
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
for (i = 0; i < n_fds; i++)
((int *)CMSG_DATA(cmsg))[i] = -1;
if (recvmsg(sock, &msghdr, 0) < 0)
return (-1);
for (i = 0; i < n_fds; i++)
fds[i] = ((int *)CMSG_DATA(cmsg))[i];
n_fds = (msghdr.msg_controllen - sizeof(struct cmsghdr)) / sizeof(int);
return (n_fds);
}
#ifndef SPARE_RECV_FDS
int ancil_recv_fds(int sock, int *fd, unsigned n_fds)
{
ANCIL_FD_BUFFER(ANCIL_MAX_N_FDS)
buffer;
assert(n_fds <= ANCIL_MAX_N_FDS);
return (ancil_recv_fds_with_buffer(sock, fd, n_fds, &buffer));
}
#endif /* SPARE_RECV_FDS */
#ifndef SPARE_RECV_FD
int ancil_recv_fd(int sock, int *fd)
{
ANCIL_FD_BUFFER(1)
buffer;
return (ancil_recv_fds_with_buffer(sock, fd, 1, &buffer) > 0 ? 0 : -1);
}
#endif /* SPARE_RECV_FD */
fd_send.cpp
/***************************************************************************
* libancillary - black magic on Unix domain sockets
* (C) Nicolas George
* fd_send.c - sending file descriptors
***************************************************************************/
/*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef _XPG4_2 /* Solaris sucks */
#define _XPG4_2
#endif
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <assert.h>
#if defined(__FreeBSD__)
#include <sys/param.h> /* FreeBSD sucks */
#endif
#include "ancillary.h"
int ancil_send_fds_with_buffer(int sock, const int *fds, unsigned n_fds, void *buffer)
{
struct msghdr msghdr;
char nothing = '!';
struct iovec nothing_ptr;
struct cmsghdr *cmsg;
unsigned i;
nothing_ptr.iov_base = ¬hing;
nothing_ptr.iov_len = 1;
msghdr.msg_name = NULL;
msghdr.msg_namelen = 0;
msghdr.msg_iov = ¬hing_ptr;
msghdr.msg_iovlen = 1;
msghdr.msg_flags = 0;
msghdr.msg_control = buffer;
msghdr.msg_controllen = sizeof(struct cmsghdr) + sizeof(int) * n_fds;
cmsg = CMSG_FIRSTHDR(&msghdr);
cmsg->cmsg_len = msghdr.msg_controllen;
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
for (i = 0; i < n_fds; i++)
((int *)CMSG_DATA(cmsg))[i] = fds[i];
return (sendmsg(sock, &msghdr, 0) >= 0 ? 0 : -1);
}
#ifndef SPARE_SEND_FDS
int ancil_send_fds(int sock, const int *fds, unsigned n_fds)
{
ANCIL_FD_BUFFER(ANCIL_MAX_N_FDS)
buffer;
assert(n_fds <= ANCIL_MAX_N_FDS);
return (ancil_send_fds_with_buffer(sock, fds, n_fds, &buffer));
}
#endif /* SPARE_SEND_FDS */
#ifndef SPARE_SEND_FD
int ancil_send_fd(int sock, int fd)
{
ANCIL_FD_BUFFER(1)
buffer;
return (ancil_send_fds_with_buffer(sock, &fd, 1, &buffer));
}
#endif /* SPARE_SEND_FD */