kotlin+Socket+协程 示例


Client.kt
class ClientManager(
    var address: String,
    var onLicked: ((String?, Exception?) -> Unit)? = null
) {

    private val scope = MainScope()

    private var socket: Socket? = null

    @Synchronized
    private fun getSocket(block: (Socket) -> Unit) {
        socket?.apply {
            block.invoke(this)
        } ?: let {
            scope.launch(Dispatchers.IO) {
                try {
                    socket = Socket(address, SERVER_PORT).apply {
                        e("启动客户端,IP:${this.inetAddress}端口号:${this.port}")
                        onLicked?.invoke(inetAddress?.hostAddress, null)
                        block.invoke(this)
                    }
                } catch (e: Exception) {
                    e.printStackTrace()
                    onLicked?.invoke("", e)
                }
            }
        }
    }

    fun startClient(
        onReceive: ((String?) -> Unit)? = null,
        success: ((Exception?) -> Unit)? = null
    ) {
        getSocket {
            scope.receive(it, onReceive, success)
        }
    }

    fun sendMsg(msg: String, result: ((Exception?) -> Unit)? = null) {
        getSocket {
            scope.send(socket, msg, result)
        }
    }

    fun close(onCutoff: ((String?) -> Unit)? = null) {
        socket?.closed(onCutoff)
        scope.cancel()
        socket = null
    }
}

internal fun Socket.closed(onCutoff: ((String?) -> Unit)? = null) {
    try {
        close()
        e("关闭客户端")
        onCutoff?.invoke(this.inetAddress?.hostAddress)
    } catch (e: Exception) {
        e.printStackTrace()
    }
}

internal fun CoroutineScope.send(
    socket: Socket?,
    msg: String,
    result: ((Exception?) -> Unit)? = null,
    onCutoff: ((String?) -> Unit)? = null
): Job {
    return this.launch(Dispatchers.IO) {
        try {
            withTimeout(Constant.TIME_OUT_SEND) {
                socket?.getOutputStream()?.let {
                    e("客户端发送:$msg")
                    it.write(msg.toByteArray(Charsets.UTF_8))
                    it.flush()
                    withMain { result?.invoke(null) }
                }
                    ?: withMain { result?.invoke(NullPointerException("socket or OutputStream is null")) }
            }
        } catch (e: Exception) {
            e.printStackTrace()
            socket?.closed(onCutoff)
            withMain { result?.invoke(e) }
        }
    }
}

internal fun CoroutineScope.receive(
    socket: Socket?,
    onReceive: ((String?) -> Unit)? = null,
    success: ((Exception?) -> Unit)? = null,
    onCutoff: ((String?) -> Unit)? = null
): Job {
    return this.launch(Dispatchers.IO) {
        socket?.apply {
            if (isClosed) {
                withMain { success?.invoke(SocketException("Socket is closed")) }
            }
            while (!isClosed) {
                try {
                    getInputStream()?.use { input ->
                        withMain { success?.invoke(null) }
                        val buffer = ByteArray(1024)
                        var len: Int
                        while (input.read(buffer).also { len = it } != -1) {
                            val msg = String(buffer, 0, len)
                            if (!msg.isReceipt()) {
                                getOutputStream()?.let {
                                    it.write(
                                        "${Constant.TAG_RECEIPT_HEADER}客户端回执:已收到消息".toByteArray(
                                            Charsets.UTF_8
                                        )
                                    )
                                    it.flush()
                                }
                            }
                            val s = msg.removeHeader()
                            withMain { onReceive?.invoke(s) }
                            e("收到来自${socket.inetAddress.hostAddress}:${socket.port}的数据为:$s")
                        }
                        withMain { onReceive?.invoke("断开连接") }
                        socket.closed(onCutoff)
                    }
                        ?: withMain { success?.invoke(NullPointerException("socket or InputStream is null")) }
                } catch (e: Exception) {
                    e.printStackTrace()
                    socket.closed(onCutoff)
                    withMain { success?.invoke(e) }
                }
            }
        } ?: withMain { success?.invoke(NullPointerException("socket is null")) }
    }
}
Server.kt
class ServerManager(var launch: ((String?, Exception?) -> Unit)? = null) {
    private var clientCount = 0
    private val socketMap = HashMap<String, Socket>()
    private val clientAddress = ArrayList<String>()
    private val scope = MainScope()
    private var serverSocket: ServerSocket? = null

