核心概念:Socket 是什么?
Socket(套接字) 是网络编程的 API,它就像一个“插座”,是网络通信的端点,通过这个插座,两个设备(比如你的手机和一台服务器)就可以在网络上进行双向的数据传输。

- IP 地址:就像你家的地址,用来唯一标识网络中的一台设备。
- 端口号:就像你家的门牌号,在一台设备上,可能有多个应用程序在进行网络通信,端口号用来区分这些不同的应用程序。
- TCP/UDP:两种主要的传输协议。
- TCP (Transmission Control Protocol):面向连接、可靠的传输,数据传输前需要先建立连接(三次握手),确保数据按顺序、无丢失地到达,适合对数据准确性要求高的场景,如文件传输、网页浏览、即时通讯,我们通常说的 Socket 指的是 TCP Socket。
- UDP (User Datagram Protocol):无连接、不可靠的传输,发送数据前不需要建立连接,速度快,但不保证数据一定能到达或顺序正确,适合对实时性要求高但能容忍少量丢包的场景,如视频直播、在线游戏。
在 Android 开发中,我们最常用的是 TCP Socket 来实现与服务器之间的稳定通信。
Android 客户端实现
下面我们一步步来构建一个 Android Socket 客户端。
1 添加网络权限
在 AndroidManifest.xml 文件中,必须声明网络权限,否则应用无法访问网络。
<uses-permission android:name="android.permission.INTERNET" />
注意:从 Android 9 (API 28) 开始,默认情况下,应用只能使用 HTTPS,禁用了明文的 HTTP 流量,如果你的服务器使用的是非加密的 HTTP,需要在 application 标签中添加 android:usesCleartextTraffic="true"。

<application
...
android:usesCleartextTraffic="true"
...>
...
</application>
2 创建后台线程进行网络操作
严禁在主线程(UI 线程)中进行网络操作! 这会导致应用抛出 NetworkOnMainThreadException 异常,并可能造成应用无响应(ANR)。
我们可以使用 AsyncTask(已废弃,但简单示例可用)、Thread + Handler,或者现代的 Kotlin Coroutines 或 RxJava,这里我们以 Thread + Handler 为例,因为它清晰地展示了线程间通信。
3 完整客户端代码示例
下面是一个完整的例子,展示了如何连接服务器、发送数据、接收数据,并在 UI 上显示结果。
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.widget.Button
import android.widget.EditText
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import java.io.*
import java.net.Socket
import java.net.SocketException
class MainActivity : AppCompatActivity() {
private val TAG = "SocketClient"
private lateinit var sendBtn: Button
private lateinit var messageEt: EditText
private lateinit var responseTv: TextView
// Handler 用于在主线程中更新 UI
private val mainHandler = Handler(Looper.getMainLooper())
// Socket 相关变量
private var socket: Socket? = null
private var writer: PrintWriter? = null
private var reader: BufferedReader? = null
private var receiveThread: Thread? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
sendBtn = findViewById(R.id.send_btn)
messageEt = findViewById(R.id.message_et)
responseTv = findViewById(R.id.response_tv)
sendBtn.setOnClickListener {
val message = messageEt.text.toString()
if (message.isNotEmpty()) {
// 在新线程中发送消息
Thread {
sendToServer(message)
}.start()
}
}
// 启动接收消息的线程
startReceiveThread()
}
private fun startReceiveThread() {
receiveThread = Thread {
try {
// 连接服务器 (IP 和端口需要替换成你自己的)
socket = Socket("你的服务器IP", 8888)
writer = PrintWriter(BufferedWriter(OutputStreamWriter(socket!!.getOutputStream())), true)
reader = BufferedReader(InputStreamReader(socket!!.getInputStream()))
// 不断读取服务器发来的消息
while (!Thread.currentThread().isInterrupted) {
val serverMessage = reader?.readLine()
if (serverMessage != null) {
// 使用 Handler 将消息传回主线程更新 UI
mainHandler.post {
responseTv.text = "服务器: $serverMessage\n${responseTv.text}"
}
} else {
// 服务器断开连接
Log.d(TAG, "服务器已断开连接")
break
}
}
} catch (e: SocketException) {
Log.e(TAG, "Socket 错误: ${e.message}")
} catch (e: IOException) {
Log.e(TAG, "IO 错误: ${e.message}")
} catch (e: Exception) {
Log.e(TAG, "未知错误: ${e.message}")
} finally {
// 确保资源被释放
releaseResources()
}
}
receiveThread?.start()
}
private fun sendToServer(message: String) {
try {
writer?.println(message) // 发送消息,println 会自动添加换行符
Log.d(TAG, "已发送: $message")
} catch (e: Exception) {
Log.e(TAG, "发送失败: ${e.message}")
}
}
private fun releaseResources() {
try {
reader?.close()
writer?.close()
socket?.close()
} catch (e: IOException) {
Log.e(TAG, "关闭资源时出错: ${e.message}")
}
}
override fun onDestroy() {
super.onDestroy()
// 在 Activity 销毁时,中断接收线程并关闭资源
receiveThread?.interrupt()
releaseResources()
}
}
activity_main.xml 布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<EditText
android:id="@+id/message_et"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="输入要发送的消息"
android:inputType="textMultiLine"
android:minHeight="100dp" />
<Button
android:id="@+id/send_btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="发送" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_marginTop="16dp">
<TextView
android:id="@+id/response_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="接收到的消息将显示在这里..."
android:textIsSelectable="true" />
</ScrollView>
</LinearLayout>
服务器端实现(Java)
服务器端可以使用 Java 来编写,它比 Android 环境更简单,没有那么多限制,这里我们使用 Java SE 的 ServerSocket。
这是一个简单的多线程 Echo Server,它可以同时处理多个客户端连接。
import java.io.*;
import java.net.*;
import java.util.concurrent.*;
public class SocketServer {
private static final int PORT = 8888;
private static final int THREAD_POOL_SIZE = 10; // 最大客户端连接数
public static void main(String[] args) {
// 使用线程池来处理客户端连接,避免为每个连接都创建一个新线程
ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
try (ServerSocket serverSocket = new ServerSocket(PORT)) {
System.out.println("服务器已启动,监听端口: $PORT");
while (true) {
// 阻塞,等待客户端连接
Socket clientSocket = serverSocket.accept();
System.out.println("新客户端连接: " + clientSocket.getInetAddress().getHostAddress());
// 为每个客户端连接创建一个任务,并提交到线程池
threadPool.execute(new ClientHandler(clientSocket));
}
} catch (IOException e) {
System.err.println("服务器异常: " + e.getMessage());
} finally {
threadPool.shutdown();
}
}
// 客户端处理器
static class ClientHandler implements Runnable {
private final Socket clientSocket;
public ClientHandler 