# uniapp-TcpClientBytesPlugin **Repository Path**: wokaixin/uniapp-tcp-client-bytes-plugin ## Basic Information - **Project Name**: uniapp-TcpClientBytesPlugin - **Description**: uniapp 安卓原生tcp协议插件,支持byte流转base64字符串原生分片传输, - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2025-06-11 - **Last Updated**: 2025-12-15 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## tcp协议通讯插件 ```agsl package com.sciyichen.tcpclientbytes import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import org.junit.Assert.* import org.junit.Test import org.junit.runner.RunWith import java.io.File import java.io.FileOutputStream import java.nio.charset.StandardCharsets import java.util.Base64 import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit import java.util.function.Consumer /** * Instrumented test, which will execute on an Android device. * * See [testing documentation](http://d.android.com/tools/testing). */ @RunWith(AndroidJUnit4::class) class ExampleInstrumentedTest { @Test fun useAppContext() { // Context of the app under test. val appContext = InstrumentationRegistry.getInstrumentation().targetContext assertEquals("com.sciyichen.tcpclientbytes.test", appContext.packageName) } // @Test // fun tcpCli() { // val options: MutableMap = mutableMapOf() // options["ip"] = "60.10.207.222" // options["port"] = 9001 // val ip: String = options["ip"] as String // val port: Int = options["port"] as Int // val packetOperations = TcpClientBytesPluginImpl() //// val jsonstr = "{\"request\":\"QueryStatus\"}" // val jsonstr = "{\"request\": \"GetDistortionImage\", \"type\": \"PNG\"}" // val checksum: Int = packetOperations.crcCcitt(jsonstr.toByteArray(), 0) // val byteArray: ByteArray = packetOperations.buildPacketFromJson(jsonstr, checksum) //// val base64String = Base64.getEncoder().encodeToString(byteArray) // val base64String: ByteArray = Base64.encode(byteArray, Base64.NO_WRAP) // val list: List = packetOperations.tcpClientByte(ip, port, base64String, 1024*2000,6000) // println(base64String) // val byteArray2: ByteArray = packetOperations.tcpClientBytes(ip, port, base64String, 1024*2000,6000) // val list1: List = packetOperations.getAJsonPacketFromSocket(byteArray2, 1024*2000) // println(base64String) // } @Test fun tcpCli() { val options: MutableMap = mutableMapOf() options["ip"] = "60.10.207.222" options["port"] = 9001 val ip: String = options["ip"] as String val port: Int = options["port"] as Int val packetOperations = TcpClientBytesPluginImpl() val jsonstr = "{\"request\":\"QueryStatus\"}" // val jsonstr = "{\"request\": \"GetDistortionImage\", \"type\": \"PNG\"}" val checksum: Int = packetOperations.crcCcitt(jsonstr.toByteArray(), 0) val byteArray: ByteArray = packetOperations.buildPacketFromJson(jsonstr, checksum) val base64String = Base64.getEncoder().encodeToString(byteArray) val list: List = packetOperations.tcpClientByte(ip, port, base64String, 1024,6000) println(base64String) val byteArray2: ByteArray = packetOperations.tcpClientBytes(ip, port, base64String, 1024,6000) val list1: List = packetOperations.getAJsonPacketFromSocket(byteArray2, 1024) println(base64String) } ``` tcp 协议 ``` @Test @Throws(Exception::class) fun testTcpByteStreamCommunication() { val plugin = TcpClientBytesPluginImpl() val host = "192.168.1.228" // val host = "60.10.207.222" val port = 9001 val timeout = 30000 // 延长超时时间到30秒 val jsonstr = "{\"request\":\"QueryStatus\"}" // 构建请求数据包 // val jsonstr = "{\"request\": \"GetDistortionImage\", \"type\": \"PNG\"}" val checksum = plugin.crcCcitt(jsonstr.toByteArray(), 0) val testBytes = plugin.buildPacketFromJson(jsonstr, checksum) // 连接到服务器 val connectLatch = CountDownLatch(1) plugin.connect( host, port,20000, { result -> println("连接成功: $result") connectLatch.countDown() }, { error -> println("连接失败: $error") connectLatch.countDown() } ) // 等待连接完成 assertTrue(connectLatch.await(10, TimeUnit.SECONDS)) // 设置数据回调 - 合并处理逻辑 val receiveLatch = CountDownLatch(1) val receivedData = arrayOfNulls(1) plugin.setDataByteArrayCallback { data -> receivedData[0] = data println("接收到数据长度: ${data.size}") // 尝试解析数据包 - 根据协议判断是否接收完整 val packets = plugin.getAJsonPacketFromSocket(data, data.size) if (packets.isNotEmpty()) { println("解析到数据包: ${packets[0]}") // 对于图像请求,判断是否包含结束标记或足够的数据长度 if (data.size > 1024) { // 假设图像数据至少1KB receiveLatch.countDown() } } } // 发送字节流数据 val sendLatch = CountDownLatch(1) // plugin.sendBytes( // testBytes, // { result -> // println("发送成功: $result") // sendLatch.countDown() // }, // { error -> // println("发送失败: $error") // sendLatch.countDown() // } // ) // val base64String = Base64.getEncoder().encodeToString(testBytes) plugin.sendBase64Str( base64String, { result -> println("发送成功: $result") sendLatch.countDown() }, { error -> println("发送失败: $error") sendLatch.countDown() } ) // 等待发送完成 assertTrue(sendLatch.await(10, TimeUnit.SECONDS)) // 等待接收响应 - 延长超时时间 val received =receiveLatch.await(30, TimeUnit.SECONDS) // 等待接收完成 // 验证测试结果 assertTrue("接收超时", received) assertNotNull("未收到响应", receivedData[0]) assertTrue("响应数据为空", receivedData[0]!!.size > 0) try { // 尝试解析为图像数据 val responseBytes = receivedData[0]!! // 验证是否为PNG图像(PNG文件以89 50 4E 47开头) if (responseBytes.size >= 4 && responseBytes[0] == 0x89.toByte() && responseBytes[1] == 0x50.toByte() && responseBytes[2] == 0x4E.toByte() && responseBytes[3] == 0x47.toByte()) { println("接收到有效的PNG图像数据,大小: ${responseBytes.size} 字节") // 保存图像用于调试 val outputFile = File("received_image.png") FileOutputStream(outputFile).use { fos -> fos.write(responseBytes) } println("图像已保存到: ${outputFile.absolutePath}") } else { // 尝试作为JSON解析 val responseText = String(responseBytes, StandardCharsets.UTF_8) println("解码后文本: $responseText") // 检查是否包含错误信息 if (responseText.contains("error") || responseText.contains("失败")) { fail("服务器返回错误: $responseText") } } } catch (e: Exception) { e.printStackTrace() fail("处理响应数据时出错: ${e.message}") } finally { // 关闭连接 val closeLatch = CountDownLatch(1) plugin.close( { result -> println("关闭成功: $result") closeLatch.countDown() }, { error -> println("关闭失败: $error") closeLatch.countDown() } ) // 等待关闭完成 assertTrue(closeLatch.await(10, TimeUnit.SECONDS)) } } // 最小测试用例 @Test fun testConnectionStayAlive() { val plugin = TcpClientBytesPluginImpl() // val host = "192.168.1.228" val host = "60.10.207.222" val port = 9001 val timeout = 30000 // 延长超时时间到30秒 // 构建请求数据包 val jsonstr = "{\"request\": \"GetDistortionImage\", \"type\": \"PNG\"}" val checksum = plugin.crcCcitt(jsonstr.toByteArray(), 0) val testBytes = plugin.buildPacketFromJson(jsonstr, checksum) // plugin.connect( host, port, // { println("连接成功") }, // { println("连接失败: $it") } // ) // // 连接到服务器 val connectLatch = CountDownLatch(1) plugin.connect( host, port,30000, { result -> println("连接成功: $result") connectLatch.countDown() }, { error -> println("连接失败: $error") connectLatch.countDown() } ) // 等待连接完成 assertTrue(connectLatch.await(10, TimeUnit.SECONDS)) // // 设置数据回调 - 合并处理逻辑 val receiveLatch = CountDownLatch(1) val receivedData = arrayOfNulls(1) plugin.setDataByteArrayCallback { data -> receivedData[0] = data println("接收到数据长度: ${data.size}") // 尝试解析数据包 - 根据协议判断是否接收完整 val packets = plugin.getAJsonPacketFromSocket(data, data.size) if (packets.isNotEmpty()) { println("解析到数据包: ${packets[0]}") // 对于图像请求,判断是否包含结束标记或足够的数据长度 if (data.size > 1024) { // 假设图像数据至少1KB receiveLatch.countDown() } } } plugin.setStringCallback({ data -> println("接收到数据长度: ${data}") }) // ``` // 发送字节流数据 ``` val sendLatch = CountDownLatch(1) plugin.sendBytes( testBytes, { result -> println("发送成功: $result") sendLatch.countDown() }, { error -> println("发送失败: $error") sendLatch.countDown() } ) // 等待发送完成 assertTrue(sendLatch.await(10, TimeUnit.SECONDS)) // 等待10秒,观察接收线程是否保持运行 Thread.sleep(20000) // 关闭连接 val closeLatch = CountDownLatch(1) plugin.close( { result -> println("关闭成功: $result") closeLatch.countDown() }, { error -> println("关闭失败: $error") closeLatch.countDown() } ) } ``` // 最小测试用例 ``` @Test fun testConnectionStayAliveBase64Str() { val plugin = TcpClientBytesPluginImpl() val host = "192.168.1.228" // val host = "60.10.207.222" val port = 9001 val timeout = 10000 // 延长超时时间到30秒 // 构建请求数据包 // val jsonstr = "{\"request\": \"GetDistortionImage\", \"type\": \"PNG\"}" val jsonstr = "{\"request\":\"QueryStatus\"}" val checksum = plugin.crcCcitt(jsonstr.toByteArray(), 0) val testBytes = plugin.buildPacketFromJson(jsonstr, checksum) // plugin.connect( host, port, // { println("连接成功") }, // { println("连接失败: $it") } // ) // // 连接到服务器 val connectLatch = CountDownLatch(1) plugin.connect( host, port,timeout, { result -> println("连接成功: $result") connectLatch.countDown() }, { error -> println("连接失败: $error") connectLatch.countDown() } ) // 等待连接完成 assertTrue(connectLatch.await(10, TimeUnit.SECONDS)) // plugin.setDataByteArrayBase64StrCallback( { data -> println("接收到数据byte[] 转base64的字符串: ${data}") }) plugin.setStringCallback({ data -> println("接收到数据字符串utf8: ${data}") }) // // 发送字节流数据 base64str val sendLatch = CountDownLatch(1) val base64String = Base64.getEncoder().encodeToString(testBytes) plugin.sendBase64Str( base64String, { result -> println("发送成功: $result") sendLatch.countDown() }, { error -> println("发送失败: $error") sendLatch.countDown() } ) // 等待发送完成 assertTrue(sendLatch.await(10, TimeUnit.SECONDS)) // 等待10秒,观察接收线程是否保持运行 Thread.sleep(30000) // 关闭连接 val closeLatch = CountDownLatch(1) plugin.close( { result -> println("关闭成功: $result") closeLatch.countDown() }, { error -> println("关闭失败: $error") closeLatch.countDown() } ) } } ``` uniapp vue ```agsl ``` tcp.js ```agsl import tcpClient from "./tcpClient"; export default { dataCallback:null, tcpPlugin: null, connected: false, reconnecting: false, reconnectTimer: null, heartbeatTimer: null, maxReconnectAttempts: 5, reconnectAttempts: 0, lastDataTime: 0, lastHeartbeatTime: 0, heartbeatSequence: 1, pageInstanceId: null, isPageActive: false, pendingMessages: [], dataBuffer: "", dataHandlers: [], currentIp: "", currentPort: 0, // 初始化TCP插件 init(pageInstanceId) { if (this.tcpPlugin && this.pageInstanceId === pageInstanceId) return; this.pageInstanceId = pageInstanceId; this.isPageActive = true; this.pendingMessages = []; this.dataBuffer = ""; this.dataHandlers = []; this.tcpPlugin = uni.requireNativePlugin('tcpclientbytes'); this.tcpPlugin.initialize(res => { console.log(res); }, err => { console.log(err); }); // 使用箭头函数保存回调,避免this指向问题 this.dataCallback = (data) => { this.onDataReceived(data); // 只注册一次回调函数 this.tcpPlugin.setDataByteArrayBase64StrCallback(this.dataCallback); }; // 只注册一次回调函数 this.tcpPlugin.setDataByteArrayBase64StrCallback(this.dataCallback); console.log('TCP客户端初始化完成,页面实例ID: ' + pageInstanceId); }, onDataReceived(data){ this.lastDataTime = Date.now(); if (this.isPageActive) { console.log("接收到数据byte[] 转base64的字符串: " + data); this.processReceivedData(data); } else { this.pendingMessages.push(data); console.log("页面不活跃,暂存消息,共暂存: " + this.pendingMessages.length + "条"); } }, // 处理接收到的数据 processReceivedData(base64Data) { try { const binaryData = atob(base64Data); const arrayBuffer = new ArrayBuffer(binaryData.length); const uintArray = new Uint8Array(arrayBuffer); for (let i = 0; i < binaryData.length; i++) { uintArray[i] = binaryData.charCodeAt(i); } this.dataBuffer += base64Data; this.parseDataPackets(); } catch (error) { console.error('处理接收到的数据时出错:', error); this.logError('processReceivedData', error); this.dataBuffer = ""; } }, // 解析数据包 parseDataPackets() { try { while (this.dataBuffer.length >= 4) { const lengthStr = this.dataBuffer.substring(0, 4); const dataLength = parseInt(lengthStr, 10); if (this.dataBuffer.length >= 4 + dataLength) { const packet = this.dataBuffer.substring(4, 4 + dataLength); this.dataBuffer = this.dataBuffer.substring(4 + dataLength); this.handlePacket(packet); } else { break; } } } catch (error) { console.error('解析数据包时出错:', error); this.logError('parseDataPackets', error); this.dataBuffer = ""; } }, // 处理单个数据包 handlePacket(packetData) { try { const jsonData = JSON.parse(packetData); console.log('解析数据包:', jsonData); this.triggerEvent('dataReceived', jsonData); this.dataHandlers.forEach(handler => handler(jsonData)); } catch (error) { console.error('解析JSON数据包时出错:', error); this.logError('handlePacket', error); this.dataHandlers.forEach(handler => handler({ rawData: packetData, error: error.message })); } }, // 添加数据处理器 addDataHandler(handler, event = 'dataReceived') { if (typeof handler === 'function') { this.dataHandlers.push({ handler, event }); } }, // 移除数据处理器 removeDataHandler(handler) { if (typeof handler === 'function') { this.dataHandlers = this.dataHandlers.filter(h => h.handler !== handler); } }, // 触发事件 triggerEvent(eventName, data) { this.dataHandlers.forEach(h => { if (h.event === eventName && typeof h.handler === 'function') { h.handler(data); } }); }, // 连接到服务器 connect(ip, port, timeout = 5000) { ip = "192.168.1.228"; // 测试IP if (!this.tcpPlugin || !this.isPageActive) { console.error('TCP客户端未初始化或页面不活跃'); return; } console.log(`尝试连接到 ${ip}:${port}`); this.currentIp = ip; this.currentPort = port; // 添加连接超时处理 const timeoutId = setTimeout(() => { if (!this.connected) { console.error('连接超时'); this.handleConnectionError(ip, port, timeout); } }, timeout + 1000); this.tcpPlugin.connect( ip, port, timeout, (res) => { clearTimeout(timeoutId); if (!this.isPageActive) return; console.log('连接成功:', res); this.connected = true; this.reconnectAttempts = 0; this.clearReconnectTimer(); this.sendInitialData(); this.startHeartbeat(); this.triggerEvent('connectionEstablished', { host: ip, port }); }, (err) => { clearTimeout(timeoutId); if (!this.isPageActive) return; console.error('连接失败:', err); this.addLog('连接失败: ' + err); // 解析错误类型 let errorType = '未知错误'; if (err.includes('Connection refused')) { errorType = '服务器拒绝连接'; } else if (err.includes('timeout')) { errorType = '连接超时'; } else if (err.includes('Unknown host')) { errorType = '未知主机'; } this.addLog('错误类型: ' + errorType); this.handleConnectionError(ip, port, timeout); this.triggerEvent('connectionFailed', { error: err }); } ); }, // 发送初始数据 sendInitialData() { const jsonData = { request: 'CONNECT', timestamp: new Date().getTime(), clientInfo: { appVersion: uni.getSystemInfoSync().appVersion, deviceId: uni.getSystemInfoSync().deviceId } }; this.sendData(jsonData); }, // 发送数据 sendData(jsonData) { if (!this.tcpPlugin || !this.connected || !this.isPageActive) { console.error('TCP客户端未连接或页面不活跃'); return; } try { const jsonStr = JSON.stringify(jsonData); const checksum = tcpClient.crcCcitt(tcpClient.stringToByteArray(jsonStr), 0); const byteArray = tcpClient.buildPacketFromJson(jsonStr, checksum); // 构建长度前缀数据包 const lengthPrefix = byteArray.length.toString().padStart(4, '0'); const packet = lengthPrefix + btoa(String.fromCharCode.apply(null, byteArray)); this.tcpPlugin.sendBase64Str( packet, (res) => { if (this.isPageActive) { console.log('发送成功:', res); this.triggerEvent('dataSent', { data: jsonData }); } }, (err) => { if (this.isPageActive) { console.error('发送失败:', err); this.connected = false; this.triggerEvent('sendFailed', { error: err, data: jsonData }); this.handleConnectionError(this.currentIp, this.currentPort); } } ); } catch (error) { console.error('构建或发送数据包时出错:', error); this.logError('sendData', error); this.triggerEvent('sendError', { error, data: jsonData }); } }, // 心跳检测 startHeartbeat(interval = 30000) { if (this.heartbeatTimer || !this.isPageActive) return; this.heartbeatTimer = setInterval(() => { if (this.connected && this.isPageActive) { const now = Date.now(); const isTimeout = now - this.lastDataTime > interval * 1.5; const isHeartbeatDue = now - this.lastHeartbeatTime > interval; if (isTimeout) { console.warn('心跳检测: 连接超时,尝试重连'); this.connected = false; this.triggerEvent('connectionTimeout', { lastDataTime: this.lastDataTime }); this.reconnect(); } else if (isHeartbeatDue) { this.sendData({ request: 'HEARTBEAT', timestamp: now, sequence: this.heartbeatSequence++ }); this.lastHeartbeatTime = now; } } }, interval); }, // 停止心跳检测 stopHeartbeat() { if (this.heartbeatTimer) { clearInterval(this.heartbeatTimer); this.heartbeatTimer = null; } }, // 重连机制 reconnect() { if (this.reconnecting || !this.isPageActive) return; this.reconnecting = true; const maxAttempts = this.maxReconnectAttempts || 5; const delay = Math.min(30000, 1000 * Math.pow(2, this.reconnectAttempts)); console.log(`尝试重新连接...第 ${this.reconnectAttempts + 1} 次,延迟 ${delay/1000} 秒`); setTimeout(() => { if (this.reconnectAttempts < maxAttempts && this.isPageActive) { this.reconnectAttempts++; this.connect(this.currentIp, this.currentPort); this.reconnecting = false; } else { console.error(`达到最大重连次数 ${maxAttempts},放弃重连`); this.reconnecting = false; this.clearReconnectTimer(); this.triggerEvent('reconnectFailed', { attempts: this.reconnectAttempts, timestamp: new Date().getTime() }); } }, delay); }, // 清理重连定时器 clearReconnectTimer() { if (this.reconnectTimer) { clearTimeout(this.reconnectTimer); this.reconnectTimer = null; } }, // 处理连接错误 handleConnectionError(ip, port, timeout) { if (!this.isPageActive) return; this.connected = false; this.currentIp = ip; this.currentPort = port; if (!this.reconnecting && !this.reconnectTimer && this.isPageActive) { this.reconnect(); } }, // 处理页面激活 onPageShow() { this.isPageActive = true; if (this.pendingMessages.length > 0) { console.log('页面激活,处理暂存的消息,共: ' + this.pendingMessages.length + '条'); this.pendingMessages.forEach(data => { this.processReceivedData(data); }); this.pendingMessages = []; } if (this.tcpPlugin && this.currentIp && this.currentPort && !this.connected) { this.connect(this.currentIp, this.currentPort); } this.triggerEvent('pageActivated'); }, // 处理页面隐藏 onPageHide() { this.isPageActive = false; this.stopHeartbeat(); this.clearReconnectTimer(); this.triggerEvent('pageDeactivated'); }, // 处理页面销毁 onPageDestroy() { this.isPageActive = false; this.stopHeartbeat(); this.clearReconnectTimer(); this.close(); this.tcpPlugin = null; this.pageInstanceId = null; this.dataHandlers = []; this.triggerEvent('pageDestroyed'); }, // 关闭连接 close() { if (!this.tcpPlugin || !this.isPageActive) { return; } // 停止心跳和重连 this.stopHeartbeat(); this.clearReconnectTimer(); this.tcpPlugin.close( (res) => { if (this.isPageActive) { console.log('断开连接:', res); this.connected = false; this.triggerEvent('connectionClosed', { reason: 'manual' }); } }, (err) => { if (this.isPageActive) { console.error('断开失败:', err); this.connected = false; this.triggerEvent('connectionError', { error: err }); } } ); }, // 统一错误处理 logError(method, error) { console.error(`[TCP Client Error] Method: ${method}, Message: ${error.message}`); if (error.stack) console.error(error.stack); // 触发错误事件 this.triggerEvent('error', { method, message: error.message, stack: error.stack }); }, // 检查连接状态 isConnected() { return this.connected && this.isPageActive; }, // 获取连接信息 getConnectionInfo() { return { connected: this.connected, host: this.currentIp, port: this.currentPort, lastDataTime: this.lastDataTime, heartbeatSequence: this.heartbeatSequence, reconnectAttempts: this.reconnectAttempts, isPageActive: this.isPageActive, pendingMessages: this.pendingMessages.length }; }, // 设置配置 setConfig(config) { if (typeof config !== 'object') return; // 允许配置的参数 const allowedKeys = [ 'maxReconnectAttempts', 'heartbeatInterval', 'reconnectDelay', 'timeout' ]; Object.keys(config).forEach(key => { if (allowedKeys.includes(key)) { this[key] = config[key]; } }); console.log('TCP客户端配置已更新:', config); } } ```