博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android NDK Socket(POSIX Socket Api)编程
阅读量:5897 次
发布时间:2019-06-19

本文共 18605 字,大约阅读时间需要 62 分钟。

hot3.png

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

清单文件

        
        
                    
  
  
  
  
  
  
    
  
  
            
          
          
          
              
          
                        
      
    

实现NDK编程

native接口文件

/* DO NOT EDIT THIS FILE - it is machine generated */  #include 
  /* 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 
  #include 
  //errno  #include 
  #include 
    #include 
  #include 
    //sockaddr_un  #include 
    //htons,sockaddr_in  #include 
  //inet_ntop  #include 
  //close,unlink  #include 
  //offsetof  #include 
    #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 
    #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

你可能感兴趣的文章
数据批量导入Oracle数据库
查看>>
调用lumisoft组件发邮件 不需要身份验证 不需要密码
查看>>
DW 正则
查看>>
抓屏原理
查看>>
ASP.NET Web API自身对CORS的支持: EnableCorsAttribute特性背后的故事
查看>>
UNIX网络编程读书笔记:TCP输出、UDP输出和SCTP输出
查看>>
扩展 DbUtility (1)
查看>>
iOS开发UI篇—使用picker View控件完成一个简单的选餐应用
查看>>
Apple Developer Registration and DUNS Number Not Accepted
查看>>
Hadoop学习笔记系列文章导航
查看>>
Win7 64位 php-5.5.13+Apache 2.4.9+mysql-5.6.19 配置
查看>>
不同页面之间实现参数传递的几种方式讨论
查看>>
程序员进阶之路—如何独当一面
查看>>
SpringMVC中ModelAndView addObject()设置的值jsp取不到的问题
查看>>
Prometheus : 入门
查看>>
使用 PowerShell 创建和修改 ExpressRoute 线路
查看>>
PHP如何学习?
查看>>
谈教育与成长
查看>>
jni c++
查看>>
在C#中获取如PHP函数time()一样的时间戳
查看>>