Android Socket通信--通過jni用c++實現客戶端

講比較重要的代碼放到native層是比較好的做法。如果你有需求需要把socket通信的部分封裝的jni中實現,那麼本文可做參考。

代碼思路

1.總共實現三個native方法:

    public native void initSocket(String ip,int port);
    public native void closeSocket();
    public native void sendData(byte[] buffer);

2.initSocket中創建套接字,並連接指定ip和端口的服務器,連接成功後創建線程,由於總是客戶端發起請求,因此,當有數據發送給服務斷後,喚醒該線程,阻塞讀取來自服務端的消息。然後將讀到的消息發送到java層,這裏只需從native調用java的方法,並把讀到的數據傳入即可。
3.sendData送來發送數據,它不在本地另開闢線程,需要在java層中開闢線程後調用它向服務器發送請求。
需要注意的點
1.c++中,子線需要首先調用javavm->AttachCurrentThread(&SocketDataDealThread::env,NULL);來獲得JNIEnv *的變量,然後使用該變量做進一步操作。
2.c++中跨線程使用JNI創建的變量時,要將其創建爲全局引用:

    gObj = env->NewGlobalRef(thiz);
    mSTh = new SocketDataDealThread(socketFd,gObj);

創建工程

android studio2.2版本開始完善了對C/C++ ,請參考開發者文檔:向您的項目添加 C 和 C++ 代碼。文檔是中文的。
本文的工程如圖所示:
這裏寫圖片描述
若感興趣,本文的源碼請從這裏下載:(免積分的)AndoridJniSocket.zip

本文運行結果展示:
這裏寫圖片描述

操作方式

1.輸入Ip和端口號
2.點INIT CLIENT按鈕,連接服務器成功後,INIT CLIENT會變成CONNECTED字樣,並且按鈕無法點擊,背景編程深灰色。
3.輸入發送內容並點擊發送按鈕,會看到read:後面出現了你發送的內容。這是因爲服務器目前只會將它收到的數據原樣返回。
4.點擊CLOSE按鈕斷開連接。

服務器代碼

服務器端的代碼比較簡單,線貼出來:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class SockeTest {

    public static void main(String[] args) {
        boolean isgo=true;
        int len;
        // TODO Auto-generated method stub
        /**
         * 基於TCP協議的Socket通信,實現用戶登錄,服務端
        */
        System.out.println("start ...");
        //1、創建一個服務器端Socket,即ServerSocket,指定綁定的端口,並監聽此端口
        ServerSocket serverSocket;
        try {
            serverSocket = new ServerSocket(10010);
            //2、調用accept()方法開始監聽,等待客戶端的連接
            Socket socket = null;
            InputStream is = null;
            InputStreamReader isr = null;
            OutputStream os = null;
            BufferedReader br = null;
            while(isgo){
                System.out.println("start accept");
                socket = serverSocket.accept();
                System.out.println("after accept");
                //3、獲取輸入流,並讀取客戶端信息
                is = socket.getInputStream();
                os = socket.getOutputStream();
                isr =new InputStreamReader(is);
                br =new BufferedReader(isr);
                String info =null;
                System.out.println("before read");
                byte[] bbbb = new byte[100];
                while((len = is.read(bbbb))>0){
                    System.out.println(String.valueOf(bbbb));
                    byte[] bbbbb = new byte[len];
                    System.arraycopy(bbbb, 0,bbbbb, 0, len);
                    os.write(bbbbb);
                    os.flush();
                }

                System.out.println("someone connect me");
            }

            //5、關閉資源
            br.close();
            isr.close();
            is.close();
            socket.close();
            serverSocket.close();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }   

    }

}

客戶端

MainActivity.java


