基于双向通信Socket编程的同网Android设备服务端-客户端APP开发问询
Hey there! Let's break down exactly how to build a bidirectional communication app between two Android devices on the same local network—one acting as the server, the other as the client. I've put together a practical, step-by-step guide that you can follow right away:
1. 核心技术选型
First up, let's pick the right tools for the job:
- TCP Socket: This is your go-to for reliable local network communication. It's natively supported on Android, requires no extra dependencies, and enables real-time, two-way data transfer.
- WifiManager: Use this to fetch the server device's local IP address, so the client can locate and connect to it.
2. 服务端实现步骤
第一步:声明必要权限
Add these permissions to your AndroidManifest.xml—they let your app access the network and Wi-Fi details:
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <!-- Android 10+ 需要后台网络权限 --> <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
第二步:服务端核心逻辑
Create a manager class to handle socket listening, client connections, and message sending/receiving. Run all network operations on a background thread to avoid blocking the main UI thread:
class ServerManager(private val onMessageReceived: (String) -> Unit) { private var serverSocket: ServerSocket? = null private var clientSocket: Socket? = null private var inputStream: BufferedReader? = null private var outputStream: PrintWriter? = null private val port = 8888 // 自定义端口,确保两台设备都能访问 fun startServer() { Thread { try { serverSocket = ServerSocket(port) // 等待客户端连接请求 clientSocket = serverSocket?.accept() // 初始化输入输出流 inputStream = BufferedReader(InputStreamReader(clientSocket?.inputStream)) outputStream = PrintWriter(clientSocket?.outputStream, true) // 持续监听客户端发来的消息 var message: String? while (inputStream?.readLine().also { message = it } != null) { message?.let { // 切换回主线程更新UI Handler(Looper.getMainLooper()).post { onMessageReceived(it) } } } } catch (e: IOException) { e.printStackTrace() } finally { closeConnections() } }.start() } // 向客户端发送消息 fun sendMessage(message: String) { outputStream?.println(message) } // 关闭所有连接资源 private fun closeConnections() { inputStream?.close() outputStream?.close() clientSocket?.close() serverSocket?.close() } }
第三步:获取服务端本地IP
Add this helper function to get the server's local network IP address (so the client knows where to connect):
fun getLocalIpAddress(context: Context): String? { val wifiManager = context.getSystemService(Context.WIFI_SERVICE) as WifiManager val ipAddress = wifiManager.connectionInfo.ipAddress return String.format( "%d.%d.%d.%d", (ipAddress and 0xff), (ipAddress shr 8 and 0xff), (ipAddress shr 16 and 0xff), (ipAddress shr 24 and 0xff) ) }
3. 客户端实现步骤
Create a client manager class to handle connecting to the server and exchanging messages:
class ClientManager(private val serverIp: String, private val onMessageReceived: (String) -> Unit) { private var socket: Socket? = null private var inputStream: BufferedReader? = null private var outputStream: PrintWriter? = null private val port = 8888 // 和服务端端口保持一致 fun connectToServer() { Thread { try { socket = Socket(serverIp, port) inputStream = BufferedReader(InputStreamReader(socket?.inputStream)) outputStream = PrintWriter(socket?.outputStream, true) // 持续监听服务端发来的消息 var message: String? while (inputStream?.readLine().also { message = it } != null) { message?.let { Handler(Looper.getMainLooper()).post { onMessageReceived(it) } } } } catch (e: IOException) { e.printStackTrace() } finally { closeConnections() } }.start() } // 向服务端发送消息 fun sendMessage(message: String) { outputStream?.println(message) } // 关闭所有连接资源 private fun closeConnections() { inputStream?.close() outputStream?.close() socket?.close() } }
4. 整合到UI层
In your server Activity, you can initialize the manager and tie it to UI elements (like buttons and text views):
class ServerActivity : AppCompatActivity() { private lateinit var serverManager: ServerManager override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_server) // 显示服务端本地IP val localIp = getLocalIpAddress(this) tv_ip.text = "服务端IP: $localIp" // 初始化服务端管理器,处理收到的消息 serverManager = ServerManager { message -> tv_received_message.text = "收到客户端消息: $message" } // 启动服务端 btn_start_server.setOnClickListener { serverManager.startServer() tv_status.text = "服务端已启动,等待客户端连接..." } // 发送消息到客户端 btn_send.setOnClickListener { val message = et_message.text.toString() serverManager.sendMessage(message) } } override fun onDestroy() { super.onDestroy() // 销毁时关闭连接 serverManager.closeConnections() } }
For the client Activity, simply let the user input the server's IP address, then connect and send/receive messages using the ClientManager class—similar to the server setup.
5. 关键注意事项
- Always use background threads: Network operations can't run on the main thread (Android will throw a
NetworkOnMainThreadException), so keep all socket logic in separate threads. - Handle runtime permissions: On Android 6.0+, you need to request dangerous permissions like
ACCESS_WIFI_STATEandINTERNETat runtime—don't skip this, or your app will crash. - Choose a unique port: Stick to a port that's not commonly used (like 8888 or 9999) to avoid conflicts with other apps on the network.
- Add reconnection logic: If the connection drops (e.g., one device loses Wi-Fi), add automatic retry logic to keep the app robust.
- Serialize complex data: If you need to send objects instead of plain text, use libraries like Gson or ProtoBuf to convert them to strings/byte streams for transmission.
内容的提问来源于stack exchange,提问作者Ravi Verma




