Kotlin Socket通信实战:从基础连接到JSON数据交换
1. Kotlin Socket通信基础入门第一次接触Socket编程时我完全被那些晦涩的网络术语吓到了。直到用Kotlin写出了第一个能跑通的Socket程序才发现原来网络通信可以这么简单。Socket本质上就是两个程序之间的电话线一个程序拨号客户端另一个程序接听服务端然后双方就能愉快地聊天了。在Kotlin中使用Socket API比Java简洁得多。先来看最基础的TCP连接建立代码// 客户端连接代码 fun createSocketConnection(host: String, port: Int): Socket { val socket Socket() socket.soTimeout 10000 // 设置10秒超时 socket.connect(InetSocketAddress(host, port), 5000) println(成功连接到$host:$port) return socket }这里有几个关键点需要注意超时设置soTimeout决定读取操作的等待时间connectTimeout决定连接建立的等待时间异常处理实际项目中要捕获SocketTimeoutException和IOException资源释放记得在finally块中关闭Socket与HTTP短连接不同Socket连接一旦建立就会保持直到主动断开。这种长连接特性特别适合需要频繁通信的场景比如即时通讯、游戏服务等。我曾经做过一个聊天应用就是靠Socket连接维持实时消息推送的。2. 构建完整的客户端-服务端通信2.1 服务端搭建服务端需要先竖起耳朵监听端口// 服务端监听代码 fun startServer(port: Int) { val serverSocket ServerSocket(port) println(服务端已启动监听端口$port) while (true) { val clientSocket serverSocket.accept() // 阻塞等待客户端连接 Thread { handleClient(clientSocket) }.start() } }这里有个重要细节服务端必须使用多线程因为accept()是阻塞调用如果不开新线程处理每个客户端连接服务端就无法同时服务多个客户端。我在第一次写服务端时就犯了这个错误结果第二个客户端怎么都连不上。2.2 消息收发处理消息传输的核心是操作输入输出流// 消息发送 fun sendMessage(socket: Socket, message: String) { socket.getOutputStream().apply { write(message.toByteArray()) flush() } } // 消息接收 fun receiveMessage(socket: Socket): String { val buffer ByteArray(1024) val length socket.getInputStream().read(buffer) return String(buffer, 0, length) }实际项目中要注意字符编码建议统一使用UTF-8缓冲区大小根据消息长度动态调整流关闭顺序先开的后关3. JSON数据交换实战3.1 为什么要用JSON早期我做项目时直接发送字符串很快就遇到了问题数据结构不明确难以扩展字段类型安全无法保证改用JSON后这些问题都迎刃而解。Kotlin中使用Gson或kotlinx.serialization都能方便地处理JSON// 使用kotlinx.serialization Serializable data class LoginRequest(val username: String, val password: String) fun sendLoginRequest(socket: Socket) { val request LoginRequest(admin, 123456) val json Json.encodeToString(request) sendMessage(socket, json) }3.2 完整登录流程实现让我们实现一个完整的用户登录流程// 客户端登录代码 fun login(host: String, port: Int) { val socket createSocketConnection(host, port) try { // 构建登录请求 val request mapOf( action to login, username to user123, password to pass456 ) val jsonRequest Json.encodeToString(request) // 发送请求 sendMessage(socket, jsonRequest) // 接收响应 val response receiveMessage(socket) val jsonResponse Json.decodeFromStringMapString, Any(response) when (jsonResponse[status]) { success - println(登录成功) failed - println(登录失败: ${jsonResponse[reason]}) } } finally { socket.close() } }服务端对应处理fun handleClient(socket: Socket) { val request Json.decodeFromStringMapString, String( receiveMessage(socket) ) val response when (request[action]) { login - { if (checkLogin(request[username], request[password])) { mapOf(status to success) } else { mapOf(status to failed, reason to 用户名或密码错误) } } else - mapOf(status to error, reason to 未知操作) } sendMessage(socket, Json.encodeToString(response)) socket.close() }4. 实战中的常见问题与优化4.1 粘包问题处理早期版本我的消息经常粘在一起后来发现需要定义消息边界。常用解决方案固定长度每条消息固定字节数分隔符用特殊字符如\n分隔长度前缀先发送消息长度我推荐第三种方式// 改进版消息发送 fun sendPacket(socket: Socket, message: String) { val data message.toByteArray() val length data.size val output socket.getOutputStream() // 先发送长度(4字节) output.write( byteArrayOf( (length shr 24).toByte(), (length shr 16).toByte(), (length shr 8).toByte(), length.toByte() ) ) // 再发送数据 output.write(data) output.flush() }4.2 心跳机制实现长连接需要心跳来检测连接状态// 心跳线程 class HeartbeatThread(private val socket: Socket) : Thread() { override fun run() { while (true) { try { sendMessage(socket, HEARTBEAT) Thread.sleep(30000) // 30秒一次 } catch (e: Exception) { // 连接已断开 socket.close() break } } } }4.3 性能优化技巧经过多次压测我总结出几个优化点对象复用重用ByteBuffer避免频繁内存分配批量操作合并小数据包发送零拷贝使用FileChannel.transferTo发送文件连接池管理Socket连接复用// 连接池示例 class SocketPool(private val host: String, private val port: Int) { private val pool mutableListOfSocket() fun getSocket(): Socket { return if (pool.isNotEmpty()) { pool.removeAt(0).also { if (!it.isConnected) it.connect(InetSocketAddress(host, port)) } } else { Socket(host, port) } } fun releaseSocket(socket: Socket) { if (socket.isConnected) { pool.add(socket) } } }5. 安全注意事项5.1 数据传输安全明文传输密码是大忌我吃过这个亏后来都改用SSL加密// SSL Socket连接 fun createSSLSocket(host: String, port: Int): SSLSocket { val context SSLContext.getInstance(TLS) context.init(null, null, null) val factory context.socketFactory return factory.createSocket(host, port) as SSLSocket }5.2 输入验证服务端必须验证所有输入fun handleRequest(request: MapString, Any): MapString, Any { // 证必需字段 if (request[action] !in listOf(login, register)) { return errorResponse(非法操作) } // 验证参数类型 if (request[username] !is String || (request[username] as String).length 20) { return errorResponse(无效用户名) } // 业务处理... }5.3 资源管理一定要确保资源释放fun safeClose(vararg closeables: AutoCloseable) { closeables.forEach { try { it?.close() } catch (e: Exception) { // 记录日志 } } }在实际项目中我习惯用use函数自动管理资源Socket(host, port).use { socket - // 使用socket // 不需要手动closeuse块结束会自动调用 }