1. 程式人生 > >Android NDK網路通訊篇(五)之本地通訊篇

Android NDK網路通訊篇(五)之本地通訊篇

Android NDK網路通訊篇(五)

本地通訊篇

前言

在同一個裝置或者同一個APP裡面,我們可以通過LocalSocket來實現本地通訊,比如可以用Java程式碼實現一個本地通訊的C/S架構的程式,也可以用Java程式碼實現客戶端程式碼,用原生程式碼實現服務端程式碼,本篇重點講解後一種。

本地socket通訊和TCP以及UDP通訊的區別在於本地socket通訊不需要IP地址和埠號,只需要一個名稱空間名稱即可。這樣我們就可以很方便的在同一個裝置的不同應用之間進行通訊,是不是很強大?在不同應用之間進行通訊,除了使用Messenger和binder,本地socket通訊給我們提供了別一種選擇。

本地通訊相關的標頭檔案

#include <sys/socket.h>

#include <sys/un.h>

#include <sys/endian.h>

#include <sys/types.h>

#include <netinet/in.h>

#include <arpa/inet.h>

#include <unistd.h>

本地通訊函式解析

建立本地socket

int socket(int domain, int type, int protocal);

引數解析:

domain:指定將會產生通訊的socket域,並且選擇用到的協議族。android平臺目前支援以下協議族:

l  PF_LOCAL:主機內部通訊協議族,該協議族使用物理上執行在同一臺裝置上的應用程式可以使用Socket APIs進行通訊。

l  PF_INET:IPv4協議族,該協議族使得執行的應用程式可以與網路上的其它應用程式進行通訊。

type:指通訊的型別,支援以下兩種型別:

SOCK_STREAM:供TCP協議使用。

SOCK_DGRAM:供UDP協議使用。

protocal:指定將會用到的協議。對於大部分協議族和協議型別來說,只能使用一個協議。為了選擇預設的協議,該引數可以設為零。

如果socket建立成功,將會返回對應的socket描述符,否則返回-1。

示例程式碼:

int NewLocalSocket(JNIEnv

*env,jobject obj){
    int sd=socket(PF_LOCAL,SOCK_STREAM,0);
    return sd;
}

繫結本地socket

int bind(int socketDescriptor, const struct sockaddr* address, int addressLength);

引數解析:

socketDescriptor:指服務端socket

address:指socket要繫結的服務端地址結構體

addressLength:指定address結構體的大小

如果繫結成功,返回0,否則返回-1

示例程式碼:

int BindLocalSocket(JNIEnv *env,jobject obj,int sd,const char *name){
    sockaddr_un address;
    memset(&address,0, sizeof(address));
    address.sun_family=AF_LOCAL;

    char *sunPath=address.sun_path;

    size_t nameLen=strlen(name);
    socklen_t pathLen=nameLen;
    pathLen++;
    *sunPath++=NULL;

    strcpy(sunPath,name);

    socklen_t socklen=(offsetof(sockaddr_un,sun_path))+ pathLen;

    unlink(address.sun_path);

    int result=bind(sd,(sockaddr *)&address,socklen);
    return result;
}

監聽本地連線

int listen(int socketDescriptor, int backlog);

引數解析:

socketDescriptor:指服務端socket

backlog:指服務端可以接受的最大的連線數,如果連線請求超過這個最大數,超過的請求就會排隊。

如果函式呼叫成功返回0,否則返回-1

示例程式碼:

int ListenOnSocket(JNIEnv *env,jobject obj,int sd,int backlog){
    int result=listen(sd,backlog);
    return result;
}

接受本地連線

int accept(int socketDescriptor, struct sockaddr* address, socklen_t* addressLength);

引數解析:

socketDescriptor:指服務端socket

address:指sockaddr結構體,這個結構體將被填入客戶端的資訊

addressLength:address結構體的大小

如果函式呼叫成功返回0,否則返回-1

示例程式碼:

int AcceptClientSocket(JNIEnv *env,jobject obj,int sd){
    sockaddr_in address;
    socklen_t len= sizeof(address);
    int client_socket=accept(sd,(sockaddr*)&address,&len);
    return client_socket;
}

