Kotlin + Netty 在 Android 上实现 Socket 的服务端(续篇)

简介: Kotlin + Netty 在 Android 上实现 Socket 的服务端(续篇)

一. 对原先 NettyServer 的改造



上一篇文章《Kotlin + Netty 在 Android 上实现 Socket 的服务端》 ,曾经介绍的 NettyServer 其实只存了最后一次使用的 Channel。


Channel 是 Netty 网络操作抽象类,包括网络的读、写、发起连接、链路关闭等,它是 Netty 网络通信的主体。


在现实的开发中,服务端可能需要的是保存多个 Channel,例如存放到 ConcurrentHashMap。


当客户端连上服务端时,通过 NettyServer 的 addChannel() 将 channel 添加到 Map 中。


当客户端断开服务端时,通过 NettyServer  的 removeChannel() 将 channel 从 Map 中移除。


为了安全考虑,服务端可能会主动断开某个 channel,则通过 NettyServer  的 disConnectChannel() 实现。

private val channelMap = ConcurrentHashMap<String,Channel>() // Map 存储 channelId、Channel
    fun addChannel(channel: Channel) {
        channelMap.put(channel.id().asShortText(), channel)
    }
    fun removeChannel(channelId:String) {
        channelMap.remove(channelId)
    }
    /**
     * 根据 channelId,断开 channel
     */
    fun disConnectChannel(channelId:String) {
        channelMap.get(channelId)?.let {
            it.close()
            // 此时不用通过 channelMap 来 remove channelId,因为 NettyService 会监听到断开连接并调用 removeChannel()
        }
    }


二. 通过 Service 方式启动 Netty 服务



2.1 NettyService 的实现


由于 App 中存在多个 Activity 会用到 Netty 的相关服务例如接受来自客户端的消息、发送消息到客户端,所以采用 Service 方式来启动 Netty 服务端是一种比较好的选择。


Service 跟 Activity 的交互也可以借助 EventBus 进行通信。

class NettyService : Service() {
    var handler:Handler = Handler(Looper.getMainLooper())
    override fun onCreate() {
        super.onCreate()
        startServer()
    }
    // 启动 Netty 服务端
    private fun startServer() {
        if (!NettyServer.isServerStart) {
            NettyServer.setListener(object : NettyServerListener<String>{
                /**
                 * 网页发送的 WebSocket 消息的回调
                 */
                override fun onMessageResponseServer(msg: String, ChannelId: String) {
                    LogUtils.d("msg = $msg")
                    val message = GsonUtils.fromJson<RequestMessage>(msg, RequestMessage::class.java)
                    if (message is RequestMessage) {
                        when(message.action) {
                             ......
                        }
                    }
                }
                /**
                 * 监听 Netty Server 的启动
                 */
                override fun onStartServer() {
                    LogUtils.d("NettyServer Start")
                }
                /**
                 * 监听 Netty Server 的关闭
                 */
                override fun onStopServer() {
                    LogUtils.d("NettyServer Stop")
                }
                /**
                 * 监听 Netty Server 的连接
                 */
                override fun onChannelConnect(channel: Channel) {
                    val insocket = channel.remoteAddress() as InetSocketAddress
                    val clientIP = insocket.address.hostAddress
                    NettyServer.addChannel(channel)
                    LogUtils.d("connect client: $clientIP")
                    handler.post {
                        BusManager.getBus().post(ConnectWifiEvent(clientIP,channel.id().asShortText()))
                    }
                }
                /**
                 * 监听 Netty Server 的断开连接
                 */
                override fun onChannelDisConnect(channel: Channel) {
                    val ip = channel.remoteAddress().toString()
                    NettyServer.removeChannel(channel.id().asShortText())
                    LogUtils.d("disconnect client: $ip")
                    handler.post {
                        BusManager.getBus().post(DisConnectWifiEvent())
                    }
                }
            })
            NettyServer.port = 8888
            NettyServer.webSocketPath = "/xxx_path"
            NettyServer.start()
        }
    }
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        return super.onStartCommand(intent, flags, startId)
    }
    override fun onDestroy() {
        if (NettyServer.isServerStart) { // 关闭 Netty Server
            NettyServer.disconnect()
        }
        super.onDestroy()
    }
    override fun onBind(intent: Intent): IBinder? {
        return null
    }
}


