Android kotlin使用Netty网络框架实践(客户端、服务端)

开发工具:Android studio 

语言:kotlin

设计原理:通讯协议:头+类型+长度+数据+尾,自定义编解码器,解析和包装发送数据流,以下贴出部分关键代码

说明:代码中封装了client和server端,可以点击按钮进行通讯,可以直接在项目中使用,尤其是处理了粘包和分包问题。

编译后的效果图:

注:结尾附上完整代码下载链接

1、配置build.gradle文件

 implementation("io.netty:netty-all:5.0.0.Alpha2")

2、主要代码

2.1 server端主要代码
    /**
     * 启动服务端
     */
    fun start() {
        Executors.newSingleThreadScheduledExecutor().submit {
            XLogUtil.d( "********服务启动********")
            bossGroup =NioEventLoopGroup()
            workerGroup = NioEventLoopGroup()
            try {
                val channelInit = ChannelInitServer(serverManager)
                val serverBootstrap = ServerBootstrap()
                serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel::class.java)//线程组设置为非阻塞
                    .childHandler(channelInit)
                    .option(ChannelOption.SO_BACKLOG, 128)//连接缓冲池的大小
                    .option(ChannelOption.TCP_NODELAY, true)
                    .option(ChannelOption.SO_KEEPALIVE, false)//设置长连接
                channelFuture = serverBootstrap.bind(Constant.SERVICE_POSR)
                channel = channelFuture?.channel()

                channelFuture!!.addListener { future: Future<in Void> ->
                    if (future.isSuccess) {
                        //服务启动成功
                        XLogUtil.d("********服务启动成功********")
                        MessageHandler.sendMessage(
                            MessageType.SERVER_START_SUCCESS,
                            "服务启动成功"
                        )
                    } else {
                        //服务启动失败
                        XLogUtil.e("********服务启动失败********")
                        MessageHandler.sendMessage(
                            MessageType.SERVER_START_FAILED,
                            "服务启动失败"
                        )
                    }
                }
                
            } catch (e: Exception) {
                e.printStackTrace()

                XLogUtil.e( "NettyServer 服务异常:"+e.message)
            } finally {

            }
        }
    }
2.2 client端主要代码
    /**
     * 启动客户端
     */
    fun start() {
        Executors.newSingleThreadScheduledExecutor().submit {
            XLogUtil.d("***********启动客户端***********")
            val group: EventLoopGroup = NioEventLoopGroup()
            try {
                val channelInit = ChannelInitClient(clientManager)
                val bootstrap = Bootstrap()
                bootstrap.group(group)
                    .channel(NioSocketChannel::class.java)
                    .remoteAddress(InetSocketAddress(address, port))
                    .handler(channelInit)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .option(ChannelOption.SO_KEEPALIVE, false)
                val channelFuture = bootstrap.connect().sync()
                channel = channelFuture.channel()
                channelFuture!!.addListener { future: Future<in Void> ->
                    if (future.isSuccess) {
                        //绑定成功
                        XLogUtil.d("***********客户端连接成功***********")
                        MessageHandler.sendMessage(
                            MessageType.CLIENT_CONNECT_SUCCESS,
                            "客户端连接成功"
                        )
                    } else {
                        //绑定失败
                        XLogUtil.d("***********客户端连接失败***********")
                        MessageHandler.sendMessage(
                            MessageType.CLIENT_CONNECT_FAILED,
                            "客户端连接失败"
                        )
                    }
                }

                channel!!.closeFuture().sync()
                XLogUtil.d("***********客户端关闭成功***********")
                MessageHandler.sendMessage(
                    MessageType.CLIENT_CLOSE_SUCCESS,
                    "客户端关闭成功"
                )
            } catch (e: Exception) {
                e.printStackTrace()
                MessageHandler.sendMessage(
                    MessageType.CLIENT_EXCEPTION,
                    "客户端异常:" + e.message
                )
                XLogUtil.e("NettyClient 客户端异常:" + e.message)
            } finally {
                try {
                    group.shutdownGracefully().sync()
                } catch (e: InterruptedException) {
                    e.printStackTrace()
                    MessageHandler.sendMessage(
                        MessageType.CLIENT_EXCEPTION,
                        "客户端异常2:" + e.message
                    )
                    XLogUtil.e("NettyClient 客户端异常2:" + e.message)
                }
            }
        }
    }
 2.3 Server端线程
