摘要
背景:心跳通常是指客户端或服务器定期发送一个小型的、空的消息以保持连接的活动状态。它用于检测连接是否仍然有效,并防止连接由于长时间没有活动而被关闭。
技术原理:App定时发消息给服务器,服务器回消息表示连接依旧有效
日志:
实现方法
1.服务端
建立WebSocket 服务器应用程序首先,添加以下依赖项到项目的
build.gradle 文件中:
dependencies {
implementation ("io.ktor:ktor-server-netty:1.6.3")
implementation ("io.ktor:ktor-websockets:1.6.3")
implementation ("ch.qos.logback:logback-classic:1.2.6")
}
使用 Ktor 框架创建一个嵌入式 Netty 服务器。它监听本地的 8080 端口,并在
/ws 路径上处理 WebSocket 连接。
import io.ktor.application.*
import io.ktor.http.cio.websocket.*
import io.ktor.routing.*
import io.ktor.server.engine.embeddedServer
import io.ktor.server.netty.Netty
import io.ktor.websocket.WebSockets
import io.ktor.websocket.webSocket
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import java.util.concurrent.atomic.AtomicInteger
fun main() {
embeddedServer(Netty, port = 8080, module = Application::module).start(wait = true)
}
fun Application.module() {
install(WebSockets)
routing {
val connections = AtomicInteger(0)
val clients = mutableListOf<WebSocketSession>()
webSocket("/ws") {
connections.incrementAndGet()
println("Client connected. Total connections: ${connections.get()}")
clients.add(this)
try {
for (frame in incoming) {
when (frame) {
is Frame.Text -> {
val receivedText = frame.readText()
println("Received message: $receivedText")
send("Server received: $receivedText")
// 在收到消息后,主动向所有客户端发送一条消息
GlobalScope.launch {
clients.forEach { client ->
client.send("Server broadcast: $receivedText")
}
}
}
else -> {}
}
}
} catch (e: Exception) {
println("WebSocket error: ${e.message}")
} finally {
clients.remove(this)
connections.decrementAndGet()
println("Client disconnected. Total connections: ${connections.get()}")
}
}
}
}
运行main函数即可,等App客户端连接即可
2.客户端
使用 Kotlin 编写的长连接示例代码,用于在 Android 应用程序中建立和保持长连接首先,添加以下依赖项到项目的
build.gradle 文件中:
implementation("org.java-websocket:Java-WebSocket:1.5.1")
创建一个
WebSocketClient 类,并实现相应的方法
package com.fadi.power.net.service
import android.util.Log
import com.fadi.power.net.config.Config
import com.fadi.power.net.utils.TimeUtils
import org.java_websocket.client.WebSocketClient
import org.java_websocket.drafts.Draft
import org.java_websocket.handshake.ServerHandshake
import java.net.URI
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.TimeUnit
class WebSocketClient(serverUri: URI, draft: Draft) : WebSocketClient(serverUri, draft) {
companion object {
const val HEARTBEAT_MESSAGE = "Heartbeat"
}
private var mEnableHeartbeat = false
private var heartbeatExecutor: ScheduledExecutorService? = null
override fun onOpen(handshakedata: ServerHandshake?) {
// 连接成功,发送数据或执行其他操作
send("Hello, Server!")
Log.d(Config.TAG, "WebSocketClient onOpen: Hello, Server!")
// 【不使用自带的心跳,使用自建Alarm定时】启动定时任务发送心跳消息
// startHeartbeat()
}
override fun onClose(code: Int, reason: String?, remote: Boolean) {
// 【不使用自带的心跳,使用自建Alarm定时】连接关闭,执行相应的处理逻辑
// 停止心跳任务
// stopHeartbeat()
}
override fun onMessage(message: String?) {
// 接收到服务器发送的消息,执行相应的处理逻辑
message?.let {
Log.d(Config.TAG, "WebSocketClient onMessage: Received message: $it")
}
}
override fun onError(ex: Exception?) {
// 连接出现错误,执行相应的错误处理逻辑
ex?.let {
Log.d(Config.TAG, "WebSocketClient onError: WebSocket error: ${it.message}")
}
}
private fun startHeartbeat() {
heartbeatExecutor = Executors.newSingleThreadScheduledExecutor()
heartbeatExecutor?.scheduleAtFixedRate(
{ sendHeartbeat() },
0, // 初始延迟为0
1, // 间隔为1分钟
TimeUnit.MINUTES
)
}
private fun stopHeartbeat() {
heartbeatExecutor?.shutdownNow()
heartbeatExecutor = null
}
fun sendHeartbeat() {
if (!mEnableHeartbeat) {
return
}
val timestamp = System.currentTimeMillis()
val heartbeatMessage = "$HEARTBEAT_MESSAGE ${TimeUtils.timeStamp2Date(timestamp)}"
send(heartbeatMessage)
Log.d(Config.TAG, "WebSocketClient sendHeartbeat: Sending heartbeat message: $heartbeatMessage")
}
fun setEnableHeartbeat(enable: Boolean) {
mEnableHeartbeat = enable
}
fun isHeartbeat(): Boolean {
return mEnableHeartbeat
}
}
应用启动时,它将连接到指定的 WebSocket 服务器,并在连接成功后发送一条消息
// 服务器ip: 把电脑当做服务器, ipconfig获取
const val SERVER_IP = "10.170.16.162"
const val SERVER_DOWNLOAD_PORT = "8000"
const val DOWN_LOAD_FILE_NAME = "test.apk"
//http://10.170.16.162:8000/test.apk"
const val DOWN_LOAD_URL = "http:" + SERVER_IP + ":" + SERVER_DOWNLOAD_PORT + "/" + DOWN_LOAD_FILE_NAME
// 加密 wss://192.168.0.100:8080/ws
// 非加密 ws://10.170.16.162:8080/ws
const val SERVER_PUSH_PORT = "8080"
const val PUSH_URL = "ws://" + SERVER_IP + ":" + SERVER_PUSH_PORT + "/ws"
初始化
val serverUri = URI(Config.PUSH_URL)
val draft = Draft_6455() // 使用 WebSocket 协议版本 13
mWebSocketClient = WebSocketClient(serverUri, draft)
mWebSocketClient.connect()
private fun handleHeadBeatEvent(scene: String, checked: Boolean) {
// 心跳场景的处理逻辑
if (checked == true) {
// 开启心跳
Log.d(Config.TAG, "开启心跳")
enableOneMinuteAlarm()
mWebSocketClient.setEnableHeartbeat(true)
mAlarmNetWorkNotification.showNotification(scene)
} else {
// 关闭心跳
Log.d(Config.TAG, "关闭心跳")
disableOneMinuteAlarm()
mWebSocketClient.setEnableHeartbeat(false)
mAlarmNetWorkNotification.hideNotification(scene)
}
}
3.运行结果
支持设定心跳间隔,调试阶段定了了1分钟一次和服务端的心跳长连接
3.1 客户端打印
3.2 服务端打印