import android.graphics.Color;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native_socket");
    }
    Button send;
    Button close;
    Button init;
    EditText ip;
    EditText port;
    EditText sendEdit;
    TextView readText;
    Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case 11:
                    readText.setText(msg.getData().getString("data"));
                    break;
            }

        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        send = (Button) findViewById(R.id.send_button);
        close = (Button) findViewById(R.id.close_button);
        init = (Button) findViewById(R.id.init_button);
        ip = (EditText)findViewById(R.id.ip);
        port = (EditText)findViewById(R.id.port);
        sendEdit = (EditText) findViewById(R.id.send_edit);
        readText = (TextView) findViewById(R.id.read_text);
        init.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                initSocket(ip.getText().toString(),Integer.valueOf(port.getText().toString()));
                init.setText("connected");
                init.setClickable(false);
                init.setBackgroundColor(Color.DKGRAY);
            }
        });
        send.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        sendData(sendEdit.getText().toString().getBytes());
                    }
                }).start();
            }
        });

        close.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        closeSocket();
                    }
                }).start();
            }
        });

    }
    public void setRecevieData(byte[] bb){
        Log.d("native_socket","setRecevieData");
        char[] cc = new char[bb.length];
        for(int i=0;i<bb.length;i++){
            cc[i]=(char)bb[i];
        }
        Log.d("native_socket",String.valueOf(cc));
        Message message = new Message();
        message.what = 11;
        Bundle bundle = new Bundle();
        bundle.putString("data",String.valueOf(cc));
        message.setData(bundle);
        handler.sendMessage(message);
    }

    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    public native void initSocket(String ip,int port);
    public native void closeSocket();
    public native void sendData(byte[] buffer);
}

nativeSocket.cpp

–註冊本地方法

#include <stdlib.h>
#include <stdio.h>
#include <jni.h>
#include <assert.h>
#include <pthread.h>
#include <termios.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <jni.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include <arpa/inet.h>
#include "my_log.h"
#include "StringTools.h"
#include "SocketDataDealThread.h"


extern "C"{

}
static int socketFd=0;
static jobject gObj;
static JNIEnv *gEnv;
static SocketDataDealThread *mSTh;
static void initSocket(JNIEnv* env, jobject thiz,jstring ip,jint port){
    LOGI("%s:initSocket",TAG);
    char *lip = StringTools::jstringTostring(env,ip);
    socketFd = socket(AF_INET,SOCK_STREAM, 0);
    struct timeval timeout = {1,0};
    //設置發送超時
    setsockopt(socketFd,SOL_SOCKET,SO_SNDTIMEO,(char *)&timeout,sizeof(struct timeval));
//設置接收超時
    setsockopt(socketFd,SOL_SOCKET,SO_RCVTIMEO,(char *)&timeout,sizeof(struct timeval));
    //接收緩衝區設置爲32K
    int nRecvBuf = 32*1024;
    setsockopt(socketFd,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));
    //發送緩衝區設置爲32K
    int nSendBuf = 32*1024;
    setsockopt(socketFd,SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));
    int nZero=0;
    setsockopt (socketFd,SOL_SOCKET,SO_SNDBUF,(const char *)&nZero,sizeof(nZero));
    ///定義sockaddr_in
    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = inet_addr(lip);  ///服務器ip
    servaddr.sin_port = htons(port);  ///服務器端口
    ///連接服務器,成功返回0,錯誤返回-1
    assert(connect(socketFd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == 0);
    gObj = env->NewGlobalRef(thiz);
    mSTh = new SocketDataDealThread(socketFd,gObj);
    LOGI("%s:initSocket,ip = %s,port = %d",TAG,lip,port);
}
static void closeSocket(JNIEnv* env, jobject thiz){
    void *status;
    LOGI("%s:closeSocket",TAG);
    SocketDataDealThread::isShoudExit = true;
    mSTh->wakeUpThread();
    pthread_join(mSTh->getSocketThreadId(), &status);
    delete mSTh;
    env->DeleteGlobalRef(gObj);
    close(socketFd);
}
static void sendData(JNIEnv* env, jobject thiz,jbyteArray buffer){
    jbyte *lb = env->GetByteArrayElements(buffer, NULL);
    int length = env->GetArrayLength(buffer);
    LOGI("%s:sendData,%s,length : %d",TAG,lb,length);
    mSTh->sendData((char *)lb,length);
    env->ReleaseByteArrayElements(buffer,lb,0);
}
static JNINativeMethod gMethods[] = {
        { "initSocket", "(Ljava/lang/String;I)V",(void*) initSocket },
        { "closeSocket", "()V",(void*) closeSocket },
        { "sendData", "([B)V",(void*) sendData },
};

