NDK Socket编程:面向连接的通信(tcp)
2021/12/6 14:51:51
本文主要是介绍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) {
}
//
//
//
//
//
//
//`
分别编译客户端和服务端,安装到两台不同的手机上。
运行结果:
代码下载
http://download.csdn.net/detail/hai836045106/8062933
这篇关于NDK Socket编程:面向连接的通信(tcp)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-23增量更新怎么做?-icode9专业技术文章分享
- 2024-11-23压缩包加密方案有哪些?-icode9专业技术文章分享
- 2024-11-23用shell怎么写一个开机时自动同步远程仓库的代码?-icode9专业技术文章分享
- 2024-11-23webman可以同步自己的仓库吗?-icode9专业技术文章分享
- 2024-11-23在 Webman 中怎么判断是否有某命令进程正在运行?-icode9专业技术文章分享
- 2024-11-23如何重置new Swiper?-icode9专业技术文章分享
- 2024-11-23oss直传有什么好处?-icode9专业技术文章分享
- 2024-11-23如何将oss直传封装成一个组件在其他页面调用时都可以使用?-icode9专业技术文章分享
- 2024-11-23怎么使用laravel 11在代码里获取路由列表?-icode9专业技术文章分享
- 2024-11-22怎么实现ansible playbook 备份代码中命名包含时间戳功能?-icode9专业技术文章分享