Android NDK Socket(POSIX Socket Api)编程

socket简介

154553_SGq0_1378445.jpg

Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。

tcpsocket和udpsocket的具体实现

讲了这么久,终于要开始讲socket的具体实现了,iOS提供了Socket网络编程的接口CFSocket,不过这里使用BSD Socket。

tcp和udp的socket是有区别的,这里给出这两种的设计框架


基本TCP客户—服务器程序设计基本框架

154850_fOKf_1378445.jpg

基本UDP客户—服务器程序设计基本框架流程图

154905_Kv9g_1378445.jpg


这里我们利用Linux C POSIX Socket API进行NDK Socket编程

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");  
    }  
  
}

客户端 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.");  
        }  
  
    }  
  
}

服务端SocketServer

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.");  
        }  
    }  
}

清单文件

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


实现NDK编程

native接口文件

/* 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

实现native Socket

SocketTool.cpp

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

实现Native 接口

#include <jni.h>  
  
#include "com_apress_echo_EchoServerActivity.h"  
#include "com_apress_echo_EchoClientActivity.h"  
  
#include "SocketTool.cpp"  
  
//服务端:启动监听  
//流程: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) {  
  
}


转载于:https://my.oschina.net/ososchina/blog/656305

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值