/*
 * 爲某一個類註冊本地方法
 */
static int registerNativeMethods(JNIEnv* env, const char* className,
                                 JNINativeMethod* gMethods, int numMethods) {
    jclass clazz;
    clazz = env->FindClass(className);
    if (clazz == NULL) {
        return JNI_FALSE;
    }
    if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
        return JNI_FALSE;
    }

    return JNI_TRUE;
}

/*
 * 爲所有類註冊本地方法
 */
static int registerNatives(JNIEnv* env) {
    const char* kClassName = "com/sharenew/androidjnitsocket/MainActivity"; //指定要註冊的類
    return registerNativeMethods(env, kClassName, gMethods,
                                 sizeof(gMethods) / sizeof(gMethods[0]));
}

/*
 * System.loadLibrary("lib")時調用
 * 如果成功返回JNI版本, 失敗返回-1
 */
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
    JNIEnv* env = NULL;
    jint result = -1;
    SocketDataDealThread::javavm = vm;
    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        return -1;
    }
    assert(env != NULL);

    if (!registerNatives(env)) { //註冊
        return -1;
    }
    //成功
    result = JNI_VERSION_1_4;

    return result;
}

SocketDataDealThread.cpp

–創建子線,接受服務器發來的數據,並調用java層的方法處理數據

//
// Created by Administrator on 2017/3/3 0003.
//
#include <sys/socket.h>
#include <jni.h>
#include "my_log.h"
#include "SocketDataDealThread.h"
pthread_cond_t SocketDataDealThread::cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t SocketDataDealThread::mutex = PTHREAD_MUTEX_INITIALIZER;
int SocketDataDealThread::socketFd = 0;
char * SocketDataDealThread::getBuffer = NULL;
bool SocketDataDealThread::isShoudExit = false;
JNIEnv* SocketDataDealThread::env=NULL;
jobject SocketDataDealThread::obj=NULL;
JavaVM*  SocketDataDealThread::javavm = NULL;

void *SocketDataDealThread::clientThread(void *args) {
    javavm->AttachCurrentThread(&SocketDataDealThread::env,NULL);

    LOGI("%s:SocketDataDealThread is running",TAG);
    while (!SocketDataDealThread::isShoudExit){
        pthread_mutex_lock(&SocketDataDealThread::mutex);
        pthread_cond_wait(&SocketDataDealThread::cond,&SocketDataDealThread::mutex);
        pthread_mutex_unlock(&SocketDataDealThread::mutex);
        LOGI("%s:clientThread wake ",TAG);
        int len = recv(socketFd,getBuffer, sizeof(getBuffer),0);
        LOGI("%s:get data %s,len = %d",TAG,getBuffer,len);
       if(SocketDataDealThread::env!=NULL && SocketDataDealThread::obj){
        jclass cls = SocketDataDealThread::env->GetObjectClass(SocketDataDealThread::obj);
        //           if(cls == NULL){
        //               LOGI("%s:find class error",TAG);
        //               pthread_exit(NULL);
        //           }
        jmethodID mid = SocketDataDealThread::env->GetMethodID(cls,"setRecevieData","([B)V");
        SocketDataDealThread::env->DeleteLocalRef(cls);
        if(mid==NULL){
            LOGI("%s:find method1 error",TAG);
            pthread_exit(NULL);
        }
        jbyteArray jarray = SocketDataDealThread::env->NewByteArray(len);
        SocketDataDealThread::env->SetByteArrayRegion(jarray,0,len,(jbyte*)getBuffer);
        SocketDataDealThread::env->CallVoidMethod(obj,mid,jarray);
        SocketDataDealThread::env->DeleteLocalRef(jarray);

        }
    }
    LOGI("%s:SocketDataDealThread exit",TAG);
    return NULL;
}

