为了账号安全,请及时绑定邮箱和手机立即绑定

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

标签:
Android Kotlin

一. 对原先 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 服务端、踩过的坑,以及如何封装消息。

点击查看更多内容
1人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
全栈工程师
手记
粉丝
1.7万
获赞与收藏
594

关注作者,订阅最新文章

阅读免费教程

感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消