Android利用LocalSocket实现Java端进程与C端进程之间的IPC

Android是建立在Linux之上的OS,在涉及到安全、网络协议、文件加密等功能时,往往需要通过C语言调用底层API来实现,而如何发出指令让C端执行我们想要的功能,并且在执行之后有返回结果呢,这就需要打通Java端进程和C端进程,使之能高效地通信。这样,C端进程用于实现功能,Java端进程负责UI、功能的触发及结果处理就可以了。

  对于unix系统来说,“一切皆为文件”,Socket也不例外,Socket按照收发双方的媒介来说有三种类型:1,通过网络端口;2,通过文件系统;3,通过内存映射文件。具体说来,三种类型均可以用来作为IPC的Socket:1,通过本地回环接口(即LoopBack)127.0.0.1来收发数据;2,通过文件作为收发数据的中转站;3,在内存中开辟一块区域作为收发数据的中转站,此区域仍然使用文件读写API进行访问。LocalSocket支持方式2和方式3,从效率的角度来说,显然是方式3效率最高,那么下面我们就使用LocalSocket来演示如何实现Java端进程与C端进程之间的IPC。

  以下的demo是Java端作为server,C端作为client;实际场景中可能更多的是Java端作为client,而C端作为server。

服务端代码如下:

package main.activity;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

import android.app.Activity;
import android.net.LocalServerSocket;
import android.net.LocalSocket;
import android.os.Bundle;
import android.util.Log;
 
/**
 * @author pengyiming
 * @note 启动localSocketServer
 *
 */

public class LocalSocketServerActivity extends Activity
{
    /* 数据段begin */
    private final String TAG = "server";
     
    private ServerSocketThread mServerSocketThread;
    /* 数据段end */
     