SocketDataDealThread::SocketDataDealThread(int fd,jobject obj1):
        threadId(0), sendLength(0){
    if(pthread_create(&threadId,NULL,SocketDataDealThread::clientThread,NULL) != 0 ){
        LOGI("%s:pthread_create error",TAG);
    }
    LOGI("%s:mSTh->getSocketThreadId():%lu",TAG,(long)threadId);
    SocketDataDealThread::obj = obj1;
    getBuffer = new char[100];
    sendBuffer = new char[100];
    socketFd=fd;
}
SocketDataDealThread::~SocketDataDealThread() {
    delete getBuffer;
    delete sendBuffer;
}
void SocketDataDealThread::sendData(char *buff, int length) {
    LOGI("%s:send data %s,len = %d",TAG,buff,length);
    int len = send(socketFd,buff, length,0);
    if(len<0){
        LOGI("%s:send data error,len = %d",TAG,len);
    }
    wakeUpThread();
}
pthread_t SocketDataDealThread::getSocketThreadId() {
    return threadId;
}
void SocketDataDealThread::wakeUpThread(){
    pthread_mutex_lock(&SocketDataDealThread::mutex);
    // 設置條件爲真
    pthread_cond_signal(&SocketDataDealThread::cond);
    pthread_mutex_unlock(&SocketDataDealThread::mutex);
}

SocketDataDealThread .h

//
// Created by Administrator on 2017/3/3 0003.
//

#ifndef ANDROIDJNITSOCKET_SOCKETDATADEALTHREAD_H
#define ANDROIDJNITSOCKET_SOCKETDATADEALTHREAD_H


#include <pthread.h>

class SocketDataDealThread {
public:
    static void * clientThread(void *args);
    static pthread_cond_t cond;
    static JavaVM*  javavm;
    static pthread_mutex_t mutex;
    static bool isShoudExit;
    static JNIEnv* env;
    static jobject obj;

    SocketDataDealThread(int fd,jobject obj);
    ~SocketDataDealThread();
    void sendData(char buff[],int length);
    void setIsShoudExit(bool isShoud);
    pthread_t getSocketThreadId();
    void wakeUpThread();

private:
    pthread_t threadId;
    static int socketFd;
    static char * getBuffer;
    char * sendBuffer;
    int sendLength=0;
};


#endif //ANDROIDJNITSOCKET_SOCKETDATADEALTHREAD_H

StringTools.cpp–工具類,提供字符串的轉換

//
// Created by Administrator on 2017/3/3 0003.
//

#include <cwchar>
#include <string.h>
#include "StringTools.h"

char* StringTools::jstringTostring(JNIEnv* env, jstring jstr){
    char* rtn = NULL;
    jclass clsstring = env->FindClass("java/lang/String");
    jstring strencode = env->NewStringUTF("utf-8");
    jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");
    jbyteArray barr= (jbyteArray)env->CallObjectMethod(jstr, mid, strencode);
    jsize alen = env->GetArrayLength(barr);
    jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE);
    if (alen > 0)
    {
        rtn = (char*)malloc(alen + 1);

        memcpy(rtn, ba, alen);
        rtn[alen] = 0;
    }
    env->ReleaseByteArrayElements(barr, ba, 0);
    return rtn;
}

StringTools .h

//
// Created by Administrator on 2017/3/3 0003.
//

#ifndef ANDROIDJNITSOCKET_STRINGTOOS_H
#define ANDROIDJNITSOCKET_STRINGTOOS_H


#include <jni.h>

class StringTools {
public:
    static char* jstringTostring(JNIEnv* env, jstring jstr);
};


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