空道 发表于 2014-11-27 12:14:29

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

本帖最后由 空道 于 2014-11-27 13:58 编辑

Android是建立在Linux之上的OS,在涉及到安全、网络协议、文件加密等功能时,往往需要通过C语言调用底层API来实现,而如何发出指令让C端执行我们想要的功能,并且在执行之后有返回结果呢,这就需要打通Java端进程和C端进程,使之能高效地通信。这样,C端进程用于实现功能,Java端进程负责UI、功能的触发及结果处理就可以了。  对于*nix系统来说,“一切皆为文件”,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;                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 __cplusplusextern "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 __cplusplusextern "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;    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开始拷贝本地域名,这就是之前为什么一直connect不上的原因,至于为什么偏移1个字节来拷贝本地域名,你可以在*nix系统下输入"man 7 unix"来找到原因。  先启动server,再启动client就可以看到结果了。对了,在成功创建并已自动连接后,我并未发送任何数据,其实发送数据就是写入文件,It's your trun now! 在close之前加入这段代码吧~Int ret;Char buf[] = “hello”;Ret = write(socketID, buf, strlen(buf));转载自:http://www.cnblogs.com/zealotrouge/p/3152941.html//////////////////////////////////////////////补充socket_local_server 函数
#define LISTEN_BACKLOG 4/*单次同步, 把信号量先初始化为0*/
/** * Binds a pre-created socket(AF_LOCAL) 's' to 'name' * returns 's' on success, -1 on fail * * Does not call listen() */int socket_local_server_bind(int s, const char *name, int namespaceId){    struct sockaddr_un addr;    socklen_t alen;    int n;    int err;
    err = socket_make_sockaddr_un(name, namespaceId, &addr, &alen);
    if (err < 0) {      return -1;    }
    /* basically: if this is a filesystem path, unlink first */#ifndef HAVE_LINUX_LOCAL_SOCKET_NAMESPACE    if (1) {#else    if (namespaceId == ANDROID_SOCKET_NAMESPACE_RESERVED      || namespaceId == ANDROID_SOCKET_NAMESPACE_FILESYSTEM) {#endif      /*ignore ENOENT*/      unlink(addr.sun_path);    }
    n = 1;    setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &n, sizeof(n));
    if(bind(s, (struct sockaddr *) &addr, alen) < 0) {      return -1;    }
    return s;
}
int socket_local_server(const char *name, int namespace, int type){    int err;    int s;
    s = socket(AF_LOCAL, type, 0);    if (s < 0) return -1;
    err = socket_local_server_bind(s, name, namespace);
    if (err < 0) {      close(s);      return -1;    }
    if (type == SOCK_STREAM) {      int ret;
      ret = listen(s, LISTEN_BACKLOG);
      if (ret < 0) {            close(s);            return -1;      }    }
    return s;}

GGLHY 发表于 2014-11-27 12:57:06

学习了~~

牛X的空道{:soso_e179:}

开心啦 发表于 2014-11-27 16:20:25

牛叉,顶起来

忧郁之子 发表于 2014-11-28 00:14:12

虽然看得不少太明白,但是还是支持一下

sndncel 发表于 2014-11-28 00:14:38

谢谢楼主分享呀。。。。。不过看到这么多的英文就头痛呀。。。。。

xdnice 发表于 2014-12-13 12:01:12

这篇文章我转走。
页: [1]
查看完整版本: Android利用LocalSocket实现Java端进程与C端进程之间的IPC