POSIX Socket API
Socket 通信分为三种:
1、面向连接的通信TCP;
2、无连接的通信UDP;
3、本地通信Local


将 Android Emulator 上的 <port number> 与主机上的 <port number> 映射。任何输入到主机上端口号所指定端口的连接都会通过 adb 转发到 Android Emulator 的指定端口上。
adb -s emulator-5556 forward tcp:57102 tcp:57102
本地 socket 族既支持基于流的 socket 协议,也支持基于数据报的 socket 协议。
AbstractEchoActivity.java
1 package com.apress.echo; 2 3 import android.app.Activity; 4 import android.os.Bundle; 5 import android.os.Handler; 6 import android.view.View; 7 import android.view.View.OnClickListener; 8 import android.widget.Button; 9 import android.widget.EditText; 10 import android.widget.ScrollView; 11 import android.widget.TextView; 12 13 /** 14 * Abstract echo activity object. 15 * 16 * @author Onur Cinar 17 */ 18 public abstract class AbstractEchoActivity extends Activity implements OnClickListener { 19 /** Port number. 端口号 */ 20 protected EditText portEdit; 21 22 /** Server button. 服务按钮 */ 23 protected Button startButton; 24 25 /** Log scroll. 日志滚动 */ 26 protected ScrollView logScroll; 27 28 /** Log view. 日志视图 */ 29 protected TextView logView; 30 31 /** Layout ID. 布局ID */ 32 private final int layoutID; 33 34 /** 35 * Constructor. 构造函数 36 * 37 * @param layoutID 38 * layout ID. 39 */ 40 public AbstractEchoActivity(int layoutID) { 41 // 绑定用户界面组件 42 this.layoutID = layoutID; 43 } 44 45 public void onCreate(Bundle savedInstanceState) { 46 super.onCreate(savedInstanceState); 47 setContentView(layoutID); 48 49 portEdit = (EditText) findViewById(R.id.port_edit); 50 startButton = (Button) findViewById(R.id.start_button); 51 logScroll = (ScrollView) findViewById(R.id.log_scroll); 52 logView = (TextView) findViewById(R.id.log_view); 53 54 startButton.setOnClickListener(this); 55 } 56 57 public void onClick(View view) { 58 if (view == startButton) { 59 onStartButtonClicked(); 60 } 61 } 62 63 /** 64 * On start button clicked. 65 */ 66 protected abstract void onStartButtonClicked(); 67 68 /** 69 * Gets the port number as an integer.以整型获取端口号 70 * 71 * @return port number or null. 72 */ 73 protected Integer getPort() { 74 Integer port; 75 76 try { 77 port = Integer.valueOf(portEdit.getText().toString()); 78 } catch (NumberFormatException e) { 79 port = null; 80 } 81 82 return port; 83 } 84 85 /** 86 * Logs the given message. 记录给定的消息 87 * 88 * @param message 89 * log message. 日志消息 90 */ 91 protected void logMessage(final String message) { 92 runOnUiThread(new Runnable() { 93 public void run() { 94 logMessageDirect(message); 95 } 96 }); 97 } 98 99 /** 100 * Logs given message directly. 直接记录给定的消息 101 * 102 * @param message 103 * log message. 日志消息 104 */ 105 protected void logMessageDirect(final String message) { 106 logView.append(message); 107 logView.append("\n"); 108 logScroll.fullScroll(View.FOCUS_DOWN); 109 } 110 111 /** 112 * Abstract async echo task. 抽象异步 echo 任务 113 * 应用在一个单独的线程而不是UI线程中执行网络操作 114 */ 115 protected abstract class AbstractEchoTask extends Thread { 116 /** Handler object. 对象 */ 117 private final Handler handler; 118 119 /** 120 * Constructor. 构造函数 121 */ 122 public AbstractEchoTask() { 123 handler = new Handler(); 124 } 125 126 /** 127 * On pre execute callback in calling thread. 128 * 在调用线程中先执行回调 129 */ 130 protected void onPreExecute() { 131 startButton.setEnabled(false); 132 logView.setText(""); 133 } 134 135 public synchronized void start() { 136 onPreExecute(); 137 super.start(); 138 } 139 140 public void run() { 141 onBackground(); 142 143 handler.post(new Runnable() { 144 public void run() { 145 onPostExecute(); 146 } 147 }); 148 } 149 150 /** 151 * On background callback in new thread. 152 * 在新线程中的背景回调 153 */ 154 protected abstract void onBackground(); 155 156 /** 157 * On post execute callback in calling thread. 158 * 在调用线程中后执行回调 159 */ 160 protected void onPostExecute() { 161 startButton.setEnabled(true); 162 } 163 } 164 165 static { 166 System.loadLibrary("Echo"); 167 } 168 }
EchoClientActivity.java
1 package com.apress.echo; 2 3 import android.os.Bundle; 4 import android.widget.EditText; 5 6 /** 7 * Echo client. 8 * 9 * @author Onur Cinar 10 */ 11 public class EchoClientActivity extends AbstractEchoActivity { 12 /** IP address. */ 13 private EditText ipEdit; 14 15 /** Message edit. 消息编辑 */ 16 private EditText messageEdit; 17 18 /** 19 * Constructor. 构造函数 20 */ 21 public EchoClientActivity() { 22 super(R.layout.activity_echo_client); 23 } 24 25 public void onCreate(Bundle savedInstanceState) { 26 super.onCreate(savedInstanceState); 27 28 ipEdit = (EditText) findViewById(R.id.ip_edit); 29 messageEdit = (EditText) findViewById(R.id.message_edit); 30 } 31 32 protected void onStartButtonClicked() { 33 String ip = ipEdit.getText().toString(); 34 Integer port = getPort(); 35 String message = messageEdit.getText().toString(); 36 37 if ((0 != ip.length()) && (port != null) && (0 != message.length())) { 38 ClientTask clientTask = new ClientTask(ip, port, message); 39 clientTask.start(); 40 } 41 } 42 43 /** 44 * Starts the TCP client with the given server IP address and port number, 45 * and sends the given message. 46 * 根据给定服务器IP 地址和端口号启动TCP客户端,并且发送给定消息 47 * 48 * @param ip 49 * IP address. 50 * @param port 51 * port number. 52 * @param message 53 * message text. 54 * @throws Exception 55 */ 56 private native void nativeStartTcpClient(String ip, int port, String message) 57 throws Exception; 58 59 /** 60 * Starts the UDP client with the given server IP address and port number. 61 * 62 * @param ip 63 * IP address. 64 * @param port 65 * port number. 66 * @param message 67 * message text. 68 * @throws Exception 69 */ 70 private native void nativeStartUdpClient(String ip, int port, String message) 71 throws Exception; 72 73 /** 74 * Client task. 客户端任务 75 */ 76 private class ClientTask extends AbstractEchoTask { 77 /** IP address to connect. 连接的IP地址 */ 78 private final String ip; 79 80 /** Port number. 端口号 */ 81 private final int port; 82 83 /** Message text to send. 发送的消息文本 */ 84 private final String message; 85 86 /** 87 * Constructor. 构造函数 88 * 89 * @param ip 90 * IP address to connect. 91 * @param port 92 * port number to connect. 93 * @param message 94 * message text to send. 95 */ 96 public ClientTask(String ip, int port, String message) { 97 this.ip = ip; 98 this.port = port; 99 this.message = message; 100 } 101 102 protected void onBackground() { 103 logMessage("Starting client."); 104 105 try { 106 nativeStartTcpClient(ip, port, message); 107 //nativeStartUdpClient(ip, port, message); 108 } catch (Throwable e) { 109 logMessage(e.getMessage()); 110 } 111 112 logMessage("Client terminated."); 113 } 114 } 115 }
EchoServerActivity .java
1 package com.apress.echo; 2 3 /** 4 * Echo server. 5 * 6 * @author Onur Cinar 7 */ 8 public class EchoServerActivity extends AbstractEchoActivity { 9 /** 10 * Constructor. 构造函数 11 */ 12 public EchoServerActivity() { 13 super(R.layout.activity_echo_server); 14 } 15 16 protected void onStartButtonClicked() { 17 Integer port = getPort(); 18 if (port != null) { 19 ServerTask serverTask = new ServerTask(port); 20 serverTask.start(); 21 } 22 } 23 24 /** 25 * Starts the TCP server on the given port. 26 * 根据给定端口启动 TCP 服务 27 * 28 * @param port 29 * 端口号 port number. 30 * @throws Exception 31 */ 32 private native void nativeStartTcpServer(int port) throws Exception; 33 34 /** 35 * Starts the UDP server on the given port. 36 * 根据给定端口启动 UDP 服务 37 * 38 * @param port 39 * 端口号 port number. 40 * @throws Exception 41 */ 42 private native void nativeStartUdpServer(int port) throws Exception; 43 44 /** 45 * Server task. 服务端任务 46 */ 47 private class ServerTask extends AbstractEchoTask { 48 /** 端口号 Port number. */ 49 private final int port; 50 51 /** 52 * Constructor.构造函数 53 * 54 * @param port 55 * 端口号 port number. 56 */ 57 public ServerTask(int port) { 58 this.port = port; 59 } 60 61 protected void onBackground() { 62 logMessage("Starting server."); 63 64 try { 65 nativeStartTcpServer(port); 66 //nativeStartUdpServer(port); 67 } catch (Exception e) { 68 logMessage(e.getMessage()); 69 } 70 71 logMessage("Server terminated."); 72 } 73 } 74 }
LocalEchoActivity.java
1 package com.apress.echo; 2 3 import java.io.File; 4 import java.io.InputStream; 5 import java.io.OutputStream; 6 7 import android.net.LocalSocket; 8 import android.net.LocalSocketAddress; 9 import android.os.Bundle; 10 import android.widget.EditText; 11 12 /** 13 * Echo local socket server and client. 14 * Echo 本地 socket 服务器和客户端 15 * 16 * @author Onur Cinar 17 */ 18 public class LocalEchoActivity extends AbstractEchoActivity { 19 /** Message edit. 消息编辑 */ 20 private EditText messageEdit; 21 22 /** 23 * Constructor. 构造函数 24 */ 25 public LocalEchoActivity() { 26 super(R.layout.activity_local_echo); 27 } 28 29 public void onCreate(Bundle savedInstanceState) { 30 super.onCreate(savedInstanceState); 31 32 messageEdit = (EditText) findViewById(R.id.message_edit); 33 } 34 35 protected void onStartButtonClicked() { 36 String name = portEdit.getText().toString(); 37 String message = messageEdit.getText().toString(); 38 39 if ((name.length() > 0) && (message.length() > 0)) { 40 String socketName; 41 42 // If it is a filesystem socket, prepend the application files directory 43 // 如果是 filesystem socket,预先准备应用程序的文件目录 44 if (isFilesystemSocket(name)) { 45 File file = new File(getFilesDir(), name); 46 socketName = file.getAbsolutePath(); 47 } else { 48 socketName = name; 49 } 50 51 ServerTask serverTask = new ServerTask(socketName); 52 serverTask.start(); 53 54 ClientTask clientTask = new ClientTask(socketName, message); 55 clientTask.start(); 56 } 57 } 58 59 /** 60 * Check if name is a filesystem socket. 61 * 检查名称是否是 filesystem socket 62 * 63 * @param name 64 * socket name. 65 * @return filesystem socket. 66 */ 67 private boolean isFilesystemSocket(String name) { 68 return name.startsWith("/"); 69 } 70 71 /** 72 * Starts the Local UNIX socket server binded to given name. 73 * 启动绑定到给定名称的本地 UNIX socket 服务器 74 * 75 * @param name 76 * socket name. 77 * @throws Exception 78 */ 79 private native void nativeStartLocalServer(String name) throws Exception; 80 81 /** 82 * Starts the local UNIX socket client. 83 * 启动本地 UNXI socket 客户端 84 * 85 * @param port 86 * port number. 87 * @param message 88 * message text. 89 * @throws Exception 90 */ 91 private void startLocalClient(String name, String message) throws Exception { 92 // Construct a local socket 构造一个本地 socket 93 LocalSocket clientSocket = new LocalSocket(); 94 try { 95 // Set the socket namespace 设置 socket 名称空间 96 LocalSocketAddress.Namespace namespace; 97 if (isFilesystemSocket(name)) { 98 namespace = LocalSocketAddress.Namespace.FILESYSTEM; 99 } else { 100 namespace = LocalSocketAddress.Namespace.ABSTRACT; 101 } 102 103 // Construct local socket address 构造本地 socket 地址 104 LocalSocketAddress address = new LocalSocketAddress(name, namespace); 105 106 // Connect to local socket 连接到本地 socket 107 logMessage("Connecting to " + name); 108 clientSocket.connect(address); 109 logMessage("Connected."); 110 111 // Get message as bytes 以字节形式获取消息 112 byte[] messageBytes = message.getBytes(); 113 114 // Send message bytes to the socket 发送消息字节到 socket 115 logMessage("Sending to the socket..."); 116 OutputStream outputStream = clientSocket.getOutputStream(); 117 outputStream.write(messageBytes); 118 logMessage(String.format("Sent %d bytes: %s", messageBytes.length, message)); 119 120 // Receive the message back from the socket 从 socket 中接收消息返回 121 logMessage("Receiving from the socket..."); 122 InputStream inputStream = clientSocket.getInputStream(); 123 int readSize = inputStream.read(messageBytes); 124 125 String receivedMessage = new String(messageBytes, 0, readSize); 126 logMessage(String.format("Received %d bytes: %s", readSize, receivedMessage)); 127 128 // Close streams 关闭流 129 outputStream.close(); 130 inputStream.close(); 131 132 } finally { 133 // Close the local socket 关闭本地 socket 134 clientSocket.close(); 135 } 136 } 137 138 /** 139 * Server task. 服务器任务 140 */ 141 private class ServerTask extends AbstractEchoTask { 142 /** Socket name. */ 143 private final String name; 144 145 /** 146 * Constructor. 构造函数 147 * 148 * @param name 149 * socket name. 150 */ 151 public ServerTask(String name) { 152 this.name = name; 153 } 154 155 protected void onBackground() { 156 logMessage("Starting server."); 157 158 try { 159 nativeStartLocalServer(name); 160 } catch (Exception e) { 161 logMessage(e.getMessage()); 162 } 163 164 logMessage("Server terminated."); 165 } 166 } 167 168 /** 169 * Client task. 客户端任务 170 */ 171 private class ClientTask extends Thread { 172 /** Socket name. */ 173 private final String name; 174 175 /** Message text to send. 发送的消息文本 */ 176 private final String message; 177 178 /** 179 * Constructor. 构造函数 180 * 181 * @parma name socket name. 182 * @param message 183 * message text to send. 184 */ 185 public ClientTask(String name, String message) { 186 this.name = name; 187 this.message = message; 188 } 189 190 public void run() { 191 logMessage("Starting client."); 192 193 try { 194 startLocalClient(name, message); 195 } catch (Exception e) { 196 logMessage(e.getMessage()); 197 } 198 199 logMessage("Client terminated."); 200 } 201 } 202 }
原生代码 Echo.cpp
1 #include "com_apress_echo_EchoClientActivity.h" 2 #include "com_apress_echo_EchoServerActivity.h" 3 #include "com_apress_echo_LocalEchoActivity.h" 4 5 // JNI 6 #include <jni.h> 7 8 // NULL 9 #include <stdio.h> 10 11 // va_list, vsnprintf 12 #include <stdarg.h> 13 14 // errno 15 #include <errno.h> 16 17 // strerror_r, memset 18 #include <string.h> 19 20 // socket, bind, getsockname, listen, accept, recv, send, connect 21 #include <sys/types.h> 22 #include <sys/socket.h> 23 24 // sockaddr_un 25 #include <sys/un.h> 26 27 // htons, sockaddr_in 28 #include <netinet/in.h> 29 30 // inet_ntop 31 #include <arpa/inet.h> 32 33 // close, unlink 34 #include <unistd.h> 35 36 // offsetof 37 #include <stddef.h> 38 39 // Max log message length 最大日志消息长度 40 #define MAX_LOG_MESSAGE_LENGTH 256 41 42 // Max data buffer size 最大数据缓冲区大小 43 #define MAX_BUFFER_SIZE 80 44 45 /** 46 * Logs the given message to the application. 47 * 将给定的消息记录到应用程序 48 * 49 * @param env JNIEnv interface. 50 * @param obj object instance. 51 * @param format message format and arguments. 52 */ 53 static void LogMessage( 54 JNIEnv* env, 55 jobject obj, 56 const char* format, 57 ...) 58 { 59 // Cached log method ID 缓存日志方法 ID 60 static jmethodID methodID = NULL; 61 62 // If method ID is not cached 如果方法ID未缓存 63 if (NULL == methodID) 64 { 65 // Get class from object 从对象获取类 66 jclass clazz = env->GetObjectClass(obj); 67 68 // Get the method ID for the given method 从给定的方法中获取方法ID 69 methodID = env->GetMethodID(clazz, "logMessage", "(Ljava/lang/String;)V"); 70 71 // Release the class reference 释放类引用 72 env->DeleteLocalRef(clazz); 73 } 74 75 // If method is found 如果找到方法 76 if (NULL != methodID) 77 { 78 // Format the log message 格式化日志消息 79 char buffer[MAX_LOG_MESSAGE_LENGTH]; 80 81 va_list ap; 82 va_start(ap, format); 83 vsnprintf(buffer, MAX_LOG_MESSAGE_LENGTH, format, ap); 84 va_end(ap); 85 86 // Convert the buffer to a Java string 将缓冲区转换为 Java 字符串 87 jstring message = env->NewStringUTF(buffer); 88 89 // If string is properly constructed 如果字符串构造正确 90 if (NULL != message) 91 { 92 // Log message 记录消息 93 env->CallVoidMethod(obj, methodID, message); 94 95 // Release the message reference 释放消息引用 96 env->DeleteLocalRef(message); 97 } 98 } 99 } 100 101 /** 102 * Throws a new exception using the given exception class 103 * and exception message. 104 * 用给定的异常类和异常消息抛出新的异常 105 * 106 * @param env JNIEnv interface. 107 * @param className class name. 108 * @param message exception message. 109 */ 110 static void ThrowException( 111 JNIEnv* env, 112 const char* className, 113 const char* message) 114 { 115 // Get the exception class 获取异常类 116 jclass clazz = env->FindClass(className); 117 118 // If exception class is found 如果异常类未找到 119 if (NULL != clazz) 120 { 121 // Throw exception 抛出异常 122 env->ThrowNew(clazz, message); 123 124 // Release local class reference 释放原生类引用 125 env->DeleteLocalRef(clazz); 126 } 127 } 128 129 /** 130 * Throws a new exception using the given exception class 131 * and error message based on the error number. 132 * 用给定异常类和基于错误号的错误消息抛出新异常 133 * 134 * @param env JNIEnv interface. 135 * @param className class name. 136 * @param errnum error number. 137 */ 138 static void ThrowErrnoException( 139 JNIEnv* env, 140 const char* className, 141 int errnum) 142 { 143 char buffer[MAX_LOG_MESSAGE_LENGTH]; 144 145 // Get message for the error number 获取错误号消息 146 if (-1 == strerror_r(errnum, buffer, MAX_LOG_MESSAGE_LENGTH)) 147 { 148 strerror_r(errno, buffer, MAX_LOG_MESSAGE_LENGTH); 149 } 150 151 // Throw exception 抛出异常 152 ThrowException(env, className, buffer); 153 } 154 155 /** 156 * Constructs a new TCP socket. 157 * 构造新的 TCP socket 158 * 159 * @param env JNIEnv interface. 160 * @param obj object instance. 161 * @return socket descriptor. 162 * @throws IOException 163 */ 164 static int NewTcpSocket(JNIEnv* env, jobject obj) 165 { 166 // Construct 构造 socket 167 LogMessage(env, obj, "Constructing a new TCP socket..."); 168 int tcpSocket = socket(PF_INET, SOCK_STREAM, 0); 169 170 // Check if socket is properly constructed 检查 socket 构造是否正确 171 if (-1 == tcpSocket) 172 { 173 // Throw an exception with error number 抛出带错误符号的异常 174 ThrowErrnoException(env, "java/io/IOException", errno); 175 } 176 177 return tcpSocket; 178 } 179 180 /** 181 * Binds socket to a port number. 182 * 将 socket 绑定到某一端口号 183 * 184 * @param env JNIEnv interface. 185 * @param obj object instance. 186 * @param sd socket descriptor. 187 * @param port port number or zero for random port. 188 * @throws IOException 189 */ 190 static void BindSocketToPort( 191 JNIEnv* env, 192 jobject obj, 193 int sd, 194 unsigned short port) 195 { 196 struct sockaddr_in address; 197 198 // Address to bind socket 绑定 socket 地址 199 memset(&address, 0, sizeof(address)); 200 address.sin_family = PF_INET; 201 202 // Bind to all addresses 绑定到所有的 IP 地址 203 address.sin_addr.s_addr = htonl(INADDR_ANY); 204 205 // Convert port to network byte order 将端口转换为网络字节顺序 206 address.sin_port = htons(port); 207 208 // Bind 绑定 socket 209 LogMessage(env, obj, "Binding to port %hu.", port); 210 if (-1 == bind(sd, (struct sockaddr*) &address, sizeof(address))) 211 { 212 // Throw an exception with error number 抛出带错误号的异常 213 ThrowErrnoException(env, "java/io/IOException", errno); 214 } 215 } 216 217 /** 218 * Gets the port number socket is currently binded. 219 * 获取当前绑定的端口号 socket 220 * 221 * @param env JNIEnv interface. 222 * @param obj object instance. 223 * @param sd socket descriptor. 224 * @return port number. 225 * @throws IOException 226 */ 227 static unsigned short GetSocketPort( 228 JNIEnv* env, 229 jobject obj, 230 int sd) 231 { 232 unsigned short port = 0; 233 234 struct sockaddr_in address; 235 socklen_t addressLength = sizeof(address); 236 237 // Get the socket address 获取 socket 地址 238 if (-1 == getsockname(sd, (struct sockaddr*) &address, &addressLength)) 239 { 240 // Throw an exception with error number 抛出带错误号的异常 241 ThrowErrnoException(env, "java/io/IOException", errno); 242 } 243 else 244 { 245 // Convert port to host byte order 将端口转换为主机字节顺序 246 port = ntohs(address.sin_port); 247 248 LogMessage(env, obj, "Binded to random port %hu.", port); 249 } 250 251 return port; 252 } 253 254 /** 255 * Listens on given socket with the given backlog for 256 * pending connections. When the backlog is full, the 257 * new connections will be rejected. 258 * 监听指定的待处理连接的 backlog 的 socket,当 backlog 已满时拒绝新的连接 259 * 260 * @param env JNIEnv interface. 261 * @param obj object instance. 262 * @param sd socket descriptor. 263 * @param backlog backlog size. 264 * @throws IOException 265 */ 266 static void ListenOnSocket( 267 JNIEnv* env, 268 jobject obj, 269 int sd, 270 int backlog) 271 { 272 // Listen on socket with the given backlog 监听给定 backlog 的 socket 273 LogMessage(env, obj, 274 "Listening on socket with a backlog of %d pending connections.", 275 backlog); 276 277 if (-1 == listen(sd, backlog)) 278 { 279 // Throw an exception with error number 抛出带错误符号的异常 280 ThrowErrnoException(env, "java/io/IOException", errno); 281 } 282 } 283 284 /** 285 * Logs the IP address and the port number from the given address. 286 * 记录给定地址的 IP 地址和端口号 287 * 288 * @param env JNIEnv interface. 289 * @param obj object instance. 290 * @param message message text. 291 * @param address adress instance. 292 * @throws IOException 293 */ 294 static void LogAddress( 295 JNIEnv* env, 296 jobject obj, 297 const char* message, 298 const struct sockaddr_in* address) 299 { 300 char ip[INET_ADDRSTRLEN]; 301 302 // Convert the IP address to string 将 IP 地址转换为字符串 303 if (NULL == inet_ntop(PF_INET, 304 &(address->sin_addr), 305 ip, 306 INET_ADDRSTRLEN)) 307 { 308 // Throw an exception with error number 抛出带错误符号的异常 309 ThrowErrnoException(env, "java/io/IOException", errno); 310 } 311 else 312 { 313 // Convert port to host byte order 将端口转换为主机字节顺序 314 unsigned short port = ntohs(address->sin_port); 315 316 // Log address 记录地址 317 LogMessage(env, obj, "%s %s:%hu.", message, ip, port); 318 } 319 } 320 321 /** 322 * Blocks and waits for incoming client connections on the given socket. 323 * 在给定的 socket 上阻塞和等待进来的客户连接 324 * 325 * @param env JNIEnv interface. 326 * @param obj object instance. 327 * @param sd socket descriptor. 328 * @return client socket. 329 * @throws IOException 330 */ 331 static int AcceptOnSocket( 332 JNIEnv* env, 333 jobject obj, 334 int sd) 335 { 336 struct sockaddr_in address; 337 socklen_t addressLength = sizeof(address); 338 339 // Blocks and waits for an incoming client connection and accepts it 340 // 阻塞和等待进来的客户连接并且接受它 341 LogMessage(env, obj, "Waiting for a client connection..."); 342 343 int clientSocket = accept(sd, 344 (struct sockaddr*) &address, 345 &addressLength); 346 347 // If client socket is not valid 如果客户 socket 无效 348 if (-1 == clientSocket) 349 { 350 // Throw an exception with error number 抛出带错误符号的异常 351 ThrowErrnoException(env, "java/io/IOException", errno); 352 } 353 else 354 { 355 // Log address 记录地址 356 LogAddress(env, obj, "Client connection from ", &address); 357 } 358 359 return clientSocket; 360 } 361 362 /** 363 * Block and receive data from the socket into the buffer. 364 * 阻塞并接收来自 socket 的数据放到缓冲区 365 * 366 * @param env JNIEnv interface. 367 * @param obj object instance. 368 * @param sd socket descriptor. 369 * @param buffer data buffer. 370 * @param bufferSize buffer size. 371 * @return receive size. 372 * @throws IOException 373 */ 374 static ssize_t ReceiveFromSocket( 375 JNIEnv* env, 376 jobject obj, 377 int sd, 378 char* buffer, 379 size_t bufferSize) 380 { 381 // Block and receive data from the socket into the buffer 382 // 阻塞并接收来自 socket 的数据放到缓冲区 383 LogMessage(env, obj, "Receiving from the socket..."); 384 ssize_t recvSize = recv(sd, buffer, bufferSize - 1, 0); 385 386 // If receive is failed 如果接收失败 387 if (-1 == recvSize) 388 { 389 // Throw an exception with error number 抛出带错误号的异常 390 ThrowErrnoException(env, "java/io/IOException", errno); 391 } 392 else 393 { 394 // NULL terminate the buffer to make it a string 以空结尾缓冲区形成一个字符串 395 buffer[recvSize] = NULL; 396 397 // If data is received 如果数据接收成功 398 if (recvSize > 0) 399 { 400 LogMessage(env, obj, "Received %d bytes: %s", recvSize, buffer); 401 } 402 else 403 { 404 LogMessage(env, obj, "Client disconnected."); 405 } 406 } 407 408 return recvSize; 409 } 410 411 /** 412 * Send data buffer to the socket. 413 * 将数据缓冲区发送到 socket 414 * 415 * @param env JNIEnv interface. 416 * @param obj object instance. 417 * @param sd socket descriptor. 418 * @param buffer data buffer. 419 * @param bufferSize buffer size. 420 * @return sent size. 421 * @throws IOException 422 */ 423 static ssize_t SendToSocket( 424 JNIEnv* env, 425 jobject obj, 426 int sd, 427 const char* buffer, 428 size_t bufferSize) 429 { 430 // Send data buffer to the socket 将数据缓冲区发送到 socket 431 LogMessage(env, obj, "Sending to the socket..."); 432 ssize_t sentSize = send(sd, buffer, bufferSize, 0); 433 434 // If send is failed 如果发送失败 435 if (-1 == sentSize) 436 { 437 // Throw an exception with error number 抛出带错误符号的异常 438 ThrowErrnoException(env, "java/io/IOException", errno); 439 } 440 else 441 { 442 if (sentSize > 0) 443 { 444 LogMessage(env, obj, "Sent %d bytes: %s", sentSize, buffer); 445 } 446 else 447 { 448 LogMessage(env, obj, "Client disconnected."); 449 } 450 } 451 452 return sentSize; 453 } 454 455 /** 456 * Connects to given IP address and given port. 457 * 连接到给定的 IP 地址和给定的端口号 458 * 459 * @param env JNIEnv interface. 460 * @param obj object instance. 461 * @param sd socket descriptor. 462 * @param ip IP address. 463 * @param port port number. 464 * @throws IOException 465 */ 466 static void ConnectToAddress( 467 JNIEnv* env, 468 jobject obj, 469 int sd, 470 const char* ip, 471 unsigned short port) 472 { 473 // Connecting to given IP address and given port 连接到给定的 IP 地址和给定的端口号 474 LogMessage(env, obj, "Connecting to %s:%uh...", ip, port); 475 476 struct sockaddr_in address; 477 478 memset(&address, 0, sizeof(address)); 479 address.sin_family = PF_INET; 480 481 // Convert IP address string to Internet address 将 IP 地址字符串转换为网络地址 482 if (0 == inet_aton(ip, &(address.sin_addr))) 483 { 484 // Throw an exception with error number 抛出带错误号的异常 485 ThrowErrnoException(env, "java/io/IOException", errno); 486 } 487 else 488 { 489 // Convert port to network byte order 将端口号转换为网络字节顺序 490 address.sin_port = htons(port); 491 492 // Connect to address 转换为地址 493 if (-1 == connect(sd, (const sockaddr*) &address, sizeof(address))) 494 { 495 // Throw an exception with error number 抛出带错误号的异常 496 ThrowErrnoException(env, "java/io/IOException", errno); 497 } 498 else 499 { 500 LogMessage(env, obj, "Connected."); 501 } 502 } 503 } 504 505 // 原生 TCP 客户端 506 void Java_com_apress_echo_EchoClientActivity_nativeStartTcpClient( 507 JNIEnv* env, 508 jobject obj, 509 jstring ip, 510 jint port, 511 jstring message) 512 { 513 // Construct a new TCP socket. 构造新的 TCP socket 514 int clientSocket = NewTcpSocket(env, obj); 515 if (NULL == env->ExceptionOccurred()) 516 { 517 // Get IP address as C string 以 C 字符串形式获取 IP 地址 518 const char* ipAddress = env->GetStringUTFChars(ip, NULL); 519 if (NULL == ipAddress) 520 goto exit; 521 522 // Connect to IP address and port 连接到 IP 地址和端口 523 ConnectToAddress(env, obj, clientSocket, ipAddress, (unsigned short) port); 524 525 // Release the IP address 释放 IP 地址 526 env->ReleaseStringUTFChars(ip, ipAddress); 527 528 // If connection was successful 如果连接成功 529 if (NULL != env->ExceptionOccurred()) 530 goto exit; 531 532 // Get message as C string 以 C 字符串形式获取消息 533 const char* messageText = env->GetStringUTFChars(message, NULL); 534 if (NULL == messageText) 535 goto exit; 536 537 // Get the message size 获取消息大小 538 jsize messageSize = env->GetStringUTFLength(message); 539 540 // Send message to socket 发送消息给 socket 541 SendToSocket(env, obj, clientSocket, messageText, messageSize); 542 543 // Release the message text 释放消息文本 544 env->ReleaseStringUTFChars(message, messageText); 545 546 // If send was not successful 如果发送未成功 547 if (NULL != env->ExceptionOccurred()) 548 goto exit; 549 550 char buffer[MAX_BUFFER_SIZE]; 551 552 // Receive from the socket 从 socket 接收 553 ReceiveFromSocket(env, obj, clientSocket, buffer, MAX_BUFFER_SIZE); 554 } 555 556 exit: 557 if (clientSocket > 0) 558 { 559 close(clientSocket); 560 } 561 } 562 563 // 原生 TCP 服务器方法 564 void Java_com_apress_echo_EchoServerActivity_nativeStartTcpServer( 565 JNIEnv* env, 566 jobject obj, 567 jint port) 568 { 569 // Construct a new TCP socket. 构造新的 TCP socket 570 int serverSocket = NewTcpSocket(env, obj); 571 if (NULL == env->ExceptionOccurred()) 572 { 573 // Bind socket to a port number 将 socket 绑定到某端口号 574 BindSocketToPort(env, obj, serverSocket, (unsigned short) port); 575 if (NULL != env->ExceptionOccurred()) 576 goto exit; 577 578 // If random port number is requested 如果请求了随机端口号 579 if (0 == port) 580 { 581 // Get the port number socket is currently binded 获取当前绑定的端口号 socket 582 GetSocketPort(env, obj, serverSocket); 583 if (NULL != env->ExceptionOccurred()) 584 goto exit; 585 } 586 587 // Listen on socket with a backlog of 4 pending connections 监听有4 个等待连接的 backlog 的 socket 588 ListenOnSocket(env, obj, serverSocket, 4); 589 if (NULL != env->ExceptionOccurred()) 590 goto exit; 591 592 // Accept a client connection on socket 接受 socket 的一个客户连接 593 int clientSocket = AcceptOnSocket(env, obj, serverSocket); 594 if (NULL != env->ExceptionOccurred()) 595 goto exit; 596 597 char buffer[MAX_BUFFER_SIZE]; 598 ssize_t recvSize; 599 ssize_t sentSize; 600 601 // Receive and send back the data 接收并发送回数据 602 while (1) 603 { 604 // Receive from the socket 从 socket 中接收 605 recvSize = ReceiveFromSocket(env, obj, clientSocket, 606 buffer, MAX_BUFFER_SIZE); 607 608 if ((0 == recvSize) || (NULL != env->ExceptionOccurred())) 609 break; 610 611 // Send to the socket 发送给 socket 612 sentSize = SendToSocket(env, obj, clientSocket, 613 buffer, (size_t) recvSize); 614 615 if ((0 == sentSize) || (NULL != env->ExceptionOccurred())) 616 break; 617 } 618 619 // Close the client socket 关闭客户端 socket 620 close(clientSocket); 621 } 622 623 exit: 624 if (serverSocket > 0) 625 { 626 close(serverSocket); 627 } 628 } 629 630 /** 631 * Constructs a new UDP socket. 632 * 构造新的 UDP socket 633 * 634 * @param env JNIEnv interface. 635 * @param obj object instance. 636 * @return socket descriptor. 637 * @throws IOException 638 */ 639 static int NewUdpSocket(JNIEnv* env, jobject obj) 640 { 641 // Construct socket 构造 socket 642 LogMessage(env, obj, "Constructing a new UDP socket..."); 643 int udpSocket = socket(PF_INET, SOCK_DGRAM, 0); 644 645 // Check if socket is properly constructed 646 // 检查 socket 构造是否正确 647 if (-1 == udpSocket) 648 { 649 // Throw an exception with error number 抛出带错误号的异常 650 ThrowErrnoException(env, "java/io/IOException", errno); 651 } 652 653 return udpSocket; 654 } 655 656 /** 657 * Block and receive datagram from the socket into 658 * the buffer, and populate the client address. 659 * 从 socket 中阻塞并接收数据报保存到缓冲区,填充客户端地址 660 * 661 * @param env JNIEnv interface. 662 * @param obj object instance. 663 * @param sd socket descriptor. 664 * @param address client address. 665 * @param buffer data buffer. 666 * @param bufferSize buffer size. 667 * @return receive size. 668 * @throws IOException 669 */ 670 static ssize_t ReceiveDatagramFromSocket( 671 JNIEnv* env, 672 jobject obj, 673 int sd, 674 struct sockaddr_in* address, 675 char* buffer, 676 size_t bufferSize) 677 { 678 socklen_t addressLength = sizeof(struct sockaddr_in); 679 680 // Receive datagram from socket 从 socket 中接收数据报 681 LogMessage(env, obj, "Receiving from the socket..."); 682 ssize_t recvSize = recvfrom(sd, buffer, bufferSize, 0, 683 (struct sockaddr*) address, &addressLength); 684 685 // If receive is failed 如果接收失败 686 if (-1 == recvSize) 687 { 688 // Throw an exception with error number 抛出带错误号的异常 689 ThrowErrnoException(env, "java/io/IOException", errno); 690 } 691 else 692 { 693 // Log address 记录地址 694 LogAddress(env, obj, "Received from", address); 695 696 // NULL terminate the buffer to make it a string 697 // 以空终止缓冲区使其为一个字符串 698 buffer[recvSize] = NULL; 699 700 // If data is received 如果数据已经接收 701 if (recvSize > 0) 702 { 703 LogMessage(env, obj, "Received %d bytes: %s", recvSize, buffer); 704 } 705 } 706 707 return recvSize; 708 } 709 710 /** 711 * Sends datagram to the given address using the given socket. 712 * 用给定的 socket 发送数据到给定的地址 713 * 714 * @param env JNIEnv interface. 715 * @param obj object instance. 716 * @param sd socket descriptor. 717 * @param address remote address. 718 * @param buffer data buffer. 719 * @param bufferSize buffer size. 720 * @return sent size. 721 * @throws IOException 722 */ 723 static ssize_t SendDatagramToSocket( 724 JNIEnv* env, 725 jobject obj, 726 int sd, 727 const struct sockaddr_in* address, 728 const char* buffer, 729 size_t bufferSize) 730 { 731 // Send data buffer to the socket 向 socket 发送数据缓冲区 732 LogAddress(env, obj, "Sending to", address); 733 ssize_t sentSize = sendto(sd, buffer, bufferSize, 0, 734 (const sockaddr*) address, sizeof(struct sockaddr_in)); 735 736 // If send is failed 如果发送失败 737 if (-1 == sentSize) 738 { 739 // Throw an exception with error number 抛出带错误号的异常 740 ThrowErrnoException(env, "java/io/IOException", errno); 741 } 742 else if (sentSize > 0) 743 { 744 LogMessage(env, obj, "Sent %d bytes: %s", sentSize, buffer); 745 } 746 747 return sentSize; 748 } 749 750 void Java_com_apress_echo_EchoClientActivity_nativeStartUdpClient( 751 JNIEnv* env, 752 jobject obj, 753 jstring ip, 754 jint port, 755 jstring message) 756 { 757 // Construct a new UDP socket. 构造一个新的 UDP socket 758 int clientSocket = NewUdpSocket(env, obj); 759 if (NULL == env->ExceptionOccurred()) 760 { 761 struct sockaddr_in address; 762 763 memset(&address, 0, sizeof(address)); 764 address.sin_family = PF_INET; 765 766 // Get IP address as C string 以 C 字符串形式获取 IP 地址 767 const char* ipAddress = env->GetStringUTFChars(ip, NULL); 768 if (NULL == ipAddress) 769 goto exit; 770 771 // Convert IP address string to Internet address 772 // 将 IP 地址字符串转换为网络地址 773 int result = inet_aton(ipAddress, &(address.sin_addr)); 774 775 // Release the IP address 释放 IP 地址 776 env->ReleaseStringUTFChars(ip, ipAddress); 777 778 // If conversion is failed 如果转换失败 779 if (0 == result) 780 { 781 // Throw an exception with error number 抛出带错误符号的异常 782 ThrowErrnoException(env, "java/io/IOException", errno); 783 goto exit; 784 } 785 786 // Convert port to network byte order 将端口转换为网络字节顺序 787 address.sin_port = htons(port); 788 789 // Get message as C string 以 C 字符串形式获取消息 790 const char* messageText = env->GetStringUTFChars(message, NULL); 791 if (NULL == messageText) 792 goto exit; 793 794 // Get the message size 获取消息大小 795 jsize messageSize = env->GetStringUTFLength(message); 796 797 // Send message to socket 发送消息给 socket 798 SendDatagramToSocket(env, obj, clientSocket, &address, messageText, messageSize); 799 800 // Release the message text 释放消息文本 801 env->ReleaseStringUTFChars(message, messageText); 802 803 // If send was not successful 如果发送未成功 804 if (NULL != env->ExceptionOccurred()) 805 goto exit; 806 807 char buffer[MAX_BUFFER_SIZE]; 808 809 // Clear address 清除地址 810 memset(&address, 0, sizeof(address)); 811 812 // Receive from the socket 从 socket 接收 813 ReceiveDatagramFromSocket(env, obj, clientSocket, &address, buffer, MAX_BUFFER_SIZE); 814 } 815 816 exit: 817 if (clientSocket > 0) 818 { 819 close(clientSocket); 820 } 821 } 822 823 void Java_com_apress_echo_EchoServerActivity_nativeStartUdpServer( 824 JNIEnv* env, 825 jobject obj, 826 jint port) 827 { 828 // Construct a new UDP socket. 构造一个新的 UDP socket 829 int serverSocket = NewUdpSocket(env, obj); 830 if (NULL == env->ExceptionOccurred()) 831 { 832 // Bind socket to a port number 将 socket 绑定到某一端口号 833 BindSocketToPort(env, obj, serverSocket, (unsigned short) port); 834 if (NULL != env->ExceptionOccurred()) 835 goto exit; 836 837 // If random port number is requested 如果请求随机端口号 838 if (0 == port) 839 { 840 // Get the port number socket is currently binded 获取当前绑定的端口号 socket 841 GetSocketPort(env, obj, serverSocket); 842 if (NULL != env->ExceptionOccurred()) 843 goto exit; 844 } 845 846 // Client address 客户端地址 847 struct sockaddr_in address; 848 memset(&address, 0, sizeof(address)); 849 850 char buffer[MAX_BUFFER_SIZE]; 851 ssize_t recvSize; 852 ssize_t sentSize; 853 854 // Receive from the socket 从 socket 中接收 855 recvSize = ReceiveDatagramFromSocket(env, obj, serverSocket, &address, buffer, MAX_BUFFER_SIZE); 856 857 if ((0 == recvSize) || (NULL != env->ExceptionOccurred())) 858 goto exit; 859 860 // Send to the socket 发送给 socket 861 sentSize = SendDatagramToSocket(env, obj, serverSocket, &address, buffer, (size_t) recvSize); 862 } 863 864 exit: 865 if (serverSocket > 0) 866 { 867 close(serverSocket); 868 } 869 } 870 871 /** 872 * Constructs a new Local UNIX socket. 873 * 构造一个新的原生 UNIX socket 874 * 875 * @param env JNIEnv interface. 876 * @param obj object instance. 877 * @return socket descriptor. 878 * @throws IOException 879 */ 880 static int NewLocalSocket(JNIEnv* env, jobject obj) 881 { 882 // Construct socket 构造 socket 883 LogMessage(env, obj, "Constructing a new Local UNIX socket..."); 884 int localSocket = socket(PF_LOCAL, SOCK_STREAM, 0); 885 886 // Check if socket is properly constructed 887 // 检查 socket 构造是否正确 888 if (-1 == localSocket) 889 { 890 // Throw an exception with error number 抛出带错误号的异常 891 ThrowErrnoException(env, "java/io/IOException", errno); 892 } 893 894 return localSocket; 895 } 896 897 /** 898 * Binds a local UNIX socket to a name. 899 * 将本地 UNIX socket 与某一名称绑定 900 * 901 * @param env JNIEnv interface. 902 * @param obj object instance. 903 * @param sd socket descriptor. 904 * @param name socket name. 905 * @throws IOException 906 */ 907 static void BindLocalSocketToName( 908 JNIEnv* env, 909 jobject obj, 910 int sd, 911 const char* name) 912 { 913 struct sockaddr_un address; 914 915 // Name length 名称长度 916 const size_t nameLength = strlen(name); 917 918 // Path length is initiall equal to name length 919 // 路径长度初始化与名称长度相等 920 size_t pathLength = nameLength; 921 922 // If name is not starting with a slash it is in the abstract namespace 923 // 如果名称不是以 '/' 开头,即它在抽象命名空间里 924 bool abstractNamespace = ('/' != name[0]); 925 926 // Abstract namespace requires having the first byte of the path to be the zero byte, 927 // update the path length to include the zero byte 928 // 抽象命名空间要求目录的第一个字节是 0 字节,更新路径长度包括 0 字节。 929 if (abstractNamespace) 930 { 931 pathLength++; 932 } 933 934 // Check the path length 检查路径长度 935 if (pathLength > sizeof(address.sun_path)) 936 { 937 // Throw an exception with error number 抛出带错误号的异常 938 ThrowException(env, "java/io/IOException", "Name is too big."); 939 } 940 else 941 { 942 // Clear the address bytes 清除地址字节 943 memset(&address, 0, sizeof(address)); 944 address.sun_family = PF_LOCAL; 945 946 // Socket path 947 char* sunPath = address.sun_path; 948 949 // First byte must be zero to use the abstract namespace 950 // 第一字节必须是 0 以使用抽象命名空间 951 if (abstractNamespace) 952 { 953 *sunPath++ = NULL; 954 } 955 956 // Append the local name 追加本地名字 957 strcpy(sunPath, name); 958 959 // Address length 地址长度 960 socklen_t addressLength = (offsetof(struct sockaddr_un, sun_path)) + pathLength; 961 962 // Unlink if the socket name is already binded 963 // 如果 socket 名称已绑定,取消连接 964 unlink(address.sun_path); 965 966 // Bind socket 967 LogMessage(env, obj, "Binding to local name %s%s.", 968 (abstractNamespace) ? "(null)" : "", name); 969 970 if (-1 == bind(sd, (struct sockaddr*) &address, addressLength)) 971 { 972 // Throw an exception with error number 抛出带错误号的异常 973 ThrowErrnoException(env, "java/io/IOException", errno); 974 } 975 } 976 } 977 978 /** 979 * Blocks and waits for incoming client connections on the given socket. 980 * 阻塞并等待给定 socket 上即将到来的客户端连接 981 * 982 * @param env JNIEnv interface. 983 * @param obj object instance. 984 * @param sd socket descriptor. 985 * @return client socket. 986 * @throws IOException 987 */ 988 static int AcceptOnLocalSocket( 989 JNIEnv* env, 990 jobject obj, 991 int sd) 992 { 993 // Blocks and waits for an incoming client connection and accepts it 994 // 阻塞并等待即将到来的客户端连接并且接收它 995 LogMessage(env, obj, "Waiting for a client connection..."); 996 int clientSocket = accept(sd, NULL, NULL); 997 998 // If client socket is not valid 如果客户端 socket 无效 999 if (-1 == clientSocket) 1000 { 1001 // Throw an exception with error number 抛出带错误号的异常 1002 ThrowErrnoException(env, "java/io/IOException", errno); 1003 } 1004 1005 return clientSocket; 1006 } 1007 1008 void Java_com_apress_echo_LocalEchoActivity_nativeStartLocalServer( 1009 JNIEnv* env, 1010 jobject obj, 1011 jstring name) 1012 { 1013 // Construct a new local UNIX socket. 构造一个新的本地 UNIX socket 1014 int serverSocket = NewLocalSocket(env, obj); 1015 if (NULL == env->ExceptionOccurred()) 1016 { 1017 // Get name as C string 以 C 字符串形式获取名称 1018 const char* nameText = env->GetStringUTFChars(name, NULL); 1019 if (NULL == nameText) 1020 goto exit; 1021 1022 // Bind socket to a port number 绑定 socket 到某一端口号 1023 BindLocalSocketToName(env, obj, serverSocket, nameText); 1024 1025 // Release the name text 释放名称文本 1026 env->ReleaseStringUTFChars(name, nameText); 1027 1028 // If bind is failed 如果绑定失败 1029 if (NULL != env->ExceptionOccurred()) 1030 goto exit; 1031 1032 // Listen on socket with a backlog of 4 pending connections 1033 // 监听有 4 个挂起连接的带 socket 的 socket 1034 ListenOnSocket(env, obj, serverSocket, 4); 1035 if (NULL != env->ExceptionOccurred()) 1036 goto exit; 1037 1038 // Accept a client connection on socket 接受 socket 的一个客户连接 1039 int clientSocket = AcceptOnLocalSocket(env, obj, serverSocket); 1040 if (NULL != env->ExceptionOccurred()) 1041 goto exit; 1042 1043 char buffer[MAX_BUFFER_SIZE]; 1044 ssize_t recvSize; 1045 ssize_t sentSize; 1046 1047 // Receive and send back the data 接收并发送回数据 1048 while (1) 1049 { 1050 // Receive from the socket 从 socket 中接收 1051 recvSize = ReceiveFromSocket(env, obj, clientSocket, 1052 buffer, MAX_BUFFER_SIZE); 1053 1054 if ((0 == recvSize) || (NULL != env->ExceptionOccurred())) 1055 break; 1056 1057 // Send to the socket 发送给 socket 1058 sentSize = SendToSocket(env, obj, clientSocket, 1059 buffer, (size_t) recvSize); 1060 1061 if ((0 == sentSize) || (NULL != env->ExceptionOccurred())) 1062 break; 1063 } 1064 1065 // Close the client socket 关闭客户端 socket 1066 close(clientSocket); 1067 } 1068 1069 exit: 1070 if (serverSocket > 0) 1071 { 1072 close(serverSocket); 1073 } 1074 }
activity_echo_client.xml
1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 xmlns:tools="http://schemas.android.com/tools" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:orientation="vertical" > 6 7 <EditText 8 android:id="@+id/ip_edit" 9 android:layout_width="match_parent" 10 android:layout_height="wrap_content" 11 android:hint="@string/ip_edit" > 12 13 <requestFocus /> 14 15 </EditText> 16 17 <EditText 18 android:id="@+id/port_edit" 19 android:layout_width="match_parent" 20 android:layout_height="wrap_content" 21 android:hint="@string/port_edit" 22 android:inputType="number" /> 23 24 <EditText 25 android:id="@+id/message_edit" 26 android:layout_width="match_parent" 27 android:layout_height="wrap_content" 28 android:hint="@string/message_edit" /> 29 30 <Button 31 android:id="@+id/start_button" 32 android:layout_width="wrap_content" 33 android:layout_height="wrap_content" 34 android:text="@string/start_client_button" /> 35 36 <ScrollView 37 android:id="@+id/log_scroll" 38 android:layout_width="match_parent" 39 android:layout_height="0dip" 40 android:layout_weight="1.0" > 41 42 <TextView 43 android:id="@+id/log_view" 44 android:layout_width="match_parent" 45 android:layout_height="wrap_content" /> 46 </ScrollView> 47 48 </LinearLayout>
activity_echo_server.xml
1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 xmlns:tools="http://schemas.android.com/tools" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:orientation="vertical" > 6 7 <LinearLayout 8 android:layout_width="match_parent" 9 android:layout_height="wrap_content" 10 android:orientation="horizontal"> 11 12 <EditText 13 android:id="@+id/port_edit" 14 android:layout_width="wrap_content" 15 android:layout_height="wrap_content" 16 android:layout_weight="1" 17 android:hint="@string/port_edit" 18 android:inputType="number" > 19 20 <requestFocus /> 21 </EditText> 22 23 <Button 24 android:id="@+id/start_button" 25 android:layout_width="wrap_content" 26 android:layout_height="wrap_content" 27 android:layout_weight="1" 28 android:text="@string/start_server_button" /> 29 30 </LinearLayout> 31 32 <ScrollView 33 android:id="@+id/log_scroll" 34 android:layout_width="match_parent" 35 android:layout_height="match_parent" > 36 37 <TextView 38 android:id="@+id/log_view" 39 android:layout_width="match_parent" 40 android:layout_height="wrap_content" /> 41 </ScrollView> 42 43 </LinearLayout>
activity_local_echo.xml
1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 xmlns:tools="http://schemas.android.com/tools" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:orientation="vertical" > 6 7 <EditText 8 android:id="@+id/port_edit" 9 android:layout_width="match_parent" 10 android:layout_height="wrap_content" 11 android:hint="@string/local_port_edit" > 12 13 <requestFocus /> 14 15 </EditText> 16 17 <EditText 18 android:id="@+id/message_edit" 19 android:layout_width="match_parent" 20 android:layout_height="wrap_content" 21 android:hint="@string/message_edit" /> 22 23 <Button 24 android:id="@+id/start_button" 25 android:layout_width="wrap_content" 26 android:layout_height="wrap_content" 27 android:text="@string/start_client_button" /> 28 29 <ScrollView 30 android:id="@+id/log_scroll" 31 android:layout_width="match_parent" 32 android:layout_height="0dip" 33 android:layout_weight="1.0" > 34 35 <TextView 36 android:id="@+id/log_view" 37 android:layout_width="match_parent" 38 android:layout_height="wrap_content" /> 39 </ScrollView> 40 41 </LinearLayout>
Echo Manifest.xml
1 <manifest xmlns:android="http://schemas.android.com/apk/res/android" 2 package="com.apress.echo" 3 android:versionCode="1" 4 android:versionName="1.0" > 5 6 <uses-sdk 7 android:minSdkVersion="8" 8 android:targetSdkVersion="15" /> 9 10 <uses-permission android:name="android.permission.INTERNET" /> 11 12 <application 13 android:icon="@drawable/ic_launcher" 14 android:label="@string/app_name" 15 android:theme="@style/AppTheme" > 16 17 <activity 18 android:name=".EchoServerActivity" 19 android:label="@string/title_activity_echo_server" 20 android:launchMode="singleTop" > 21 <intent-filter> 22 <action android:name="android.intent.action.MAIN" /> 23 24 <category android:name="android.intent.category.LAUNCHER" /> 25 </intent-filter> 26 </activity> 27 28 <activity 29 android:name=".EchoClientActivity" 30 android:label="@string/title_activity_echo_client" 31 android:launchMode="singleTop" > 32 <intent-filter> 33 <action android:name="android.intent.action.MAIN" /> 34 35 <category android:name="android.intent.category.LAUNCHER" /> 36 </intent-filter> 37 </activity> 38 <activity 39 android:name=".LocalEchoActivity" 40 android:label="@string/title_activity_local_echo" > 41 <intent-filter> 42 <action android:name="android.intent.action.MAIN" /> 43 44 <category android:name="android.intent.category.LAUNCHER" /> 45 </intent-filter> 46 </activity> 47 </application> 48 49 </manifest>

浙公网安备 33010602011771号