socket简介
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。
tcpsocket和udpsocket的具体实现
讲了这么久,终于要开始讲socket的具体实现了,iOS提供了Socket网络编程的接口CFSocket,不过这里使用BSD Socket。
tcp和udp的socket是有区别的,这里给出这两种的设计框架
基本TCP客户—服务器程序设计基本框架
基本UDP客户—服务器程序设计基本框架流程图
这里我们利用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) { }