使用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) {
}
//
//
//
//
//
//
//`
分别编译客户端和服务端,安装到两台不同的手机上。
运行结果:
代码下载
http://download.csdn.net/detail/hai836045106/8062933