ChannelInitServer.kt
服务端数据收发线程
class ChannelInitServer internal constructor(adapter: MyServerHandler) :
    ChannelInitializer<SocketChannel?>() {
    private val adapter: MyServerHandler

    init {
        this.adapter = adapter
    }

    override fun initChannel(ch: SocketChannel?) {
        try {
            val channelPipeline: ChannelPipeline = ch!!.pipeline()
            //添加心跳机制,例:每3000ms发送一次心跳
            //channelPipeline.addLast(IdleStateHandler(3000, 3000, 3000, TimeUnit.MILLISECONDS))
            //添加数据处理(接收、发送、心跳)
            //FrameCodec 中处理粘包分包问题
            channelPipeline.addLast(FrameCodec())
            channelPipeline.addLast(adapter)
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }
}
2.4 client 端线程
客户端数据收发线程
class ChannelInitClient internal constructor(adapter: MyClientHandler) :
    ChannelInitializer<Channel?>() {
    private val adapter: MyClientHandler

    init {
        this.adapter = adapter
    }


    override fun initChannel(ch: Channel?) {
        try {
            if (ch == null) {
                XLogUtil.e("ChannelInitClient Channel==null,initChannel fail")
            }
            val channelPipeline: ChannelPipeline = ch!!.pipeline()
            //添加心跳机制,例:每3000ms发送一次心跳
            // channelPipeline.addLast(IdleStateHandler(3000, 3000, 3000, TimeUnit.MILLISECONDS))
            //自定义编解码器,处理粘包分包问题
            channelPipeline.addLast(FrameCodec())
          //添加数据处理
            channelPipeline.addLast(adapter)
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }
}
2.5 在Activity文件中调用
package com.android.agent

import android.annotation.SuppressLint
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.os.Handler
import android.os.Message
import android.provider.Settings
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import com.alibaba.fastjson.JSON
import com.android.agent.netty.NettyClient
import com.android.agent.netty.NettyServer
import com.android.agent.netty.message.MessageSend
import com.android.agent.netty.message.SettingIp
import com.android.agent.utils.Constant
import com.android.agent.xlog.XLogUtil
import com.android.agent.R


class MainActivity : AppCompatActivity() {

    private var isTestServer = false
    private var isTestClient = false
    private var client: NettyClient? = null
    private var server: NettyServer? = null
    private var result = ""
    private var tvResult: TextView? = null



    @SuppressLint("MissingInflatedId")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            if (!Environment.isExternalStorageManager()) {
                val intent = Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
                startActivity(intent);
                return;
            }
        }

        XLogUtil.d(">>>>>>>>>>welcome to  AndroidGif")

        tvResult = findViewById<TextView>(R.id.tv_text)

        findViewById<Button>(R.id.btnTestClient).setOnClickListener {
            XLogUtil.d(">>>>>>>>>>btnTestClient OnClick 启动"+!isTestClient)
            if (!isTestClient) {
                result = "";
                testNettyClient();
            } else {
                stopNettyClient();
            }
            isTestClient = !isTestClient;
        }

        findViewById<Button>(R.id.btnTestServer).setOnClickListener {
            XLogUtil.d(">>>>>>>>>>btnTestServer OnClicks 启动:"+!isTestServer)
            if (!isTestServer) {
                result = "";
                testNettyServer();

            } else {
                stopNettyServer();
            }
            isTestServer = !isTestServer;
        }

        findViewById<Button>(R.id.btnClientSend).setOnClickListener {
            client?.apply {
                XLogUtil.d("btnClientSend data")
                var setIp= SettingIp("192.168.11.185","192.168.11.1","255.255.255.0","8.8.8.8")
                var sendMsg= MessageSend("xxxxxxxxxxxx",3000,JSON.toJSONString(setIp))
                sentData(JSON.toJSONString(sendMsg),0x30) //charset("GBK")
            }
        }
    }


    private fun testNettyClient() {
         client = NettyClient(Constant.SERVICE_IP, Constant.SERVICE_POSR)
//        client.addHeartBeat(object : HeartBeatListener {
//            override fun getHeartBeat(): ByteArray {
//                val data = "心跳"
//                try {
//                    client.sentData("测试数据".toByteArray(charset("GBK")))
//                    return data.toByteArray(charset("GBK"))
//                } catch (e: UnsupportedEncodingException) {
//                    e.printStackTrace()
//                }
//                return "".toByteArray()
//            }
//        })
        client!!.setHandler(handler)
        client!!.start()

    }

    private fun stopNettyClient() {
        client?.apply {
            stop()
        }
    }

    private fun testNettyServer() {
        server = NettyServer.getInstance()

        server?.apply {
//            addHeartBeat(object : HeartBeatListener {
//                override fun getHeartBeat(): ByteArray {
//                    val data = "心跳"
//                    try {
//                        sentData("123".toByteArray(Charsets.UTF_8))//GBK
//                        return data.toByteArray(Charsets.UTF_8)
//                    } catch (e: UnsupportedEncodingException) {
//                        e.printStackTrace()
//                    }
//                    return "".toByteArray()
//                }
//            })
            setHandler(handler)
            start()
        }
    }

    private fun stopNettyServer() {
        server?.apply {
            stop()
        }
    }

    @SuppressLint("HandlerLeak")
    private val handler: Handler = object : Handler() {
        override fun handleMessage(msg: Message) {
            XLogUtil.d("收到信息:::" + msg.obj.toString())
            result += "\r\n"
            result += msg.obj
            tvResult!!.text = "收到信息:$result"
        }
    }

}

