You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python TCP Socket接收JSON解析报'length'键错误问题排查

TCP转HTTP转发程序'length'键错误排查方案

你遇到的Other error occurred: 'length'是KeyError异常,本质是代码尝试读取不存在的字典键,叠加TCP流处理、逻辑写法的多个bug共同导致,具体问题点和修复方式如下:

已确认的代码问题

  • 无意义的数据重复逻辑:接收TCP数据后写的data += data会将刚收到的分片列表复制拼接一份,直接产生大量重复无效内容,是触发解析异常的核心诱因之一。
  • TCP流处理逻辑完全不可靠:TCP是面向流的协议,单次recv()调用无法保证刚好拿到完整、边界对齐的JSON对象,靠替换}{为},{拼接JSON的写法容错性为0——只要JSON字段值中包含}{字符串、或者单次recv拿到半包/粘包数据,JSON解析就会直接失败。
  • 键读取缺少前置判断:协议中length字段只存在于长度包头中,业务数据包(比如你示例里的agent_hello结构)没有这个字段,代码默认解析结果的最后一个元素一定带length键,处理到业务包时必然触发键不存在的错误。
  • HTTP头读取缺少容错:GET请求默认无请求体,多数客户端不会携带Content-Length头,代码直接读取self.headers['Content-Length']会额外触发KeyError。
  • 连接生命周期管理错误:TCP连接在全局作用域初始化,只要一次接收出错就直接关闭连接,后续所有请求都会因为连接失效直接失败。

修复步骤

  1. 删掉data += data这行无效逻辑。
  2. 替换字符串拼接JSON的处理方式,用标准库json.JSONDecoder的raw_decode方法做增量解析,维护一个持久的接收缓冲区:每次收到新数据就追加到缓冲区,循环从缓冲区头部解析完整JSON对象,解析成功就截断缓冲区对应长度,解析失败说明数据不全,继续等待下一批TCP数据即可,从根本上解决粘包、半包问题。
  3. 读取length键前先做存在性判断:只有解析出的JSON对象包含length字段时,才更新下一次接收的缓冲区大小,普通业务数据包直接存入待转发队列即可,不要强行读取length字段。
  4. 读取Content-Length头前先判断键是否存在,不存在则默认内容长度为0,避免触发头字段缺失错误。
  5. 增加TCP连接状态检查逻辑,连接断开时自动重连,避免单次错误导致整个服务不可用。

参考修复代码

#!/usr/bin/python3
import http.server
import socketserver
import json
import socket

PORT = 8000
INIT_BUFF_SIZE = 4096
TCP_TARGET_ADDR = ('10.10.10.10', 2100)

tcp_conn = None
recv_buf = ""
json_decoder = json.JSONDecoder()

def get_alive_tcp_conn():
    global tcp_conn
    if tcp_conn is None or tcp_conn.fileno() == -1:
        tcp_conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        tcp_conn.connect(TCP_TARGET_ADDR)
    return tcp_conn

def get_forward_data():
    global recv_buf
    conn = get_alive_tcp_conn()
    forward_list = []
    current_buf_size = INIT_BUFF_SIZE

    while True:
        # 先从已有缓冲区解析完整JSON
        recv_buf = recv_buf.strip()
        while recv_buf:
            try:
                json_obj, parse_end_pos = json_decoder.raw_decode(recv_buf)
                recv_buf = recv_buf[parse_end_pos:].lstrip()
                # 区分长度包和业务数据包
                if "length" in json_obj:
                    current_buf_size = json_obj["length"] + 16
                else:
                    forward_list.append(json_obj)
                    # 按需调整返回逻辑:示例为拿到1个完整业务包即返回
                    if len(forward_list) >= 1:
                        return forward_list
            except json.JSONDecodeError:
                # 剩余数据不完整,跳出解析继续收数据
                break
        
        # 从TCP连接读取新数据
        new_chunk = conn.recv(current_buf_size).decode("utf-8")
        if not new_chunk:
            conn.close()
            raise ConnectionError("上游TCP连接已断开")
        recv_buf += new_chunk

class MyHttpHandler(http.server.SimpleHTTPRequestHandler):
    def do_GET(self):
        # 拉取上游TCP数据
        try:
            resp_data = get_forward_data()
        except Exception as e:
            self.send_response(502)
            self.end_headers()
            self.wfile.write(f"拉取上游数据失败: {str(e)}".encode("utf-8"))
            return
        
        # 解析请求参数
        content_len = 0
        if "Content-Length" in self.headers:
            try:
                content_len = int(self.headers["Content-Length"])
            except ValueError:
                content_len = 0
        
        req_data = None
        if content_len > 0:
            try:
                req_data = json.loads(self.rfile.read(content_len))
            except json.JSONDecodeError:
                req_data = None
        print("收到请求参数:", req_data)

        # 返回响应
        self.send_response(200)
        self.send_header("Content-Type", "application/json")
        self.end_headers()
        self.wfile.write(json.dumps(resp_data).encode("utf-8"))

if __name__ == "__main__":
    socketserver.TCPServer.allow_reuse_address = True
    with socketserver.TCPServer(("", PORT), MyHttpHandler) as httpd:
        print(f"HTTP服务启动成功,监听0.0.0.0:{PORT}")
        try:
            httpd.serve_forever()
        except KeyboardInterrupt:
            print("收到终止信号,正在关闭服务")
            if tcp_conn and tcp_conn.fileno() != -1:
                tcp_conn.close()
            httpd.server_close()

可以根据实际业务需求调整get_forward_data里的返回逻辑,比如需要一次返回多个业务包、或者匹配特定类型的包再返回,基于增量解析的逻辑不会再出现JSON解析错位、键读取错误的问题。

内容的提问来源于stack exchange,提问作者Aleksa Markovic

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 20:57:19