当前位置: 首页 > news >正文

TCP 流通信中的 EOFException 与 JSON 半包问题解析

一、背景

在做机器人与上位机通信调试时,我遇到一个典型的异常:

Caused by: java.io.EOFException: End of input at line 1 column 2697 path $.results.charge_point_3F_1.pose.orientation.y

表面看似 JSON 格式错误,实际上是——收到的 JSON 不完整(半包)

二、问题重现

在 Android 端通过 TCP 接收上位机发来的 JSON 数据,直接用 Gson 解析:

val json = String(bytes, Charsets.UTF_8)
val obj = Gson().fromJson(json, RobotMarkers::class.java)

第一段刚好停在 "y": 后面。
Gson 在解析时发现缺少值和右括号,于是抛出 EOFException。


三、TCP 是流,不是消息队列

这其实是一个经典误区:

“一次 read() 读取的数据,就是一条完整消息。”

错!

TCP 是“流式传输”,它只保证字节顺序,不保证消息边界。
因此可能出现:

情况描述示例
半包一条消息被拆成多次到达{ "a":1, "b": + 2 }
粘包多条消息连在一起{ "a":1 }{ "b":2 }

四、为什么会抛 EOFException?

Gson.fromJson() 会逐字符读取输入流:

  • 它期望 JSON 结构完整闭合({} 或 [])。

  • 当还没读完对象,却到达字符串结尾(EOF)→ 抛异常。

也就是说:

EOFException = “输入流已结束,但我还没看到该有的 } 或 ]。”


五、解决方案

✅ 方案一:协议层加“定界符”

最根本的办法是:

让每条消息“有边界”。

常见两种协议形式:

1. 长度前缀

发送端先写入 4 字节长度,再写入 JSON 内容。

[4字节长度][JSON字节...]

接收端先读长度,再循环读取指定字节数 → 这就是完整 JSON。

2. 行分隔(NDJSON)

每条 JSON 以换行 \n 结尾。
接收端用 BufferedReader.readLine() 读取一整行后解析。


✅ 方案二:客户端做“拼包器”

如果短期内无法改协议,可以在客户端拼接:

class JsonFramer {private val sb = StringBuilder()fun feed(chunk: String): List<String> {sb.append(chunk)val out = mutableListOf<String>()var i = 0; var depth = 0; var inStr = false; var esc = false; var start = -1while (i < sb.length) {val c = sb[i]if (inStr) {if (esc) esc = false else if (c == '\\') esc = true else if (c == '"') inStr = false} else when (c) {'"' -> inStr = true'{' -> if (depth++ == 0) start = i'}' -> if (--depth == 0 && start >= 0) {out += sb.substring(start, i + 1)start = -1}}i++}if (out.isNotEmpty()) sb.delete(0, sb.lastIndexOf('}') + 1)return out}
}

每次 read() 到的数据都 feed() 一次,
feed() 会在 JSON 结构完整时返回一条完整字符串。

✅ 方案三:解析前清洗数据

有时候日志里包含前缀,比如:

收到 TCP 消息: {...}

要裁掉这些内容:

fun clean(raw: String): String {val start = raw.indexOf('{')val end = raw.lastIndexOf('}')return raw.substring(start, end + 1)
}

六、lenient 模式能解决吗?

JsonReader.setLenient(true) 只是在语法层放宽规则:

  • 允许单引号、注释、NaN 等;

  • ❌ 不会修复“半包”或“粘包”问题

 

七、最佳实践:稳健 TCP 接收逻辑

val buf = ByteArray(16 * 1024)
val framer = JsonFramer()while (true) {val n = input.read(buf)if (n == -1) breakval chunk = String(buf, 0, n, Charsets.UTF_8)val frames = framer.feed(chunk)for (json in frames) {val clean = clean(json)val obj = Gson().fromJson(clean, Response::class.java)process(obj)}
}

八、常见排查 Checklist ✅

检查项说明
是否分多次 read()打印每次字节数确认
是否有日志前缀清洗字符串
缓冲大小是否过小建议 ≥ 16KB
是否拼包必须有!
是否多条粘在一起用 Framer 逐条拆分
是否 lenient可用于调试,但不治本

九、总结

核心问题原因解决方案
EOFExceptionJSON 被截断(半包)拼包或协议定界
MalformedJsonException粘包或脏前缀拆包 + 清洗
偶发成功偶发失败TCP 分段不固定循环读 + 拼包

⚙️ 记住一句话:
TCP 是流,JSON 是消息。要想稳定,必须“定界”。

十、结语

EOFException 并不可怕,
可怕的是我们误以为“读一次就是一条消息”。

在任何基于 TCP 的 JSON 通信中,都应该建立一层消息边界感知机制。
只有这样,通信层才真正做到“稳如老狗”。

http://www.dtcms.com/a/537271.html

相关文章:

  • Garnet技术深度解析:微软研究院出品的高性能缓存存储引擎
  • 如何制作论坛网站网页制作软件是哪个
  • 《从点击到响应:HTTP 请求是如何传出去的》
  • 科技+文化:从“参观游览”到“沉浸共生”的文旅产业革命
  • 做条形图的网站河南郑州网站建设
  • 青少儿机器人技术学什么?
  • 基本魔法语言数组 (一) (C语言)
  • php源码网站修改保定建设厅网站
  • 网站建设访问对象网站开发毕设的需求分析
  • 【实战】自然语言处理--长文本分类(2)BERTSplitLSTM算法
  • Markdown模板20251027
  • Linux命令解释:cp -r --parents用法
  • 关于企业的网站天津做网站找谁
  • 0138. 随机链表的复制
  • 长春网站建设公司seo是什么意思教程
  • 【vllm】源码解读:vLLM 中 Data Parallelism DP=8 核心原理详解
  • 对信号的理解
  • 【系统分析师】高分论文:论软件的安全性设计(某校通系统)
  • 硬盘专业名词:总线、协议、接口详细解析
  • Agent Skills应用解析:构建可扩展、高效率AI探员
  • 【车载测试常见问题】CAN一致性测试包含哪些内容?
  • 成都网站开发制作上海进博会
  • 云手机和虚拟机的区别都有哪些?
  • php wap网站实现滑动式数据分页大公司网站开发
  • WebSocket 详解
  • SPR 实验笔记:从原理到实操,解锁天然产物筛选、靶点验证与膜蛋白互作的“金标准”技术
  • 发布会回顾|袋鼠云发布多模态数据中台,重构AI时代的数据底座
  • AOI在PCB制造领域的核心应用
  • 网站建设系统规划seo信息优化
  • 建筑公司网站设计思路静态网站怎么样