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