基于 Kotlin + Netty 实现一个简单的 TCP 自定义协议

根据上述所设计的网络协议,定义一个抽象类 Packet:

abstract class Packet {

var magic:Int? = MAGIC_NUMBER // 魔数

var version:Byte = 1 // 版本号,当前协议的版本号为 1

abstract val serializeMethod:Byte // 序列化方式

abstract val command:Byte // Watcher 跟 App 相互通讯的指令

}

有多少个指令就需要定义多少个 Packet,下面以心跳的 Packet 为例,定义一个 HeartBeatPacket:

data class HeartBeatPacket(var msg:String = “ping”,

override val serializeMethod: Byte = Serialize.JSON,

override val command: Byte = Commands.HEART_BEAT) : Packet() {

}

HeartBeatPacket 是由 TCP 客户端发起,由 TCP 服务端接收并返回给客户端。

每个 Packet 类都包含了该 Packet 所使用的序列化方式。

/**

  • 序列化方式的常量列表

*/

interface Serialize {

companion object {

const val JSON: Byte = 0

}

}

每个 Packet 也包含了其对应的 command。下面是 Commands 是指令集,支持256个指令。

/**

  • 指令集,支持从 -128 到 127 总共 256 个指令

*/

interface Commands {

companion object {

/**

  • 心跳包

*/

const val HEART_BEAT: Byte = 0

/**

  • 登录(App 需要告诉 Watcher :cameraPosition 的位置)

*/

const val LOGIN: Byte = 1

}

}

由于使用自定义的协议,必须要有对报文的 encode、decode,PacketManager 负责这些事情。

encode 时按照协议的结构进行组装报文,同理 decode 是其逆向的过程。

/**

  • 报文的管理类,对报文进行 encode、decode

*/

object PacketManager {

fun encode(packet: Packet):ByteBuf = encode(ByteBufAllocator.DEFAULT, packet)

fun encode(alloc:ByteBufAllocator, packet: Packet) = encode(alloc.ioBuffer(), packet)

fun encode(buf: ByteBuf, packet: Packet): ByteBuf {

val serializer = SerializerFactory.getSerializer(packet.serializeMethod)

val bytes: ByteArray = serializer.serialize(packet)

//组装报文:魔数(4字节)+ 版本号(1字节)+ 序列化方式(1字节)+ 指令(1字节)+ 数据长度(4字节)+ 数据(N字节)

buf.writeInt(MAGIC_NUMBER)

buf.writeByte(packet.version.toInt())

buf.writeByte(packet.serializeMethod.toInt())

buf.writeByte(packet.command.toInt())

buf.writeInt(bytes.size)

buf.writeBytes(bytes)

return buf

}

fun decode(buf:ByteBuf): Packet {

buf.skipBytes(4) // 魔数由单独的 Handler 进行校验

buf.skipBytes(1)

val serializationMethod = buf.readByte()

val serializer = SerializerFactory.getSerializer(serializationMethod)

val command = buf.readByte()

val clazz = PacketFactory.getPacket(command)

val length = buf.readInt() // 数据的长度

val bytes = ByteArray(length) // 定义需要读取的字符数组

buf.readBytes(bytes)

return serializer.deserialize(clazz, bytes)

}

}

三. TCP 服务端

==========================================================================

启动 TCP 服务的方法

fun execute() {

boss = NioEventLoopGroup()

worker = NioEventLoopGroup()

val bootstrap = ServerBootstrap()

bootstrap.group(boss, worker).channel(NioServerSocketChannel::class.java)

.option(ChannelOption.SO_BACKLOG, 100)

.childOption(ChannelOption.SO_KEEPALIVE, true)

.childOption(ChannelOption.SO_REUSEADDR, true)

.childOption(ChannelOption.TCP_NODELAY, true)

.childHandler(object : ChannelInitializer() {

@Throws(Exception::class)

override fun initChannel(nioSocketChannel: NioSocketChannel) {

val pipeline = nioSocketChannel.pipeline()

pipeline.addLast(ServerIdleHandler())

pipeline.addLast(MagicNumValidator())

pipeline.addLast(PacketCodecHandler)

pipeline.addLast(HeartBeatHandler)

pipeline.addLast(ResponseHandler)

}

})

val future: ChannelFuture = bootstrap.bind(TCP_PORT)

future.addListener(object : ChannelFutureListener {

@Throws(Exception::class)

override fun operationComplete(channelFuture: ChannelFuture) {

if (channelFuture.isSuccess) {

logInfo(logger, “TCP Server is starting…”)

} else {

logError(logger,channelFuture.cause(),“TCP Server failed”)

}

}

})

}

其中,ServerIdleHandler: 表示 5 分钟内没有收到心跳,则断开连接。