对应的布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.android.agent.MainActivity">

    <Button
        android:id="@+id/btnTestServer"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="测试服务端"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="50dp"
        />

    <Button
        android:id="@+id/btnTestClient"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="100dp"
        android:text="测试客户端"
        />

    <Button
        android:id="@+id/btnClientSend"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="100dp"
        android:text="客户端发送数据"
        />

    <TextView
        android:id="@+id/tv_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="100dp"
        android:text="收到信息:"
        />


</LinearLayout>
2.6 数据编码解码器

需要根据协议去定义自己的编解码器,处理粘包丢包问题

完整代码下载地址:https://download.csdn.net/download/banzhuantuqiang/89705769

Netty自身有很多解码器,也可以结合Google的Protobuf(Google Protocol Buffers, 它是谷歌公司开源的一个序列化框架)使用,看项目需要决定是否需要集成。

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
要在Kotlin中开发Netty服务器,需要遵循以下步骤: 1. 添加Netty依赖项:在构建文件中添加以下依赖项: ```kotlin dependencies { implementation("io.netty:netty-all:4.1.42.Final") } ``` 2. 创建服务器:创建Netty服务器并设置端口号和处理程序,如下所示: ```kotlin val bossGroup = NioEventLoopGroup() val workerGroup = NioEventLoopGroup() val serverBootstrap = ServerBootstrap() serverBootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel::class.java) .childHandler(object : ChannelInitializer<SocketChannel>() { override fun initChannel(ch: SocketChannel) { ch.pipeline().addLast(ServerHandler()) } }) .option(ChannelOption.SO_BACKLOG, 128) .childOption(ChannelOption.SO_KEEPALIVE, true) val channelFuture = serverBootstrap.bind(8080).sync() channelFuture.channel().closeFuture().sync() ``` 3. 创建处理程序:编写处理程序来处理客户端请求和响应,如下所示: ```kotlin class ServerHandler : ChannelInboundHandlerAdapter() { override fun channelRead(ctx: ChannelHandlerContext?, msg: Any?) { val byteBuf = msg as ByteBuf val request = byteBuf.toString(Charset.defaultCharset()) println("Server received: $request") val response = "Hello from server" val responseBuf = Unpooled.copiedBuffer(response, Charset.defaultCharset()) ctx?.write(responseBuf) } override fun channelReadComplete(ctx: ChannelHandlerContext?) { ctx?.flush() } override fun exceptionCaught(ctx: ChannelHandlerContext?, cause: Throwable?) { cause?.printStackTrace() ctx?.close() } } ``` 4. 运行服务器:运行应用程序并访问服务器的端口号,以进行测试。可以使用curl或其他HTTP客户端来测试服务器,例如: ``` $ curl localhost:8080 Hello from server ``` 这是一个简单的例子,可以根据需要进行修改和扩展。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

粤M温同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值