凌峰创科服务平台

Android服务器Socket如何实现稳定通信?

核心概念:Socket 是什么?

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

Android服务器Socket如何实现稳定通信?-图1
(图片来源网络,侵删)
  • 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"

Android服务器Socket如何实现稳定通信?-图2
(图片来源网络,侵删)
<application
    ...
    android:usesCleartextTraffic="true"
    ...>
    ...
</application>

2 创建后台线程进行网络操作

严禁在主线程(UI 线程)中进行网络操作! 这会导致应用抛出 NetworkOnMainThreadException 异常,并可能造成应用无响应(ANR)。

我们可以使用 AsyncTask(已废弃,但简单示例可用)、Thread + Handler,或者现代的 Kotlin CoroutinesRxJava,这里我们以 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 布局文件:

Android服务器Socket如何实现稳定通信?-图3
(图片来源网络,侵删)
<?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
分享:
扫描分享到社交APP
上一篇
下一篇