(问题已解决)写 kotlin 给自己写不会了

这两天在写一个 Kotlin Compose 项目,有一个服务类愣是写不明白,几个 AI 问一圈也解决不了。

package com.bhznjns.inputshare.utils

import android.net.LocalServerSocket
import android.net.LocalSocket
import android.net.LocalSocketAddress
import android.util.Log
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import java.io.DataInputStream
import java.io.DataOutputStream
import java.net.SocketException
import java.util.concurrent.Executors

private enum class ServerState {
    STOPPED,
    LISTENING,
    CONNECTED
}

private const val SOCKET_NAME = "inputshare"

class LocalSocketServer {
    private companion object {
        const val TAG = "LocalSocketServer"
    }

    private val serverScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
    private val state = MutableStateFlow(ServerState.STOPPED)
    private val _messageReceivedFlow = MutableSharedFlow<String>()
    private var serverJob: Job? = null
    private var serverSocket: LocalServerSocket? = null
    private val sendChannel: Channel<ByteArray> = Channel(capacity = 64)

    val messageReceivedFlow = _messageReceivedFlow.asSharedFlow()

    fun start() {
        if (serverJob?.isActive == true) {
            Log.w(TAG, "Server is already running.")
            return
        }
        serverJob = serverScope.launch {
            try {
                serverSocket = LocalServerSocket(SOCKET_NAME)
                state.value = ServerState.LISTENING

                while (isActive) {
                    try {
                        Log.d(TAG, "Waiting for client connection.")
                        val client = serverSocket!!.accept()
                        handleClient(client)
                    } catch (_: SocketException) {
                        Log.d(TAG, "Server socket closed.")
                        break
                    } catch (e: Exception) {
                        Log.e(TAG, "Client handler failed: ${e.message}")
                        state.value = ServerState.LISTENING
                    }
                }
            } catch (e: Exception) {
                Log.e(TAG, "Server failed to start or run: ${e.message}")
            } finally {
                Log.i(TAG, "Server loop finished.")
                state.value = ServerState.STOPPED
            }
        }
    }

    @OptIn(ExperimentalCoroutinesApi::class)
    private suspend fun handleClient(client: LocalSocket) {
        state.value = ServerState.CONNECTED

        val senderJob = serverScope.launch {
            val writer = DataOutputStream(client.outputStream)
            try {
                for (messageBytes in sendChannel) {
                    writer.writeInt(messageBytes.size)
                    writer.write(messageBytes)
                    writer.flush()
                }
            } catch (e: Exception) {
                Log.e(TAG, "Sender coroutine failed: ${e.message}")
            }
        }

        try {
            val reader = DataInputStream(client.getInputStream())
            while (serverJob!!.isActive && !senderJob.isCompleted) {
                val messageLength = reader.readInt()
                when {
                    messageLength == 0 -> Log.d(TAG, "Received keep-alive signal.")
                    messageLength < 0 -> Log.e(TAG, "Invalid message length: $messageLength")
                    else -> {
                        val messageBytes = ByteArray(messageLength)
                        reader.readFully(messageBytes)
                        val message = String(messageBytes)
                        _messageReceivedFlow.emit(message)
                    }
                }
            }
        } catch (e: Exception) {
            Log.e(TAG, "Client reader: ${e.message}")
        } finally {
            while (!sendChannel.isEmpty) {
                sendChannel.tryReceive()
            }

            client.close()
            runCancellable { senderJob.cancelAndJoin() }

            Log.d(TAG, "Client disconnected.")
            state.value = ServerState.LISTENING
        }
    }

    fun send(message: String): Boolean {
        if (state.value != ServerState.CONNECTED) {
            Log.w(TAG, "Cannot send message, no client connected.")
            return false
        }
        val messageBytes = message.toByteArray()
        return sendChannel.trySend(messageBytes).isSuccess
    }

    suspend fun stop() {
        Log.d(TAG, "Stopping server.")
        state.value = ServerState.STOPPED
        sendChannel.close()

        serverScope.launch {
            try {
                val tempClient = LocalSocket()
                tempClient.connect(LocalSocketAddress(SOCKET_NAME, LocalSocketAddress.Namespace.ABSTRACT))
                tempClient.close()
            } catch (_: Exception) { }
        }

        Log.d(TAG, "Temp client created and closed.")

        serverJob?.join()
        serverSocket?.close()
        serverScope.cancel()
        Log.d(TAG, "Server stopped.")
    }
}

遇到的问题是服务在 stop 时无法正确释放 socket,导致重新创建服务时会报 Address already in use 错误

调了一天了也没解决,这 kotlin 太难搞了 :sob: :sob:

Android 本机内嵌消息总线实现 - Kimi 你说怎么不明白

2 个赞

我目前遇到的问题如下:
socket 的 accept 调用发生在协程中,无法被 socket.close 中止(貌似必须要在 accept 调用所在线程调用 close 才能正确中止 accept)
如果使用 SingleThreadExecutor 的话,由于 accept 调用是同步阻塞的,在 executor 中调用了 accept 的话,后续无法在同一线程内调用 close。
也因此,会导致服务在退出时无法正确释放 socket,在重新启动时会出现 Address already in use 报错

launch{}会启动一个新的协程,感觉有问题。试试withContext(Dispatchers.IO)

1 个赞

我查到如果 socket closed 会导致 accept blocking, 你看看是不是这个原因

try {
    Os.shutdown(localSocket.fileDescriptor, OsConstants.SHUT_RDWR)
} catch (e: ErrnoException) {
    if (e.errno != OsConstants.EBADF) throw e   // suppress fd already closed
}
1 个赞

感谢,我去看一下

感谢佬,用佬给的帖子中的方法解决了

1 个赞

ok,我对kotlin也不熟悉,正好学习了