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之外了另一個選擇,功能很強大,大家一定要多加練習,熟練掌握本篇的知識點,以後才能在需要的時候熟練運用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章