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

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 客户端

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

模拟一个客户端的实现

val topLevelClass = object : Any() {}.javaClass.enclosingClass

val logger: Logger = LoggerFactory.getLogger(topLevelClass)

fun main() {

val worker = NioEventLoopGroup()

val bootstrap = Bootstrap()

bootstrap.group(worker).channel(NioSocketChannel::class.java)

.handler(object : ChannelInitializer() {

@Throws(Exception::class)

override fun initChannel(channel: SocketChannel) {

channel.pipeline().addLast(PacketCodecHandler)

channel.pipeline().addLast(ClientIdleHandler())

channel.pipeline().addLast(ClientLogin())

}

})

val future: ChannelFuture = bootstrap.connect(“127.0.0.1”, TCP_PORT).addListener(object : ChannelFutureListener {

@Throws(Exception::class)

override fun operationComplete(channelFuture: ChannelFuture) {

if (channelFuture.isSuccess()) {

logInfo(logger,“connect to server success!”)

} else {

logger.info("failed to connect the server! ")

System.exit(0)

}

}

})

try {

future.channel().closeFuture().sync()

logInfo(logger,“与服务端断开连接!”)

} catch (e: InterruptedException) {

e.printStackTrace()

}

}

其中,PacketCodecHandler 跟服务端使用的解析报文的 Handler 是一样的。

ClientIdleHandler:客户端实现心跳,每隔 30 秒发送一次心跳。

class ClientIdleHandler : IdleStateHandler(0, 0, HEART_BEAT_TIME) {

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

@Throws(Exception::class)

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

logInfo(logger,“发送心跳…”)

ctx.writeAndFlush(HeartBeatPacket())

}

companion object {

private const val HEART_BEAT_TIME = 30

}

}

ClientLogin:登录服务端的 Handler。

@ChannelHandler.Sharable

class ClientLogin: ChannelInboundHandlerAdapter() {

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

@Throws(Exception::class)

override fun channelActive(ctx: ChannelHandlerContext) {

val packet: LoginPacket = LoginPacket()

logInfo(logger,“packet = ${GsonUtils.toJson(packet)}”)

val byteBuf = PacketManager.encode(packet)

ctx.channel().writeAndFlush(byteBuf)

推荐学习资料

  • Android进阶学习全套手册

  • Android对标阿里P7学习视频

  • BAT TMD大厂Android高频面试题


《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!
override fun channelActive(ctx: ChannelHandlerContext) {

val packet: LoginPacket = LoginPacket()

logInfo(logger,“packet = ${GsonUtils.toJson(packet)}”)

val byteBuf = PacketManager.encode(packet)

ctx.channel().writeAndFlush(byteBuf)

推荐学习资料

  • Android进阶学习全套手册

    [外链图片转存中…(img-oZ6Hd6mg-1715027377199)]

  • Android对标阿里P7学习视频

    [外链图片转存中…(img-WK2nS5y8-1715027377200)]

  • BAT TMD大厂Android高频面试题

[外链图片转存中…(img-SFfKvesy-1715027377201)]
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

  • 19
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,下面是 Kotlin+Jetpack 实现登录接口请求的示例代码: 首先,在项目的 build.gradle 文件中添加以下依赖项: ```groovy dependencies { // Jetpack 相关依赖 implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.1" implementation "androidx.lifecycle:lifecycle-common-java8:2.3.1" // Retrofit 相关依赖 implementation "com.squareup.retrofit2:retrofit:2.9.0" implementation "com.squareup.retrofit2:converter-gson:2.9.0" } ``` 然后,创建一个 ViewModel 类,用于处理登录请求和响应: ```kotlin class LoginViewModel : ViewModel() { // 定义 LiveData 对象,用于保存登录结果 private val _loginResult = MutableLiveData<LoginResult>() val loginResult: LiveData<LoginResult> = _loginResult // 定义 Retrofit 对象,用于发起网络请求 private val retrofit = Retrofit.Builder() .baseUrl("https://example.com/") .addConverterFactory(GsonConverterFactory.create()) .build() // 定义登录接口 private val loginApi = retrofit.create(LoginApi::class.java) // 定义登录方法,接收用户名和密码作为参数 fun login(username: String, password: String) { viewModelScope.launch { try { // 发起登录请求 val response = loginApi.login(username, password) // 根据响应状态设置登录结果 if (response.isSuccessful) { _loginResult.value = LoginResult.Success } else { _loginResult.value = LoginResult.Failure(response.message()) } } catch (e: Exception) { _loginResult.value = LoginResult.Error(e) } } } } // 定义登录结果的 sealed class sealed class LoginResult { object Success : LoginResult() data class Failure(val message: String) : LoginResult() data class Error(val exception: Exception) : LoginResult() } ``` 其中,`LoginApi` 是一个 Retrofit 接口,用于定义登录接口: ```kotlin interface LoginApi { @FormUrlEncoded @POST("login") suspend fun login( @Field("username") username: String, @Field("password") password: String ): Response<Unit> } ``` 最后,在 Activity 或 Fragment 中使用 `LoginViewModel` 发起登录请求: ```kotlin class LoginActivity : AppCompatActivity() { private val viewModel by viewModels<LoginViewModel>() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_login) // 监听登录结果 viewModel.loginResult.observe(this, { result -> when (result) { is LoginResult.Success -> { // 登录成功,跳转到主界面 startActivity(Intent(this, MainActivity::class.java)) finish() } is LoginResult.Failure -> { // 登录失败,弹出提示框 Toast.makeText(this, result.message, Toast.LENGTH_SHORT).show() } is LoginResult.Error -> { // 登录出错,打印日志 Log.e("LoginActivity", "Login error", result.exception) } } }) // 点击登录按钮时发起登录请求 loginButton.setOnClickListener { val username = usernameEditText.text.toString() val password = passwordEditText.text.toString() viewModel.login(username, password) } } } ``` 这样,我们就完成了 Kotlin+Jetpack 实现登录接口请求的示例代码。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值