接收本地訊息

ssize_t recv(int socketDescriptor, void* buffer, size_t bufferLength, int flags);

引數解析:

socketDescriptor:指客戶端socket

buffer:指接收的資料緩衝區指標

bufferLength:指接收的資料緩衝區的大小

flags:指接收需要的額外標誌,預設傳0

如果函式呼叫成功返回0,否則返回-1

示例程式碼:

int ReceiveFromSocket(JNIEnv *env,jobject obj,int sd,char *buffer,int bufferLen){
    int receive_size=recv(sd,buffer,bufferLen-1,0);
    return receive_size;
}

傳送本地訊息

ssize_t send(int socketDescriptor, const void* buffer, size_t bufferLength, int flags);

引數解析:

socketDescriptor:指客戶端socket

buffer:指接收的資料緩衝區指標

bufferLength:指接收的資料緩衝區的大小

flags:指接收需要的額外標誌,預設傳0

如果函式呼叫成功返回0,否則返回-1

示例程式碼:

int SendToSocket(JNIEnv *env,jobject obj,int sd,const char *buffer,int bufferLen){
    int send_size=send(sd,buffer,bufferLen,0);
    return send_size;
}

獲取客戶端socket 資訊

int getpeername(int socketDescriptor, struct sockaddr* address, socklen_t* addressLength);

引數解析:

socketDescriptor:指客戶端socket

address:指sockaddr結構體,這個結構體將被填入客戶端的資訊

addressLength:address結構體的大小

如果函式呼叫成功返回0,否則返回-1

示例程式碼:

struct SocketInfo{
    int port;
    const char *ip;
};

SocketInfo * GetClientSocketInfo(JNIEnv *env,jobject obj,int sd){
    sockaddr_in address;
    socklen_t addressLength= sizeof(address);
    getpeername(sd,(sockaddr *)&address,&addressLength);
    SocketInfo *socketInfo=new SocketInfo;
    socketInfo->port=ntohs(address.sin_port);
    socketInfo->ip=inet_ntoa(address.sin_addr);
    return socketInfo;
}

獲取服務端socket資訊

int getsockname(int socketDescriptor, struct sockaddr* address, socklen_t* addressLength);

引數解析:

socketDescriptor:指服務端socket

address:指sockaddr結構體,這個結構體將被填入服務端的資訊

addressLength:address結構體的大小

如果函式呼叫成功返回0,否則返回-1

示例程式碼:

struct SocketInfo{
    int port;
    const char *ip;
};

SocketInfo * GetServerSocketInfo(JNIEnv *env,jobject obj,int sd){
    sockaddr_in address;
    socklen_t addressLength= sizeof(address);
    getsockname(sd,(sockaddr *)&address,&addressLength);
    SocketInfo *socketInfo=new SocketInfo;
    socketInfo->port=ntohs(address.sin_port);
    socketInfo->ip=inet_ntoa(address.sin_addr);
    return socketInfo;
}

連線服務端socket

int connect(int socketDescriptor, const struct sockaddr* address, socklen_t addressLength);

引數解析:

socketDescriptor:指客戶端socket

address:指要連線的服務端地址結構體

addressLength:指定address結構體的大小

如果繫結成功,返回0,否則返回-1

示例程式碼:

sockaddr_in getSockaddr_in(const char *ip,int port){
    sockaddr_in address;
    memset(&address,0, sizeof(sockaddr_in));
    address.sin_family=AF_INET;
    address.sin_port=htons(port);
    inet_aton(ip,&address.sin_addr);

    return address;
}

int ConnectSocket(JNIEnv *env,jobject obj,int sd,const char *ip,int port){
    sockaddr_in address=getSockaddr_in(ip,port);

    int result=connect(sd,(sockaddr *)&address, sizeof(address));
    return result;
}

本地服務端

本地服務端程式流程圖

1.   建立TCP socket(socket())

2.   繫結TCP socket(bind())

3.   監聽TCP連線(listen())

4.   接受TCP連線(accept())

5.   接收TCP訊息(recv())

6.   傳送TCP訊息(send())

本地服務端程式示例