    private fun addSocket(address: String?, socket: Socket?) {
        if (!(address.isNullOrEmpty() || clientAddress.contains(address))) {
            socket?.apply {
                clientAddress.add(address)
                socketMap[address] = socket
                clientCount = socketMap.size
                e(address, clientCount)
            }
        }
    }

    private fun removeSocket(address: String?) {
        if (!address.isNullOrEmpty()) {
            clientAddress.remove(address)
            socketMap.remove(address)
            clientCount = socketMap.size
        }
    }

    @Synchronized
    private fun getServerSocket(block: (ServerSocket) -> Unit) {
        serverSocket?.apply {
            block.invoke(this)
        } ?: let {
            try {
                serverSocket =
                    ServerSocket(
                        Constant.SERVER_PORT,
                        Constant.MAX_BACKLOG,
                        InetAddress.getByName(getIpV4String())
                    ).apply {
                        e("启动服务 IP:${this.localSocketAddress}")
                        launch?.invoke(this.inetAddress.hostAddress, null)
                        block.invoke(this)
                    }
            } catch (e: Exception) {
                launch?.invoke("", e)
            }
        }
    }

    fun startServer(
        onReceive: ((String?) -> Unit)? = null,
        onLink: ((address: String?, socket: Socket?, error: Exception?) -> Unit)? = null
    ) {
        getServerSocket {
            scope.accept(it, onReceive, {
                removeSocket(it)
            }) { address, socket, error ->
                onLink?.invoke(address, socket, error)
                addSocket(address, socket)
            }
        }
    }

