讲比较重要的代码放到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