    /* 函数段begin */
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);         
        mServerSocketThread = new ServerSocketThread();
        mServerSocketThread.start();
    }
     
    @Override
    protected void onDestroy()
    {
        super.onDestroy();         
        mServerSocketThread.stopRun();
    }
    /* 函数段end */
     
    /* 内部类begin */
    private class ServerSocketThread extends Thread
    {
        private boolean keepRunning = true;
        private LocalServerSocket serverSocket;
         
        private void stopRun()
        {
            keepRunning = false;
        }
         
        @Override
        public void run()
        {
            try
            {
                serverSocket = new LocalServerSocket("pym_local_socket");
            }
            catch (IOException e)
            {
                e.printStackTrace();                 
                keepRunning = false;
            }
             
            while(keepRunning)
            {
                Log.d(TAG, "wait for new client coming !");
                 
                try
                {
                    LocalSocket interactClientSocket = serverSocket.accept();
                     
                    //由于accept()在阻塞时,可能Activity已经finish掉了,所以再次检查keepRunning
                    if (keepRunning)
                    {
                        Log.d(TAG, "new client coming !");                         
                        new InteractClientSocketThread(interactClientSocket).start();
                    }
                }
                 catch (IOException e)
                {
                    e.printStackTrace();                     
                    keepRunning = false;
                }
            }
             
            if (serverSocket != null)
            {
                try
                {
                    serverSocket.close();
                }
                catch (IOException e)
                {
                    e.printStackTrace();
                }
            }
        }
    }
     
    private class InteractClientSocketThread extends Thread
    {
        private LocalSocket interactClientSocket;
         
        public InteractClientSocketThread(LocalSocket interactClientSocket)
        {
            this.interactClientSocket = interactClientSocket;
        }
         
        @Override
        public void run()
        {
            StringBuilder recvStrBuilder = new StringBuilder();
            InputStream inputStream = null;
            try
            {
                inputStream = interactClientSocket.getInputStream();
                InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
                char[] buf = new char[4096];
                int readBytes = -1;
                while ((readBytes = inputStreamReader.read(buf)) != -1)
                {
                    String tempStr = new String(buf, 0, readBytes);
                    recvStrBuilder.append(tempStr);
                }
            }
            catch (IOException e)
            {
                e.printStackTrace();
                
                Log.d(TAG, "resolve data error !");
            }
            finally
            {
                if (inputStream != null)
                {
                    try
                    {
                        inputStream.close();
                    }
                    catch (IOException e)
                    {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    /* 内部类end */
}

客户端代码如下:

package main.activity;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;

/**
 * @author pengyiming
 * @note 用于启动localSocketClient,向server发送心跳报文
 *
 */

public class LocalSocketClientActivity extends Activity
{
    /* 数据段begin */
    private final String TAG = "client";
    
    private HeartBeatThread mHeartBeatThread;
    
    public native int startHeartBeat();
    /* 数据段end */
    
    /* 函数段begin */
    static
    {
        System.loadLibrary("pymclient");
    }
    
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        
        mHeartBeatThread = new HeartBeatThread();
        mHeartBeatThread.start();
    }
    
    @Override
    protected void onDestroy()
    {
        super.onDestroy();
        
        mHeartBeatThread.stopRun();
    }
    /* 函数段end */
    
    /* 内部类begin */
    private class HeartBeatThread extends Thread
    {
        int ret;
        boolean keepRunning = true;
        
        public void stopRun()
        {
            keepRunning = false;
        }
        
        @Override
        public void run()
        {
            Log.d(TAG, "start heart beat!");
            
            while (keepRunning)
            {
                ret = startHeartBeat();
                
                Log.d(TAG, "ret = " + ret);
                
                if (ret != 0)
                {
                    break;
                }
                
                try
                {
                    sleep(1000);
                }
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
            }
            
            Log.d(TAG, "stop heart beat!");
        }
    }
    /* 内部类end */
}

上述客户端代码启动了一个线程用于发送“心跳”报文,每隔1s构建一个新的LocalSocket,连接服务端并发送流数据,其核心就在于native方法的实现。值得一提的是,我最初使用原生socket函数,没想connect总是返回错误;后来在同事的提醒下,我参考了Android源码rild.c中socket_local_client的使用,并从socket_local_client.c中抽取出相应代码改写而成。

客户端native方法头文件:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class main_activity_LocalSocketClientActivity */

#ifndef _Included_main_activity_LocalSocketClientActivity
#define _Included_main_activity_LocalSocketClientActivity
#ifdef __cplusplus
extern "C" {
#endif

/* socket命名空间(见cutils/sockets.h) */
#define ANDROID_SOCKET_NAMESPACE_ABSTRACT 0
#define ANDROID_SOCKET_NAMESPACE_RESERVED 1
#define ANDROID_SOCKET_NAMESPACE_FILESYSTEM 2

/* socket类型 */
#define SOCK_STREAM      1
#define SOCK_DGRAM       2
#define SOCK_RAW         3
#define SOCK_RDM         4
#define SOCK_SEQPACKET   5
#define SOCK_PACKET      10

/* 清0宏 */
#define MEM_ZERO(pDest, destSize) memset(pDest, 0, destSize)

/* 错误码定义 */
#define NO_ERR 0
#define CREATE_ERR -1
#define CONNECT_ERR -2
#define LINUX_MAKE_ADDRUN_ERROR -3
#define NO_LINUX_MAKE_ADDRUN_ERROR -4
#define CLOSE_ERR -5

/* 是否使用linux的本地socket命令空间 */
#define HAVE_LINUX_LOCAL_SOCKET_NAMESPACE "linux_local_socket_namespace"

#undef main_activity_LocalSocketClientActivity_MODE_PRIVATE
#define main_activity_LocalSocketClientActivity_MODE_PRIVATE 0L
#undef main_activity_LocalSocketClientActivity_MODE_WORLD_READABLE
#define main_activity_LocalSocketClientActivity_MODE_WORLD_READABLE 1L
#undef main_activity_LocalSocketClientActivity_MODE_WORLD_WRITEABLE
#define main_activity_LocalSocketClientActivity_MODE_WORLD_WRITEABLE 2L
#undef main_activity_LocalSocketClientActivity_MODE_APPEND
#define main_activity_LocalSocketClientActivity_MODE_APPEND 32768L
#undef main_activity_LocalSocketClientActivity_MODE_MULTI_PROCESS
#define main_activity_LocalSocketClientActivity_MODE_MULTI_PROCESS 4L
#undef main_activity_LocalSocketClientActivity_BIND_AUTO_CREATE
#define main_activity_LocalSocketClientActivity_BIND_AUTO_CREATE 1L
#undef main_activity_LocalSocketClientActivity_BIND_DEBUG_UNBIND
#define main_activity_LocalSocketClientActivity_BIND_DEBUG_UNBIND 2L
#undef main_activity_LocalSocketClientActivity_BIND_NOT_FOREGROUND
#define main_activity_LocalSocketClientActivity_BIND_NOT_FOREGROUND 4L
#undef main_activity_LocalSocketClientActivity_BIND_ABOVE_CLIENT
#define main_activity_LocalSocketClientActivity_BIND_ABOVE_CLIENT 8L
#undef main_activity_LocalSocketClientActivity_BIND_ALLOW_OOM_MANAGEMENT
#define main_activity_LocalSocketClientActivity_BIND_ALLOW_OOM_MANAGEMENT 16L
#undef main_activity_LocalSocketClientActivity_BIND_WAIVE_PRIORITY
#define main_activity_LocalSocketClientActivity_BIND_WAIVE_PRIORITY 32L
#undef main_activity_LocalSocketClientActivity_BIND_IMPORTANT
#define main_activity_LocalSocketClientActivity_BIND_IMPORTANT 64L
#undef main_activity_LocalSocketClientActivity_BIND_ADJUST_WITH_ACTIVITY
#define main_activity_LocalSocketClientActivity_BIND_ADJUST_WITH_ACTIVITY 128L
#undef main_activity_LocalSocketClientActivity_CONTEXT_INCLUDE_CODE
#define main_activity_LocalSocketClientActivity_CONTEXT_INCLUDE_CODE 1L
#undef main_activity_LocalSocketClientActivity_CONTEXT_IGNORE_SECURITY
#define main_activity_LocalSocketClientActivity_CONTEXT_IGNORE_SECURITY 2L
#undef main_activity_LocalSocketClientActivity_CONTEXT_RESTRICTED
#define main_activity_LocalSocketClientActivity_CONTEXT_RESTRICTED 4L
#undef main_activity_LocalSocketClientActivity_RESULT_CANCELED
#define main_activity_LocalSocketClientActivity_RESULT_CANCELED 0L
#undef main_activity_LocalSocketClientActivity_RESULT_OK
#define main_activity_LocalSocketClientActivity_RESULT_OK -1L
#undef main_activity_LocalSocketClientActivity_RESULT_FIRST_USER
#define main_activity_LocalSocketClientActivity_RESULT_FIRST_USER 1L
#undef main_activity_LocalSocketClientActivity_DEFAULT_KEYS_DISABLE
#define main_activity_LocalSocketClientActivity_DEFAULT_KEYS_DISABLE 0L
#undef main_activity_LocalSocketClientActivity_DEFAULT_KEYS_DIALER
#define main_activity_LocalSocketClientActivity_DEFAULT_KEYS_DIALER 1L
#undef main_activity_LocalSocketClientActivity_DEFAULT_KEYS_SHORTCUT
#define main_activity_LocalSocketClientActivity_DEFAULT_KEYS_SHORTCUT 2L
#undef main_activity_LocalSocketClientActivity_DEFAULT_KEYS_SEARCH_LOCAL
#define main_activity_LocalSocketClientActivity_DEFAULT_KEYS_SEARCH_LOCAL 3L
#undef main_activity_LocalSocketClientActivity_DEFAULT_KEYS_SEARCH_GLOBAL
#define main_activity_LocalSocketClientActivity_DEFAULT_KEYS_SEARCH_GLOBAL 4L
/*
 * Class:     main_activity_LocalSocketClientActivity
 * Method:    startHeartBeat
 * Signature: ()I
 */
JNIEXPORT jint JNICALL Java_main_activity_LocalSocketClientActivity_startHeartBeat
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

客户端native方法实现:

/* 头文件begin */
#include "main_activity_LocalSocketClientActivity.h"

#include <sys/socket.h>
#include <sys/un.h>
#include <stddef.h>
#include <string.h>
/* 头文件end */

#ifdef __cplusplus
extern "C" {
#endif

/*
 * Class:     main_activity_LocalSocketClientActivity
 * Method:    startHeartBeat
 */
JNIEXPORT jint JNICALL Java_main_activity_LocalSocketClientActivity_startHeartBeat(JNIEnv * env, jobject object)
{
    int socketID;
    struct sockaddr_un serverAddr;
    char path[] = "pym_local_socket\0";
    int ret;

    socketID = socket_local_client(path, ANDROID_SOCKET_NAMESPACE_ABSTRACT, SOCK_STREAM);
    if (socketID < 0)
    {
        return socketID;
    }

    ret = close(socketID);
    if (ret < 0)
    {
        return CLOSE_ERR;
    }

    return NO_ERR;
}

/* 创建本地socket客户端 */
int socket_local_client(const char *name, int namespaceId, int type)
{
    int socketID;
    int ret;

    socketID = socket(AF_LOCAL, type, 0);
    if(socketID < 0)
    {
        return CREATE_ERR;
    }

    ret = socket_local_client_connect(socketID, name, namespaceId, type);
    if (ret < 0)
    {
        close(socketID);

        return ret;
    }

    return socketID;
}

/* 连接到相应的fileDescriptor上 */
int socket_local_client_connect(int fd, const char *name, int namespaceId, int type)
{
    struct sockaddr_un addr;
    socklen_t socklen;
    size_t namelen;
    int ret;

    ret = socket_make_sockaddr_un(name, namespaceId, &addr, &socklen);
    if (ret < 0)
    {
        return ret;
    }

    if(connect(fd, (struct sockaddr *) &addr, socklen) < 0)
    {
        return CONNECT_ERR;
    }

    return fd;
}

/* 构造sockaddr_un */
int socket_make_sockaddr_un(const char *name, int namespaceId, struct sockaddr_un *p_addr, socklen_t *socklen)
{
    size_t namelen;

    MEM_ZERO(p_addr, sizeof(*p_addr));
#ifdef HAVE_LINUX_LOCAL_SOCKET_NAMESPACE

    namelen  = strlen(name);

    // Test with length +1 for the *initial* '\0'.
    if ((namelen + 1) > sizeof(p_addr->sun_path))
    {
        return LINUX_MAKE_ADDRUN_ERROR;
    }
    p_addr->sun_path[0] = 0;
    memcpy(p_addr->sun_path + 1, name, namelen);

#else

    namelen = strlen(name) + strlen(FILESYSTEM_SOCKET_PREFIX);

    /* unix_path_max appears to be missing on linux */
    if (namelen > (sizeof(*p_addr) - offsetof(struct sockaddr_un, sun_path) - 1))
    {
        return NO_LINUX_MAKE_ADDRUN_ERROR;
    }

    strcpy(p_addr->sun_path, FILESYSTEM_SOCKET_PREFIX);
    strcat(p_addr->sun_path, name);

#endif

    p_addr->sun_family = AF_LOCAL;
    *socklen = namelen + offsetof(struct sockaddr_un, sun_path) + 1;

    return NO_ERR;
}

#ifdef __cplusplus
}
#endif

注意到100~101行比较特殊,是从p_addr->sun_path[1]开始拷贝本地域名,这就是之前为什么一直connect不上的原因,至于为什么偏移1个字节来拷贝本地域名,你可以在unix系统下输入"man 7 unix"来找到原因。

  先启动server,再启动client就可以看到结果了。对了,在成功创建并已自动连接后,我并未发送任何数据,其实发送数据就是写入文件,It's your trun now! 在close之前加入这段代码吧~

int ret;
char buf[] = "hello";

ret = write(socketID, buf, strlen(buf));


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值