    fun close() {
        serverSocket?.closed()
        scope.cancel()
        socketMap.forEach {
            try {
                it.value.close()
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
        socketMap.clear()
        clientAddress.clear()
    }

    fun sendMsg(msg: String, result: ((Exception?) -> Unit)? = null) {
        scope.launch(Dispatchers.IO) {
            try {
                var count = 0
                socketMap.forEach {
                    it.value.getOutputStream()?.let { out ->
                        out.write(msg.toByteArray(Charsets.UTF_8))
                        out.flush()
                        val s = "服务端发送:$msg"
                        e(s)
                        count++
                    }
                }
                if (count != clientCount) {
                    withMain { result?.invoke(SocketException("${clientCount - count} failed")) }
                }
            } catch (e: Exception) {
                e.printStackTrace()
                withMain { result?.invoke(e) }
            }
        }
    }

    fun sendMsg(address: String, msg: String, result: ((Exception?) -> Unit)? = null) {
        scope.launch(Dispatchers.IO) {
            try {
                socketMap[address]?.getOutputStream()?.let { out ->
                    out.write(msg.toByteArray(Charsets.UTF_8))
                    out.flush()
                    val s = "服务端发送:$msg"
                    e(s)
                    withMain { result?.invoke(null) }
                }
                    ?: withMain { result?.invoke(NullPointerException("socket or OutputStream is null")) }
            } catch (e: Exception) {
                e.printStackTrace()
                withMain { result?.invoke(e) }
            }
        }
    }

    private fun getIpV4String(): String? {
        try {
            val enNetI: Enumeration<NetworkInterface> = NetworkInterface.getNetworkInterfaces()
            while (enNetI.hasMoreElements()) {
                val netI: NetworkInterface = enNetI.nextElement()
                val enumIp: Enumeration<InetAddress> = netI.inetAddresses
                while (enumIp.hasMoreElements()) {
                    val ip: InetAddress = enumIp.nextElement()
                    if (ip is Inet4Address && !ip.isLoopbackAddress) {
                        return ip.hostAddress
                    }
                }
            }
        } catch (e: SocketException) {
            e.printStackTrace()
        }
        return "127.0.0.1"
    }
}

internal fun ServerSocket.closed(onCutoff: ((String?) -> Unit)? = null) {
    try {
        close()
        e("关闭服务端")
        onCutoff?.invoke(this.inetAddress?.hostAddress)
    } catch (e: Exception) {
        e.printStackTrace()
    }
}

internal fun CoroutineScope.accept(
    serverSocket: ServerSocket?,
    onReceive: ((String?) -> Unit)? = null,
    onCutoff: ((String?) -> Unit)? = null,
    success: ((address: String?, socket: Socket?, error: Exception?) -> Unit)? = null
): Job {
    return this.launch(Dispatchers.IO) {
        val mutex = Mutex()
        serverSocket?.apply {
            if (isClosed) {
                withMain { success?.invoke("", null, SocketException("serverSocket is closed")) }
            }
            while (!isClosed) {
                try {
                    accept()?.let { socket ->
                        socket.remoteSocketAddress?.apply {
                            mutex.withLock(this) {
                                withMain {
                                    success?.invoke(
                                        socket.inetAddress.hostAddress,
                                        socket,
                                        null
                                    )
                                }
                                receive(socket, onReceive, onCutoff)
                            }
                        }
                    }
                } catch (e: Exception) {
                    serverSocket.closed()
                    withMain { success?.invoke("", null, e) }
                }
            }
        } ?: withMain { success?.invoke("", null, NullPointerException("serverSocket is null")) }
    }
}

internal fun CoroutineScope.receive(
    socket: Socket?,
    onReceive: ((String?) -> Unit)? = null,
    onCutoff: ((String?) -> Unit)? = null
): Job {
    return this.launch(Dispatchers.IO) {
        socket?.apply {
            try {
                getInputStream()?.let { input ->
                    val buffer = ByteArray(1024)
                    var len: Int
                    while (input.read(buffer).also { len = it } != -1) {
                        val msg = String(buffer, 0, len)
                        if (!msg.isReceipt()) {
                            getOutputStream()?.let {
                                it.write(
                                    "${Constant.TAG_RECEIPT_HEADER}服务端回执:已收到消息".toByteArray(
                                        Charsets.UTF_8
                                    )
                                )
                                it.flush()
                            }
                        }
                        val s = msg.removeHeader()
                        withMain { onReceive?.invoke(s) }
                        e("收到来自${socket.inetAddress.hostAddress}:${socket.port}的数据为:$s")
                    }
                    withMain { onReceive?.invoke("断开连接") }
                    socket.closed(onCutoff)
                }
            } catch (e: Exception) {
                socket.closed(onCutoff)
                e.printStackTrace()
            }
        }
    }
}

工具
Constant
public interface Constant {
    String TAG_RECEIPT_HEADER = "${HZ}$:";
    int SERVER_PORT = 10086;
    long TIME_OUT_SEND = 30000;
    long TIME_OUT_LINK = 30000;
    int MAX_BACKLOG = 50;
}

Utils.kt
internal fun String.isReceipt() = startsWith(Constant.TAG_RECEIPT_HEADER)

internal fun String.removeHeader() = if (isReceipt()) StringBuffer(this)
    .replace(0, getHeaderLength(), "")
    .toString() else this

internal fun getHeaderLength() = Constant.TAG_RECEIPT_HEADER.length

internal suspend fun <T> withMain(block: suspend CoroutineScope.() -> T) {
    withContext(Dispatchers.Main, block = block)
}

internal suspend fun <T> withIO(block: suspend CoroutineScope.() -> T) {
    withContext(Dispatchers.IO, block = block)
}
Log.kt
private fun getInfo(): String {
    val targetElement = Thread.currentThread().stackTrace[4]
    var className = targetElement.className
    val classNameInfo = className.split("\\.".toRegex()).toTypedArray()
    if (classNameInfo.isNotEmpty()) {
        className = classNameInfo[classNameInfo.size - 1]
    }
    if (className.contains("$")) {
        className = className.split("\\$".toRegex()).toTypedArray()[0]
    }
    return "Thread: ${Thread.currentThread().name}, ${targetElement.methodName}(${className}.java:${targetElement.lineNumber})${
        System.getProperty(
            "line.separator"
        )
    }"
}

internal fun e(vararg msgs: Any?) {
    val info = getInfo()
    Log.e("header", info)
    msgs.forEach {
        Log.e("body", it.toString())
    }
    Log.e("end", "________________________________________")
}

internal fun i(vararg msgs: Any?) {
    val info = getInfo()
    Log.i("header", info)
    msgs.forEach {
        Log.i("body", it.toString())
    }
    Log.i("end", "________________________________________")
}


internal fun d(vararg msgs: Any?) {
    val info = getInfo()
    Log.d("header", info)
    msgs.forEach {
        Log.d("body", it.toString())
    }
    Log.d("end", "________________________________________")
}

internal fun w(vararg msgs: Any?) {
    val info = getInfo()
    Log.w("header", info)
    msgs.forEach {
        Log.w("body", it.toString())
    }
    Log.w("end", "________________________________________")
}
MainActivity
class MainActivity : AppCompatActivity() {
    private var clientManager: ClientManager? = null
    private var serverManager: ServerManager? = null
    override fun onDestroy() {
        clientManager?.close()
        serverManager?.close()
        super.onDestroy()
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        serverManager ?: let {
            serverManager = ServerManager { s, e ->
                e(s, e?.message)
            }.apply {
                startServer({
                    e(it)
                    onReceive(it)
                }, { s, sc, e ->
                    e(s, e?.message)
                    if (s.isNullOrEmpty()) {
                        onReceive("连接客户端失败")
                    } else {
                        onReceive("连接客户端:$s")
                    }
                })
            }
        }
        btn_link?.setOnClickListener {
            link()
        }
        btn_client_send?.setOnClickListener { clientSend() }
        btn_server_send?.setOnClickListener { serverSend() }
        tv_receive_msg?.movementMethod = ScrollingMovementMethod.getInstance()
    }