void *localSocketServerThread(void * args){
    JNIEnv *env;
    gVM->AttachCurrentThread(&env,NULL);
    int server_socket=NewLocalSocket(env,gThiz);
    int result=BindLocalSocket(env,gThiz,server_socket,"kgdwbb");
    SocketInfo *serverSocketInfo=GetServerSocketInfo(env,gThiz,server_socket);
    __android_log_print(ANDROID_LOG_VERBOSE,"hello","client ip= %s,client port=%d",serverSocketInfo->ip,serverSocketInfo->port);
   ListenOnSocket(env,gThiz,server_socket,100);


    int client_socket=AcceptClientSocket(env,gThiz,server_socket);
    SocketInfo *clientSocketInfo=GetClientSocketInfo(env,gThiz,client_socket);

    __android_log_print(ANDROID_LOG_VERBOSE,"hello","client ip= %s,client port=%d",clientSocketInfo->ip,clientSocketInfo->port);

    while(true){
        char buffer[1024];
        int receive_size=ReceiveFromSocket(env,gThiz,client_socket,buffer,1024);
        if(receive_size==0 || env->ExceptionOccurred()!=NULL) break;
        if(receive_size>0){
            __android_log_print(ANDROID_LOG_VERBOSE,"hello","receive datafrom client is %s",buffer);

            int send_size=SendToSocket(env,gThiz,client_socket,buffer,receive_size);
            if(send_size==0 ||env->ExceptionOccurred()!=NULL) break;
            __android_log_print(ANDROID_LOG_VERBOSE,"hello","send data toclient is %s",buffer);
        }
    }

    CloseSocket(env,gThiz,server_socket);
    __android_log_print(ANDROID_LOG_VERBOSE,"hello","%s","serversocket close");

    gVM->DetachCurrentThread();

    return (void *)1;
}

extern "C"
JNIEXPORT void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_startLocalServerSocket(JNIEnv* env, jobject thiz){
    if(gThiz==NULL){
        gThiz=env->NewGlobalRef(thiz);
    }
    pthread_t pthread;
    pthread_create(&pthread,NULL,localSocketServerThread,NULL);
}

JAVA服務端程式示例

private void startJavaLocalServerSocket(){
    try
   
{
        LocalServerSocketlocalServerSocket=new LocalServerSocket("kgdwbb");
        LocalSocketlocalSocket=localServerSocket.accept();
        InputStreamis=localSocket.getInputStream();
        OutputStreamos=localSocket.getOutputStream();
        while (true){
            byte[]buffer=new byte[1024];
            int readed=is.read(buffer);
            if(readed>0){
                buffer[readed]=0;
                Log.v("hello","receivedfrom local socket is "+new String(buffer));

                os.write(buffer);
                os.flush();

                Log.v("hello","send tolocal socket is "+new String(buffer));
            }
            else if(readed==-1){
                break;
            }
        }
        localServerSocket.close();
    }
    catch (Exception e){
        e.printStackTrace();
    }
}

JAVA客戶端

JAVA客戶端程式示例

private void startLocalSocket(){
    LocalSocket localSocket=new LocalSocket();

    LocalSocketAddresslocalSocketAddress=new LocalSocketAddress("kgdwbb");
    try
   
{
        localSocket.connect(localSocketAddress);
        if(localSocket.isConnected()){
            OutputStreamos=localSocket.getOutputStream();
            InputStreamis=localSocket.getInputStream();
            byte[]data="helloboy".getBytes();
            os.write(data);
            os.flush();

            byte[] buffer=new byte[1024];
            int readed=is.read(buffer);
            buffer[readed]=0;
            Log.v("hello",new String(buffer,0,readed));

            localSocket.close();
        }
    }
    catch (Exception e){
        e.printStackTrace();
    }
}

結束語

本篇重點講解了通過本地socket進行通訊的相關知識,並通過一個Java客戶端和一個原生服務端的示例為大家詳細講解了本地socket通訊的流程。本地socket的出現,給同一裝置不同應用程式通訊提供了除Messenger和binder之外了另一個選擇,功能很強大,大家一定要多加練習,熟練掌握本篇的知識點,以後才能在需要的時候熟練運用。