NDK Socket编程:面向连接的通信(tcp)

使用posix socket api,java层调用c层。

AndroidManifest.xml:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.apress.echo"
    android:versionCode="1"
    android:versionName="1.0" >
 
    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="19" />
 
    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        
        <!-- 服务端app -->
<!--         <activity -->
<!--             android:name=".EchoServerActivity" -->
<!--             android:label="@string/title_activity_echo_server" -->
<!--             android:launchMode="singleTop" > -->
<!--             <intent-filter> -->
<!--                 <action android:name="android.intent.action.MAIN" /> -->
 
<!--                 <category android:name="android.intent.category.LAUNCHER" /> -->
<!--             </intent-filter> -->
<!--         </activity> -->
 
        <!-- 客户端app -->
        <activity
        android:name=".EchoClientActivity"
        android:label="@string/title_activity_echo_client"
        android:launchMode="singleTop" >
        <intent-filter>
        <action android:name="android.intent.action.MAIN" />
 
 
        <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
        </activity>
    </application>
 
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />
 
</manifest>

AbstractEchoActivity:`package com.apress.echo;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ScrollView;
import android.widget.TextView;

/**

  • 客户端和服务端的抽象父类 共同有一个启动按钮,显示日志的TextView,端口设置EditText

*/
public abstract class AbstractEchoActivity extends Activity implements
OnClickListener {

protected static final int TCP = 1;
protected static final int UDP = 2;

protected EditText editPort;// Port number
protected Button btnStart;// server button
protected ScrollView scrollLog;//
protected TextView tvLog;// log view

private final int layoutID;

public AbstractEchoActivity(int layoutID) {
	this.layoutID = layoutID;
}

@Override
protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(layoutID);

	editPort = (EditText) findViewById(R.id.port_edit);
	btnStart = (Button) findViewById(R.id.start_button);
	scrollLog = (ScrollView) findViewById(R.id.scroll_view);
	tvLog = (TextView) findViewById(R.id.log_view);

	btnStart.setOnClickListener(this);
}

@Override
public void onClick(View v) {

	if (v == btnStart) {
		onStartButtonClicked();
	} else {
		Log.v("onClick", "onClick no done.");
	}
}

/**
 * 获取端口
 * 
 * @return
 */
protected Integer getPort() {

	Integer port;

	try {
		port = Integer.valueOf(editPort.getText().toString());

	} catch (Exception e) {
		e.printStackTrace();
		port = null;
	}

	return port;
}

protected void logMessage(final String message) {

	runOnUiThread(new Runnable() {

		@Override
		public void run() {
			logMessageDirect(message);

		}
	});
}

protected void logMessageDirect(final String message) {
	tvLog.append(message);
	tvLog.append("\n");
	scrollLog.fullScroll(View.FOCUS_DOWN);
}

protected abstract void onStartButtonClicked();

/**
 * 这个thread抽象出onBackground()方法作为线程的执行方法,在启动前先设置控件状态为不可用,同时清空日志。执行完毕后设置控件可用。
 * 
 */
protected abstract class AbstractEchoTask extends Thread {
	private final Handler handler;

	public AbstractEchoTask() {
		handler = new Handler();
	}

	protected void onPreExecute() {
		btnStart.setEnabled(false);
		// 清空日志
		tvLog.setText("");
	}

	/* 
	 * 
	 */
	@Override
	public synchronized void start() {
		// 这里start是由主线程来调用的。调用之前先设置控件状态。
		onPreExecute();
		super.start();
	}

	@Override
	public void run() {
		// run是在新线程中运行的
		onBackground();

		// 用handler来修改控件
		handler.post(new Runnable() {

			@Override
			public void run() {
				onPostExecute();

			}
		});
	}

	/**
	 * 线程的执行体
	 */
	protected abstract void onBackground();

	/**
	 * 
	 */
	protected void onPostExecute() {
		btnStart.setEnabled(true);
	}
}

static {
	System.loadLibrary("Echo");
}

}客户端app EchoClientActivity:package com.apress.echo;

import android.os.Bundle;
import android.widget.EditText;

public class EchoClientActivity extends AbstractEchoActivity {

private EditText editIp;
private EditText editMessage;

@Override
protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);

	editIp = (EditText) findViewById(R.id.ip_edit);
	editMessage = (EditText) findViewById(R.id.message_edit);

}

public EchoClientActivity() {
	super(R.layout.activity_echo_client);

}

@Override
protected void onStartButtonClicked() {
	String ip = editIp.getText().toString();

	Integer port = getPort();
	String message = editMessage.getText().toString();

	if (0 != ip.length() && port != null && (0 != message.length())) {
		new ClientTask(ip, port, message).start();
	}
}

private native void nativeStartTcpClient(String ip, int port, String message)
		throws Exception;

private class ClientTask extends AbstractEchoTask {

	private final String ip;
	private final int port;
	private final String message;

	public ClientTask(String ip, int port, String message) {
		this.ip = ip;
		this.port = port;
		this.message = message;
	}

	@Override
	protected void onBackground() {
		logMessage("Starting client");

		try {
			nativeStartTcpClient(ip, port, message);
		} catch (Exception e) {
			logMessage(e.getMessage());
		}
		logMessage("Client terminated.");
	}

}

}`
服务端app