    @SuppressLint("SetTextI18n")
    fun onReceive(msg: String?) {
        runOnUiThread {
            tv_receive_msg?.text = "${tv_receive_msg?.getString()}\n$msg"
        }
    }

    private fun link() {
        et_server_address?.getString()?.apply {
            if (!isNullOrEmpty()) {
                clientManager = ClientManager(this) { s, e ->
                    e(s, e)
                    if (s.isNullOrEmpty()) {
                        onReceive("连接服务端失败")
                    } else {
                        onReceive("连接服务端:$s")
                    }
                }.apply {
                    startClient({
                        e(it)
                        onReceive(it)
                    }, {
                        e(it?.message)
                    })
                }
            } else {
                toast("地址不能为空")
            }
        }
    }

    private fun clientSend() {
        et_send_msg?.getString()?.apply {
            if (!isNullOrEmpty()) {
                clientManager?.sendMsg(this) {
                    e(it?.message)
                }
                et_send_msg?.setText("")
            } else {
                toast("消息不能为空")
            }
        }
    }

    private fun serverSend() {
        et_send_msg?.getString()?.apply {
            if (!isNullOrEmpty()) {
                serverManager?.sendMsg(this) {
                    e(it?.message)
                }
                et_send_msg?.setText("")
            } else {
                toast("消息不能为空")
            }
        }
    }

    private fun TextView.getString() = text?.toString() ?: ""

    private fun toast(msg: String) {
        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
    }
}
  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值