2.2 onStartCommand 的坑?


在使用 NettyService 时,发现会遇到如下的异常:

Caused by: java.lang.IllegalArgumentException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull, parameter intent


NettyService 是通过 startService() 启动,所以会调用 onStartCommand()。而使用 Kotlin 在创建 Service 时,默认的 onStartCommand() 方法是这样的:

override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
        return super.onStartCommand(intent, flags, startId)
    }


但是 intent 存在为空的可能性,需要改成:

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        return super.onStartCommand(intent, flags, startId)
    }


三. 对消息的封装



客户端和服务端之间传递的消息类型是 String 类型。即便是这样,还是要稍微定义一下 Message 的格式。


例如:

abstract class Message{
    abstract var action:String
}
// 网页发送过来的消息
data class RequestMessage(override var action:String="",val body:Map<String,String>?=null):Message()
// 发送给网页的消息
data class ResponseMessage(override var action:String="",val body:Map<String,String>?=null):Message()


封装一个 NettyManager,它是单例,用于专门发送消息给客户端。


其中 sendMsg(msg: ()->String) 方法,它的参数是函数类型。将函数作为参数,可扩展性会更强。


Kotlin 的函数是第一等公民,函数就是对象,这是 Kotlin 作为函数式编程语言的重要特性。对象可以直接赋值给变量、可以作为某个函数的参数、也可以作为别的函数的返回值,那么函数也可以。

object NettyManager {
    fun sendXXX() {
        sendMsg {
            val responseMsg = ResponseMessage(action="xxx")
            GsonUtils.toJson(responseMsg)
        }
    }
    /**
     * 服务端向网页发送生成二维码的消息,并返回生成随机的字符串
     */
    fun sendDisplayQrcode():String{
        val action = "display_qrcode"
        val map = mutableMapOf<String,String>()
        val randomString = RandomStringUtils.randomAlphanumeric(6)
        map.put("qrCode", randomString)
        sendMsg {
            val responseMsg = ResponseMessage(action,map)
            GsonUtils.toJson(responseMsg)
        }
        return randomString
    }
    ......
    private fun sendMsg(msg: ()->String) {
        NettyServer.sendMsgToWS(msg.invoke(), ChannelFutureListener { channelFuture ->
            if (channelFuture.isSuccess) {
                LogUtils.d("write successful")
            } else {
                LogUtils.d("write error")
            }
        })
    }
}


服务端发送消息给客户端:

NettyManager.sendXXX()


四. 总结



本文是上一篇《Kotlin + Netty 在 Android 上实现 Socket 的服务端》的延续,介绍了如何做一个 Android 的 Netty 服务端、踩过的坑,以及如何封装消息。