class ServerIdleHandler : IdleStateHandler(0, 0, HERT_BEAT_TIME) {

private val logger: Logger = LoggerFactory.getLogger(ServerIdleHandler::class.java)

@Throws(Exception::class)

override fun channelIdle(ctx: ChannelHandlerContext, evt: IdleStateEvent) {

logInfo(logger) {

ctx.channel().close()

“$HERT_BEAT_TIME 秒内没有收到心跳,则断开连接”

}

}

companion object {

private const val HERT_BEAT_TIME = 300

}

}

MagicNumValidator:用于 TCP 报文的魔数校验。

class MagicNumValidator : LengthFieldBasedFrameDecoder(Int.MAX_VALUE, LENGTH_FIELD_OFFSET, LENGTH_FIELD_LENGTH) {

private val logger: Logger = LoggerFactory.getLogger(this.javaClass)

@Throws(Exception::class)

override fun decode(ctx: ChannelHandlerContext, in: ByteBuf): Any? {

if (in.getInt(in.readerIndex()) !== MAGIC_NUMBER) { // 魔数校验不通过,则关闭连接

logInfo(logger,“魔数校验失败”)

ctx.channel().close()

return null

}

return super.decode(ctx, in)

}

companion object {

private const val LENGTH_FIELD_OFFSET = 7

private const val LENGTH_FIELD_LENGTH = 4

}

}

PacketCodecHandler: 解析报文的 Handler。

PacketCodecHandler 继承自 ByteToMessageCodec ,它是用来处理 byte-to-message 和message-to-byte,便于解码字节消息成 POJO 或编码 POJO 消息成字节。

@ChannelHandler.Sharable

object PacketCodecHandler : MessageToMessageCodec<ByteBuf, Packet>() {

override fun encode(ctx: ChannelHandlerContext, msg: Packet, list: MutableList) {

val byteBuf = ctx.channel().alloc().ioBuffer()

PacketManager.encode(byteBuf, msg)

list.add(byteBuf)

}

override fun decode(ctx: ChannelHandlerContext, msg: ByteBuf, list: MutableList) {

list.add(PacketManager.decode(msg));

}

}

HeartBeatHandler:心跳的 Handler,接收 TCP 客户端发来的"ping",然后给客户端返回"pong"。

@ChannelHandler.Sharable

object HeartBeatHandler : SimpleChannelInboundHandler(){

private val logger: Logger = LoggerFactory.getLogger(this.javaClass)

override fun channelRead0(ctx: ChannelHandlerContext, msg: HeartBeatPacket) {

logInfo(logger,“收到心跳包:${GsonUtils.toJson(msg)}”)

msg.msg = “pong” // 返回 pong 给到客户端

ctx.writeAndFlush(msg)

}

}

ResponseHandler:通用的处理接收 TCP 客户端发来指令的 Handler,可以根据对应的指令去查询对应的 Handler 并处理其命令。

object ResponseHandler: SimpleChannelInboundHandler() {

private val logger: Logger = LoggerFactory.getLogger(this.javaClass)

private val handlerMap: ConcurrentHashMap<Byte, SimpleChannelInboundHandler> = ConcurrentHashMap()

init {

handlerMap[LOGIN] = LoginHandler

handlerMap[ERROR] = ErrorHandler

}

override fun channelRead0(ctx: ChannelHandlerContext, msg: Packet) {

logInfo(logger,“收到客户端的指令: ${msg.command}”)

val handler: SimpleChannelInboundHandler? = handlerMap[msg.command]

handler?.let {

logInfo(logger,“找到响应指令的 Handler: ${it.javaClass.simpleName}”)

it.channelRead(ctx, msg)

} ?: logInfo(logger,“未找到响应指令的 Handler”)

}

@Throws(Exception::class)

override fun channelInactive(ctx: ChannelHandlerContext) {

val insocket = ctx.channel().remoteAddress() as InetSocketAddress

val clientIP = insocket.address.hostAddress

val clientPort = insocket.port

logError(logger,“客户端掉线: $clientIP : $clientPort”)

super.channelInactive(ctx)

}

}

四. TCP 客户端

先自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以扫码领取!!!!

最后

目前已经更新的部分资料:



《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可免费领取!

**

[外链图片转存中…(img-Qmoh7p3V-1711290403108)]

[外链图片转存中…(img-96n0rxia-1711290403109)]

[外链图片转存中…(img-nDXt7iNV-1711290403109)]

[外链图片转存中…(img-JXZyehkI-1711290403109)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以扫码领取!!!!

最后

目前已经更新的部分资料:

[外链图片转存中…(img-oxnimiWQ-1711290403109)]
[外链图片转存中…(img-HlPddreu-1711290403110)]
[外链图片转存中…(img-nwHJ12V1-1711290403110)]

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可免费领取!

  • 19
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值