android socket communication

介绍

最近一直在弄一些Android跟socket相关的东西,打算好好总结一下Android socket的使用。其实Android是将进程的概念给弱化了的,但是进程还是存在的,基本上一般的app,如果你没有特殊处理的话,一个app就是一个进程。我们平时开发App的时候,更多的是基于Android基本组件概念的,也就是说Android四大组件 Activity,Service,BroadCast receiver,Content provider。但是如果需要在进程间通信,socket 是一个很不错的选择。当然,上一篇博客也讲到过管道。管道也是可以实现进程间通信的。下面主要从NDK以及java层对Android socket 相关的一些东西进行总结介绍。

我们在其他地方使用socket 一般是一个地址,一个端口。但是在Android中,建立socket 是通过一个叫做socketName 的东西。客户端和服务器端使用同一个socketName就可以建立连接。服务器端建一个指定socketName的socketserver,客户端连接到指定socketName的Socket服务器。下面介绍具体的方式。

NDK 层

android jni socket 其实就是linux socket。本身android就是基于linux 内核的。所以linux那一套东西,在android上面基本上是适用的,当然也有一些不同。

Socket Server

首先描述一下整个过程。先通过函数android_get_socket(SOCKET_NAME)来获取一个监听号。然后使用得到的监听号,以及设置的最大请求数,监听这个监听号。最后调用accept 函数等待请求。接收到数据方面的东西其实跟linux是一样的。下面看从网友提供的代码简化的具体的代码

#define SOCKET_NAME "socketnamexxx"

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/un.h>
#include <cutils/sockets.h>
#include <utils/Log.h>
#include <android/log.h>


int main(){
    char log[200];

    int connect_number = 6;
    int fdListen = -1, new_fd = -1;
    int ret;
    struct sockaddr_un peeraddr;
    socklen_t socklen = sizeof (peeraddr);
    int numbytes ;
    char buff[256];
    //这一步很关键,就是获取init.rc中配置的名为 "htfsk" 的socket
    //fdListen = android_get_control_socket(SOCKET_NAME);
    //上面这个api 可能在高版本的ndk中已经不存在了。
    fdListen = socket(AF_LOCAL, SOCK_STREAM, PF_UNIX);
    if(fdListen < 0)
        return (false);

    // set socket is reusable
    int option = true;
    if(setsockopt(fdListen, SOL_SOCKET, SO_REUSEADDR, (char *)&option, sizeof(option)) < 0)
    {
        //close(fdListen);
        return (false);
    }

    if(bind(fdListen, (const struct sockaddr*)& peeraddr, socklen) < 0)
    {
        close(fdListen);
        return (false);
    }



    if (fdListen < 0) {
        sprintf(log,"Failed to get socket '" SOCKET_NAME "' errno:%d", errno);
        __android_log_write(ANDROID_LOG_DEBUG,"FTM_JNI",log);
        exit(-1);
    }
    //开始监听
    ret = listen(fdListen, connect_number);    


    if (ret < 0) {  //小于0 表示监听失
        sprintf(log,"Listen result %d",ret);
            __android_log_write(ANDROID_LOG_DEBUG,"FTM_JNI",log);
        exit(-1);
    }
    //等待Socket客户端发启连接请求
    new_fd = accept(fdListen, (struct sockaddr *) &peeraddr, &socklen);
    sprintf(log,"Accept_fd %d",new_fd);
    __android_log_write(ANDROID_LOG_DEBUG,"FTM_JNI",log);
    if (new_fd < 0 ) {
        sprintf(log,"%d",errno);
        __android_log_write(ANDROID_LOG_DEBUG,"FTM_JNI",log);
        perror("accept error");
        exit(-1);
    }

    while(1){
        //循环等待Socket客户端发来消息
        __android_log_write(ANDROID_LOG_DEBUG,"FTM_JNI","Waiting for receive");
        if((numbytes = recv(new_fd,buff,sizeof(buff),0))==-1){
            sprintf(log,"%d",errno);
            __android_log_write(ANDROID_LOG_DEBUG,"FTM_JNI",log);
            perror("recv");
            continue;
        }
        //发送消息回执给Socket客户端
        if(send(new_fd,buff,strlen(buff),0)==-1)
        {
            perror("send");
            close(new_fd);
            exit(0);
        }        
    }

    close(new_fd);
    close(fdListen);
    return 0;
}

其实认真跟linux的函数比较一下就会发现。这个函数android_get_socket(SOCKET_NAME) 是将端口,都整合好了。通过SocketName来指定对应的位置。弱化了原本端口和地址的概念。其他的accept,接收recv 和发送 send 其实是跟linux一样的。

Socket Client

Client 也是依据SOCKET_NAME 来与对应的Server建立连接。具体的Demo程序如下:

char[10] socketName = "socketnamexxx"; //socketName  
int clientFD = 0 ;
socklen_t serverLen = 0;
struct sockaddr_un serverAddr;
memset(&serverAddr, 0, sizeof(serverAddr));
//init socket
// check socket name to avoid overflow
if (strlen(socketName) > (int) sizeof(serverAddr.sun_path))
    return 0;

serverAddr.sun_path[0] = '\0';
strcpy(serverAddr.sun_path + 1, socketName);
serverAddr.sun_family = AF_LOCAL;
serverLen = 1 +  strlen(socketName) + offsetof(struct sockaddr_un, sun_path);

if ((clientFD = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
    __android_log_write(ANDROID_LOG_ERROR, LOGTAG_CCLIENT,
            "create socket error");
    return 0;
}

sockaddr pserverAddr = (sockaddr) serverAddr;
if (connect(&clientFD, &pserverAddr, serverLen) == -1) {
    //连接失败        
    return 0;
}

char[6] data="hello";
if (send(clientFD, data, 6, 0) == -1) {
    __android_log_write(ANDROID_LOG_ERROR, LOGTAG_CCLIENT,
            "send message error");
    close(clientFD);
    return 0;
}

Java层

在java层的话,其实相对来说就简单了。当然指定位置的同样是SocketName。另外两个就是LocalSocket 跟LocalServerSocket了。接着就是java 通用的输入输出流了。

借用一下网友的图片来直接描述一下他们直接的关系:

android-socket.jpg

在Client Socket 范例代码是:

LocalSocket socket = new LocalSocket();
LocalSocketAddress address = new LocalSocketAddress(SocketName,LocalSocketAddress.Namespace.RESERVED);//第二个参数是命名空间,直接用上面那个enum 选择一个就好
socket.connect(address);


//socket.getInputStream(); 读取
//socket.getOutputStream(); 写入

在Server Socket 如下:

LocalServerSocket server = new LocalServerSocket(SocketName);
try{
    LocalSocket receiver = server.accept();
}catch(IOException e){
    e.printStackTrace();
}

//读取与写入跟客户端类似,都是利用流来做的。
//receiver.getInputStream(); 读取
//receiver.getOutputStream(); 写入

总结

其实Socket 连接关键还是在于地址。客户端和服务器端,其实都是指定了相同的地址。服务器端监听,客户端向该地址请求。在Android socket中,它替换了直接的地址,而是用名称来标志,这样就变得更加简单。但是万变不离其宗,概念还是依据于基本的socket的。

之后再写一个demo程序。但是个人有时候觉得,demo能够让你快速了解,但是到了一定程度后,跑个程序时累赘。信手拈来才是Perfect的。

https://github.com/xxxzhi/AndroidSocketDemo


大四已经逐渐完了,在读研方面,曾对我有醍醐灌顶之效的一句话是:厚积薄发。