相关文章
|
2月前
|
设计模式 Android开发 Kotlin
Android经典实战之Kotlin委托模式和by关键字
本文介绍了Kotlin中`by`关键字在类及属性委托中的运用,通过实例展示了如何利用类委托简化接口实现,以及如何借助标准与自定义属性委托管理属性的读写操作。通过`by`关键字的支持,Kotlin使得委托模式的实现更为直观且高效。
51 4
|
21天前
|
Android开发 Kotlin
Android经典面试题之Kotlin的==和===有什么区别?
本文介绍了 Kotlin 中 `==` 和 `===` 操作符的区别:`==` 用于比较值是否相等,而 `===` 用于检查对象身份。对于基本类型,两者行为相似;对于对象引用,`==` 比较值相等性,`===` 检查引用是否指向同一实例。此外,还列举了其他常用比较操作符及其应用场景。
171 93
|
14天前
|
存储 机器人 Linux
Netty(二)-服务端网络编程常见网络IO模型讲解
Netty(二)-服务端网络编程常见网络IO模型讲解
|
29天前
|
安全 Android开发 开发者
探索安卓开发的未来:Kotlin的崛起与Flutter的挑战
在移动开发的广阔天地中,安卓平台始终占据着举足轻重的地位。随着技术的不断进步和开发者需求的多样化,Kotlin和Flutter成为了改变游戏规则的新玩家。本文将深入探讨Kotlin如何以其现代化的特性赢得开发者的青睐,以及Flutter凭借跨平台的能力如何挑战传统的安卓开发模式。通过实际案例分析,我们将揭示这两种技术如何塑造未来的安卓应用开发。
57 6
|
26天前
|
Android开发 开发者 Kotlin
告别AsyncTask:一招教你用Kotlin协程重构Android应用,流畅度飙升的秘密武器
【9月更文挑战第13天】随着Android应用复杂度的增加,有效管理异步任务成为关键。Kotlin协程提供了一种优雅的并发操作处理方式,使异步编程更简单直观。本文通过具体示例介绍如何使用Kotlin协程优化Android应用性能,包括网络数据加载和UI更新。首先需在`build.gradle`中添加coroutines依赖。接着,通过定义挂起函数执行网络请求,并在`ViewModel`中使用`viewModelScope`启动协程,结合`Dispatchers.Main`更新UI,避免内存泄漏。使用协程不仅简化代码,还提升了程序健壮性。
50 1
|
1月前
|
安全 Java Android开发
探索安卓应用开发的新趋势:Kotlin和Jetpack Compose
在安卓应用开发领域,随着技术的不断进步,新的编程语言和框架层出不穷。Kotlin作为一种现代的编程语言,因其简洁性和高效性正逐渐取代Java成为安卓开发的首选语言。同时,Jetpack Compose作为一个新的UI工具包,提供了一种声明式的UI设计方法,使得界面编写更加直观和灵活。本文将深入探讨Kotlin和Jetpack Compose的特点、优势以及如何结合使用它们来构建现代化的安卓应用。
37 4
|
2月前
|
算法 安全 数据安全/隐私保护
Android经典实战之常见的移动端加密算法和用kotlin进行AES-256加密和解密
本文介绍了移动端开发中常用的数据加密算法,包括对称加密(如 AES 和 DES)、非对称加密(如 RSA)、散列算法(如 SHA-256 和 MD5)及消息认证码(如 HMAC)。重点讲解了如何使用 Kotlin 实现 AES-256 的加密和解密,并提供了详细的代码示例。通过生成密钥、加密和解密数据等步骤,展示了如何在 Kotlin 项目中实现数据的安全加密。
60 1
|
2月前
|
算法 安全 数据安全/隐私保护
Android经典实战之常见的移动端加密算法和用kotlin进行AES-256加密和解密
本文介绍了移动端开发中常用的数据加密算法,包括对称加密(如 AES 和 DES)、非对称加密(如 RSA)、散列算法(如 SHA-256 和 MD5)及消息认证码(如 HMAC)。重点展示了如何使用 Kotlin 实现 AES-256 的加密和解密,提供了详细的代码示例。
46 2
|
2月前
|
Java 调度 Android开发
Android经典实战之Kotlin的delay函数和Java中的Thread.sleep有什么不同?
本文介绍了 Kotlin 中的 `delay` 函数与 Java 中 `Thread.sleep` 方法的区别。两者均可暂停代码执行,但 `delay` 适用于协程,非阻塞且高效;`Thread.sleep` 则阻塞当前线程。理解这些差异有助于提高程序效率与可读性。
52 1
|
29天前
|
调度 Android开发 开发者
探索安卓开发中的新技术:Kotlin协程
【9月更文挑战第9天】本文将深入探讨Kotlin协程在安卓开发中的应用,揭示其如何优雅地处理异步任务。我们将从基础概念入手,逐步深入到实际开发场景,最后通过代码示例直观展示协程的魔力。无论你是初学者还是有经验的开发者,这篇文章都将为你打开一扇通往更高效、更简洁代码的大门。