EchoServerActivity:package com.apress.echo;

public class EchoServerActivity extends AbstractEchoActivity {

public EchoServerActivity() {
	super(R.layout.activity_echo_server);

}

@Override
protected void onStartButtonClicked() {
	Integer port = getPort();
	if (port != null) {

		new ServerTask(port, TCP).start();
	} else {
		logMessage("port error");

	}

}

/**
 * 启动tcp服务
 * 
 * @param port
 * @throws Exception
 */
private native void nativeStartTcpServer(int port) throws Exception;

/**
 * 启动udp服务
 * 
 * @param port
 * @throws Exception
 */
private native void nativeStartUdpServer(int port) throws Exception;

private class ServerTask extends AbstractEchoTask {
	private final int port;
	private final int protocol;

	/**
	 * @param port端口
	 * @param protocol
	 *            使用的协议
	 */
	public ServerTask(int port, int protocol) {
		this.port = port;
		this.protocol = protocol;
	}

	@Override
	protected void onBackground() {
		logMessage("Starting server.");
		logMessage("server ip:" + Commons.getIpAddress());
		try {
			if (protocol == TCP) {
				nativeStartTcpServer(port);
			} else if (protocol == UDP) {
				nativeStartUdpServer(port);
			} else {
				logMessage("protocol error.");
			}

		} catch (Exception e) {
			logMessage(e.getMessage());
		}

		logMessage("Server terminated.");
	}
}

}
ndk代码

com_apress_echo_EchoClientActivity.h:`/* DO NOT EDIT THIS FILE - it is machine generated /
#include <jni.h>
/
Header for class com_apress_echo_EchoClientActivity */

#ifndef _Included_com_apress_echo_EchoClientActivity
#define _Included_com_apress_echo_EchoClientActivity
#ifdef __cplusplus
extern “C” {
#endif
/*

  • Class: com_apress_echo_EchoClientActivity
  • Method: nativeStartTcpClient
  • Signature: (Ljava/lang/String;ILjava/lang/String;)V
    */
    JNIEXPORT void JNICALL Java_com_apress_echo_EchoClientActivity_nativeStartTcpClient
    (JNIEnv *, jobject, jstring, jint, jstring);

#ifdef __cplusplus
}
#endif
#endifcom_apress_echo_EchoServerActivity.h:/* DO NOT EDIT THIS FILE - it is machine generated /
#include <jni.h>
/
Header for class com_apress_echo_EchoServerActivity */

#ifndef _Included_com_apress_echo_EchoServerActivity
#define _Included_com_apress_echo_EchoServerActivity
#ifdef __cplusplus
extern “C” {
#endif
#undef com_apress_echo_EchoServerActivity_TCP
#define com_apress_echo_EchoServerActivity_TCP 1L
#undef com_apress_echo_EchoServerActivity_UDP
#define com_apress_echo_EchoServerActivity_UDP 2L
/*

  • Class: com_apress_echo_EchoServerActivity
  • Method: nativeStartTcpServer
  • Signature: (I)V
    */
    JNIEXPORT void JNICALL Java_com_apress_echo_EchoServerActivity_nativeStartTcpServer
    (JNIEnv *, jobject, jint);

/*

  • Class: com_apress_echo_EchoServerActivity
  • Method: nativeStartUdpServer
  • Signature: (I)V
    */
    JNIEXPORT void JNICALL Java_com_apress_echo_EchoServerActivity_nativeStartUdpServer
    (JNIEnv *, jobject, jint);

#ifdef __cplusplus
}
#endif
#endif`
实现socket函数的头文件

