首页 > 代码库 > NDK Socket编程:面向连接的通信(tcp)

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
#endif

com_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(JNIEnv*env, jobject obj, int sd, const char*ip,
		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) {

}

//
//
//
//
//
//
//
分别编译客户端和服务端,安装到两台不同的手机上。

运行结果:



代码下载

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

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