SocketUtils.h:`#include <stdio.h>
#include <stdarg.h>
//errno
#include <errno.h>
#include <string.h>

#include <sys/types.h>
#include <sys/socket.h>

//sockaddr_un
#include <sys/un.h>

//htons,sockaddr_in
#include <netinet/in.h>
//inet_ntop
#include <arpa/inet.h>
//close,unlink
#include <unistd.h>
//offsetof
#include <stddef.h>

#ifndef SOCKET_UTILS
#define _SOCKET_UTILS

//MAX log message length
#define MAX_LOG_MESSAGE_LENGTH 256
//MAX data buffer size
#define MAX_BUFFER_SIZE 80

//打印日志到java环境中
static void LogMessage(JNIEnv* env, jobject obj, const char* format, …) {

//cache log method ID
static jmethodID methodID = NULL;
if (methodID == NULL) {
	jclass clazz = env->GetObjectClass(obj);
	methodID = env->GetMethodID(clazz, "logMessage",
			"(Ljava/lang/String;)V");

	env->DeleteLocalRef(clazz);
}

if (methodID != NULL) {
	char buffer[MAX_BUFFER_SIZE];

	//将可变参数输出到字符数组中
	va_list ap;
	va_start(ap, format);
	vsnprintf(buffer, MAX_LOG_MESSAGE_LENGTH, format, ap);
	va_end(ap);

	//转换成java字符串
	jstring message = env->NewStringUTF(buffer);
	if (message != NULL) {
		env->CallVoidMethod(obj, methodID, message);
		env->DeleteLocalRef(message);
	}
}

}

//通过异常类和异常信息抛出异常
static void ThrowException(JNIEnv* env, const char* className,
const char* message) {

jclass clazz = env->FindClass(className);
if (clazz != NULL) {
	env->ThrowNew(clazz, message);
	env->DeleteLocalRef(clazz);
}

}

//通过异常类和错误号抛出异常
static void ThrowErrnoException(JNIEnv* env, const char* className,
int errnum) {

char buffer[MAX_LOG_MESSAGE_LENGTH];

//通过错误号获得错误消息
if (-1 == strerror_r(errnum, buffer, MAX_LOG_MESSAGE_LENGTH)) {
	strerror_r(errno, buffer, MAX_LOG_MESSAGE_LENGTH);
}

ThrowException(env, className, buffer);

}

//sock用到的一些公用方法
//创建一个socket:socket()
static int NewTcpSocket(JNIEnv* env, jobject obj) {

LogMessage(env, obj, "Constructing a new TCP socket...");
int tcpSocket = socket(PF_INET, SOCK_STREAM, 0);

if (-1 == tcpSocket) {
	ThrowErrnoException(env, "java/io/IOException", errno);
}

return tcpSocket;

}

//绑定 bind()
static void BindSocketToPort(JNIEnv* env, jobject obj, int sd,
unsigned short port) {
struct sockaddr_in address;
//清空结构体
memset(&address, 0, sizeof(address));

address.sin_family = PF_INET;
//Bind to all address
address.sin_addr.s_addr = htonl(INADDR_ANY);
//Convert port to network byte order
address.sin_port = htons(port);
//Bind socket
LogMessage(env, obj, "Binding to port %hu.", port);
//sockaddr方便函数传递, sockaddr_in方便用户设定, 所以需要的时候在这2者之间进行转换
if (-1 == bind(sd, (struct sockaddr*) &address, sizeof(address))) {
	ThrowErrnoException(env, "java/io/IOException", errno);
}

}
//返回当前socket绑定的端口
static unsigned short GetSocketPort(JNIEnv* env, jobject obj, int sd) {
unsigned short port = 0;
struct sockaddr_in address;
socklen_t addressLength = sizeof(address);
if (-1 == getsockname(sd, (struct sockaddr*) &address, &addressLength)) {
ThrowErrnoException(env, “java/io/IOException”, errno);
} else {
port = ntohs(address.sin_port);
LogMessage(env, obj, “Binding to the random port %hu.”, port);
}
return port;
}

//监听 listen()
static void ListenOnSocket(JNIEnv*env, jobject obj, int sd, int backlog) {
LogMessage(env, obj,
“Listening on socket with a baklog of %d pending connections.”,
backlog);

//listen()用来等待参数s 的socket 连线. 参数backlog 指定同时能处理的最大连接要求,
//如果连接数目达此上限则client 端将收到ECONNREFUSED 的错误.
//Listen()并未开始接收连线, 只是设置socket 为listen 模式, 真正接收client 端连线的是accept().
//通常listen()会在socket(), bind()之后调用, 接着才调用accept().

if (-1 == listen(sd, backlog)) {
	ThrowErrnoException(env, "java/io/IOException", errno);
}

}

//根据地址打印IP和端口
static void LogAddress(JNIEnv* env, jobject obj, const char* message,
const struct sockaddr_in* address) {
char ip[INET_ADDRSTRLEN];

if (NULL == inet_ntop(PF_INET, &(address->sin_addr), ip, INET_ADDRSTRLEN)) {
	ThrowErrnoException(env, "java/io/IOException", errno);
} else {
	unsigned short port = ntohs(address->sin_port);
	LogMessage(env, obj, "%s %s:%hu", message, ip, port);
}

}

//accept()
static int AcceptOnSocket(JNIEnv* env, jobject obj, int sd) {
struct sockaddr_in address;
socklen_t addressLength = sizeof(address);
LogMessage(env, obj, “Waiting for a client connection…”);
int clientSocket = accept(sd, (struct sockaddr*) &address, &addressLength);
if (-1 == clientSocket) {
ThrowErrnoException(env, “java/io/IOException”, errno);
} else {
LogAddress(env, obj, "Client connection from ", &address);
}
return clientSocket;
}

//接收 recv()
static ssize_t ReceiveFromSocket(JNIEnv* env, jobject obj, int sd, char* buffer,
size_t bufferSize) {
LogMessage(env, obj, "Receiving from the socket… ");
ssize_t recvSize = recv(sd, buffer, bufferSize - 1, 0);

if (-1 == recvSize) {
	ThrowErrnoException(env, "java/io/IOException", errno);
} else {
	//字符串截断
	buffer[recvSize] = NULL;

	if (recvSize > 0) {
		//接收成功,打印
		LogMessage(env, obj, "Received %d bytes:%s", bufferSize, buffer);
	} else {
		LogMessage(env, obj, "Client disconnected.");
	}
}

return recvSize;

}

//发送消息:send()
static ssize_t SendToSocket(JNIEnv env, jobject obj, int sd,
const char
buffer, size_t bufferSize) {
LogMessage(env, obj, "Sending to the socket… ");
ssize_t sentSize = send(sd, buffer, bufferSize, 0);

if (-1 == sentSize) {
	ThrowErrnoException(env, "java/io/IOException", errno);
} else {
	if (sentSize > 0) {
		LogMessage(env, obj, "Send %d bytes: %s", sentSize, buffer);
	} else {
		LogMessage(env, obj, "Client disconnected.");
	}
}

return sentSize;

}

//链接到服务器 connect()
static void ConnectToAddress(JNIEnvenv, jobject obj, int sd, const charip,
unsigned short port) {
LogMessage(env, obj, “Connecting to %s:%hu…”, ip, port);

struct sockaddr_in address;

memset(&address, 0, sizeof(address));
address.sin_family = PF_INET;

//转换ip
if (0 == inet_aton(ip, &(address.sin_addr))) {
	ThrowErrnoException(env, "java/io/IOException", errno);
} else {
	address.sin_port = htons(port);
}

if (-1 == connect(sd, (const sockaddr*) &address, sizeof(address))) {
	ThrowErrnoException(env, "java/io/IOException", errno);
} else {
	LogMessage(env, obj, "Connected.");
}

}

//----------------udp

//创建udp socket
static int NewUdpSocket(JNIEnv* env, jobject obj) {

LogMessage(env, obj, "Constructing a new UDP socket...");
int udpSocket = socket(PF_INET, SOCK_DGRAM, 0);

if (-1 == udpSocket) {
	ThrowErrnoException(env, "java/io/IOException", errno);
}

return udpSocket;

}

#endif _SOCKET_UTILS实现代码: Echo.cpp:#include <jni.h>

#include “com_apress_echo_EchoServerActivity.h”
#include “com_apress_echo_EchoClientActivity.h”

#include “SocketUtils.h”

//服务端:启动监听
//流程:socket()->listen()->accept()->recv()->send()_close()
void JNICALL Java_com_apress_echo_EchoServerActivity_nativeStartTcpServer(
JNIEnv *env, jobject obj, jint port) {
int serverSocket = NewTcpSocket(env, obj);

if (NULL == env->ExceptionOccurred()) {
	//绑定
	BindSocketToPort(env, obj, serverSocket, (unsigned short) port);
	if (NULL != env->ExceptionOccurred()) {
		goto exit;
	}

	//如果端口是0,打印出当前随机分配的端口
	if (0 == port) {
		GetSocketPort(env, obj, serverSocket);
		if (NULL != env->ExceptionOccurred()) {
			goto exit;
		}
	}

	//监听 链接4
	ListenOnSocket(env, obj, serverSocket, 4);
	if (NULL != env->ExceptionOccurred()) {
		goto exit;
	}

	//
	int clientSocket = AcceptOnSocket(env, obj, serverSocket);
	if (NULL != env->ExceptionOccurred()) {
		goto exit;
	}

	char buffer[MAX_BUFFER_SIZE];
	ssize_t recvSize;
	ssize_t sentSize;

	while (1) {
		//接收
		recvSize = ReceiveFromSocket(env, obj, clientSocket, buffer,
		MAX_BUFFER_SIZE);

		if ((0 == recvSize) || (NULL != env->ExceptionOccurred())) {
			break;
		}

		//发送
		sentSize = SendToSocket(env, obj, clientSocket, buffer,
				(size_t) recvSize);
		if ((0 == sentSize) || (NULL != env->ExceptionOccurred())) {
			break;
		}
	}

	//close the client socket
	close(clientSocket);

}

exit: if (serverSocket > 0) {
	close(serverSocket);
}

}

//客户端:连接
void JNICALL Java_com_apress_echo_EchoClientActivity_nativeStartTcpClient(
JNIEnv *env, jobject obj, jstring ip, jint port, jstring message) {

int clientSocket = NewTcpSocket(env, obj);
if (NULL == env->ExceptionOccurred()) {
	const char* ipAddress = env->GetStringUTFChars(ip, NULL);

	if (NULL == ipAddress) {
		goto exit;
	}
	ConnectToAddress(env, obj, clientSocket, ipAddress,
			(unsigned short) port);
	//释放ip
	env->ReleaseStringUTFChars(ip, ipAddress);

	//connect exception check
	if (NULL != env->ExceptionOccurred()) {
		goto exit;
	}

	const char* messageText = env->GetStringUTFChars(message, NULL);
	if (NULL == messageText) {
		goto exit;
	}

	//这里的size不用release??
	jsize messageSize = env->GetStringUTFLength(message);
	SendToSocket(env, obj, clientSocket, messageText, messageSize);

	//
	env->ReleaseStringUTFChars(message, messageText);

	if (NULL != env->ExceptionOccurred()) {
		goto exit;
	}

	char buffer[MAX_BUFFER_SIZE];

	ReceiveFromSocket(env, obj, clientSocket, buffer, MAX_BUFFER_SIZE);
}

exit: if (clientSocket > -1) {
	close(clientSocket);
}

}

//启动udp服务端
void JNICALL Java_com_apress_echo_EchoServerActivity_nativeStartUdpServer(
JNIEnv *, jobject, jint) {

}

//
//
//
//
//
//
//`
分别编译客户端和服务端,安装到两台不同的手机上。
运行结果:NDK Socket编程:面向连接的通信(tcp)
NDK Socket编程:面向连接的通信(tcp)
代码下载

http://download.csdn.net/detail/hai836045106/8062933

上一篇:NDK 交叉编译


下一篇:Blender岩浆熔化特效教学