☰
JMeter TCP压测中文乱码解决:从原理到自定义TCPClient实践
2026/10/21 7:11:06 网站建设 项目流程

从实际项目里踩过来的坑说起:做接口压测的时候,只要涉及 TCP 长连接,几乎躲不开中文乱码的问题。尤其是用 JMeter 直接发 TCP 请求,请求体里带上中文参数,服务端收到的经常是一串问号,或者干脆把整个消息拆得稀碎。再加上响应报文里如果带着中文,查看结果树里一片乱码,日志文件里更是惨不忍睹。这个问题不算高频,但一旦碰上,排查起来特别费劲。这篇文章把 JMeter 压测 TCP 请求的中文乱码问题从头到尾拆开讲清楚,给出一套能直接落地的方案。

先说清楚适用范围:如果你是刚接触 JMeter 压测 TCP 服务,或者已经在压测 but 被编码问题卡住的同学,这篇文章可以帮你少走很多弯路。我会从乱码产生的原理讲起,再给出几个实操性的解决路径,最后附上我自己在项目中踩过的坑和排查技巧,尽量让每个方案都能直接照着做。

1. 乱码问题的根源与排查思路

1.1 TCP 压测场景里,中文乱码到底是怎么产生的

很多人遇到乱码第一反应就是“改编码”,实际上编码问题在 TCP 层并不是一个简单能改配置就解决的事情。JMeter 的 TCP 取样器(TCP Sampler)本质上是把你在界面里输入的字符串,通过某种方式编码成字节数组,然后通过 Socket 发出去。服务端收到的是字节,不是字符,这意味着发送端和接收端必须对同一个字节序列使用相同的字符集解码,才能还原出正确的中文。

举一个例子:你在 JMeter 的 TCP Sampler 的数据字段里输入:

{"userName":"张三","action":"register"}

JMeter 内部默认会按照平台默认字符集(Windows 上通常是 GBK,Linux 上通常是 UTF-8)对这个字符串做getBytes()操作,然后发送。如果你的服务端统一按 UTF-8 接收,而 JMeter 所在机器是 Windows 并且默认用了 GBK,那么“张三”这两个字就会被编码成 GBK 的字节序列,服务端按 UTF-8 解码,自然就是乱码。

反过来也一样:服务端返回的字节流按 UTF-8 编码,但 JMeter 读取响应时用错了解码字符集,查看结果树里就会显示一堆 � 这样的替换字符或者无法识别的符号。

还有一种隐蔽情况:服务端本身没有乱码,但是 JMeter 的日志输出、CSV 结果文件、查看结果树这些展示模块使用的字符集不一致,导致原本正确的字节序列被转成了乱码字样。这种“假乱码”也很常见。

1.2 从 TCP 字节流角度看编码链路

TCP 本身是字节流协议,它不认识什么是字符串,也不关心编码。所谓“中文乱码”,一定发生在两个环节中的至少一个:

  • 发送环节:字符串 → 字节数组时,选择了错误的字符集
  • 接收环节:字节数组 → 字符串时,选择了错误的字符集

如果用一条链来表示就是:

JMeter 输入框(String) ↓ 字符集A编码 Socket 发送字节数组 ↓ 网络传输 服务端接收字节数组 ↓ 字符集B解码 服务端业务处理

如果字符集 A 和字符集 B 一致,理论上不会有问题。如果 A 是 GBK、B 是 UTF-8,或者 A 是 UTF-8、B 是 GBK,那中文必然乱码。

1.3 JMeter TCP 取样器默认行为解析

JMeter 的 TCP 取样器在发送数据时,默认使用的是TCPClientImpl这个实现类。它处理字符串转字节的逻辑,就是调用字符串的getBytes()方法,而getBytes()如果不带参数,会使用 JVM 的默认字符集(file.encoding)。这个默认字符集在 Windows 上通常是 GBK 或 GB2312,在大部分 Linux 发行版上是 UTF-8。

问题就出在这里:默认行为不可控。你写脚本的机器和服务端可能处于不同的系统语言环境,甚至同一台机器上 JMeter 的启动参数不同,都会导致默认字符集不同。一旦默认字符集和服务端预期不一致,中文乱码几乎是必然的。

所以解决 TCP 压测中文乱码的重点,不是去改 JMeter 的某个显示选项,而是要控制住发送/接收环节的编码行为,让编码过程完全可控、显式指定。

2. 整体解决思路与方案选型

2.1 三个层面的解决方案对比

在真实项目中,我试过三类方案来解决 TCP 中文乱码,各有利弊。先放在一起看:

方案方向具体做法优势劣势适用场景
JMeter 配置层修改 jmeter.properties 中的编码项简单、快速、无需改代码对 TCP 取样器发送部分通常无效,仅影响展示响应显示乱码,且服务端编码确实为 UTF-8
前置处理器层在 TCP 取样器之前用 JSR223 或 BeanShell 做字符串编码转换灵活、不用编译插件需要写脚本,且要控制好字符串拼接时机请求体是纯文本字符串、结构简单
自定义 TCPClient编写自定义 TCPClient 实现类,显式编码/解码最彻底、可控性最强、能处理复杂协议需要编译 Java 类、打包,门槛稍高请求/响应结构复杂,或需要处理粘包、二进制流

这三层方案并不是互斥的,实际项目中我通常建议“配置层打底 + 自定义 TCPClient 兜底”。原因是 TCP 压测只要涉及中文,几乎所有问题最终都会落到编码控制上,而 JMeter 自带的 TCPClientImpl 不够可靠,自定义虽然多花一点时间,但一步到位。

2.2 为什么自定义 TCPClient 是最可靠的选择

默认的TCPClientImpl在发送阶段用String.getBytes()获取字节,在接收阶段用new String(bytes)构造字符串,这两个关键点都没有显式指定字符集,所以天然存在不确定性。而自定义 TCPClient 的好处是:把字符集固死在代码里,不管 JMeter 跑在什么操作系统、什么语言环境下,都会被强制使用你指定的字符集。

写一个自定义 TCPClient 并不复杂,核心就是继承AbstractTCPClient类,重写write和read方法,分别指定发送和接收的编码即可。整个类可能还不到 100 行代码,但能解决掉 95% 以上的中文乱码问题。后面实操章节我会给出完整代码。

2.3 特殊场景:协议自带长度字段时的处理思路

有些 TCP 服务会在报文头部携带长度字段,比如前两个字节是总消息长度(大端序),后面跟着具体内容。这类协议如果直接在 JMeter 里输入字符串,那么一旦包含中文,长度字段就可能算错,因为一个中文字符在 UTF-8 下占 3 个字节,在 GBK 下占 2 个字节,不同类型的字符长度不同,人肉计算极易出错。

这种情况下,最好的做法是在自定义 TCPClient 的内部拼接报文,而不是在 JMeter 输入框里手写完整的带长度的报文。也就是说:你在 JMeter 里只需要填业务体字符串,由自定义类负责计算长度、填入头部字段、按指定字符集编码,这是一个很实用的扩展思路。

3. 实操:从配置修改到自定义 TCPClient

3.1 第一步:修改 JMeter 的基础配置,解决响应显示乱码

在编写自定义 TCPClient 之前,先处理最基础的一层:确保查看结果树里的响应内容能正确显示中文。

修改 JMeter 安装目录下的bin/jmeter.properties文件,找到这一行:

sampleresult.default.encoding=ISO-8859-1

把它改成:

sampleresult.default.encoding=UTF-8

改完后保存,重启 JMeter。这一步影响的是 JMeter 对采样结果字符串的显示解码方式。如果你的服务端返回的数据确实是 UTF-8,那么响应内容里的中文就能正常显示。

但是请注意:这个配置对 TCP 取样器的发送环节没有任何帮助。如果你的服务端接收时要求 GBK,而 JMeter 默认按 UTF-8 发,这里改一万遍也不影响发送结果。

3.2 第二步:编写自定义 TCPClient 完整代码

直接上代码。这个类实现的是:发送时按 UTF-8 编码、接收时按 UTF-8 解码。如果服务端要求 GBK,把两处UTF_8换成GBK即可。

package com.example.jmeter; import org.apache.jmeter.protocol.tcp.sampler.AbstractTCPClient; import org.apache.jmeter.protocol.tcp.sampler.TCPClient; import org.apache.jmeter.protocol.tcp.sampler.ReadException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; /** * 自定义 TCP Client,用于解决 JMeter TCP 请求中文乱码问题。 * 发送和接收均强制使用 UTF-8,避免依赖平台默认字符集。 */ public class UTF8TCPClient extends AbstractTCPClient { private Charset charset = StandardCharsets.UTF_8; @Override public void write(OutputStream os, String s) throws IOException { // 核心:把字符串按指定字符集编码为字节数组后写出 os.write(s.getBytes(charset)); os.flush(); } @Override public String read(InputStream is) throws ReadException { try { byte[] buffer = new byte[8192]; int len = is.read(buffer); if (len <= 0) { return ""; } // 核心:按指定字符集将字节数组解码为字符串,避免乱码 return new String(buffer, 0, len, charset); } catch (IOException e) { throw new ReadException("读取 TCP 响应失败", e); } } }

这个类里有几个点需要解释一下:

  • AbstractTCPClient是 JMeter 提供的一个抽象基类,它实现了TCPClient接口的大部分默认方法。我们只需要关心write和read这两个核心方法。
  • write(OutputStream os, String s)方法的参数s就是你在 TCP Sampler 数据字段里输入的内容。在这个方法里,我们把字符串真正转换成字节数组,所以此刻指定字符集,才能从根上杜绝发送乱码。
  • read(InputStream is)方法要负责从 Socket 输入流中读取服务端返回的数据,按同样的字符集解码。注意我用了固定 8192 字节的缓冲区,如果你的服务端返回的数据可能超过 8KB,需要调整或改成循环读取。

3.3 第三步:编译、打包并配置到 JMeter

写好的 Java 类需要编译并放到 JMeter 的 classpath 中。整体步骤:

  1. 在本地创建一个 Java 项目,把上面的代码放进去。项目依赖只需要ApacheJMeter_core.jar,你可以在 JMeter 安装目录的lib目录下找到(比如ApacheJMeter_core-5.6.3.jar,版本号随安装包变化)。

  2. 编译并打包成 jar 文件。如果你用 Maven,可以加入 JMeter 核心依赖后直接mvn package。如果不想搭建 Maven,也可以直接用 javac 编译,然后把 class 文件打进 jar 包。

  3. 把生成的 jar 包复制到 JMeter 安装目录的lib/ext目录下。

  4. 重启 JMeter,让自定义类被加载。

  5. 在 TCP Sampler 里添加一个配置项。先在 TCP Sampler 面板上添加一个classname参数,参数值填你自定义类的完整类名:

com.example.jmeter.UTF8TCPClient

具体操作是:选中 TCP Sampler -> 在控制面板的“Allow port and class info in…”或老版本直接在 TCP Sampler 上添加一个用户自定义变量 / 直接在配置面板的类名输入框填写。不同 JMeter 版本界面略有区别,核心思路就是让 TCP Sampler 知道要使用哪个 TCPClient 实现类。

新版 JMeter 的 TCP Sampler 面板上会有一个TCPClient classname的配置下拉框,你选择或填写com.example.jmeter.UTF8TCPClient即可。

3.4 第四步:验证是否生效

配置完成后,在 TCP Sampler 的数据字段里随便输入一个包含中文的报文,比如:

{"action":"login","username":"测试账号","password":"123456"}

运行一次,在查看结果树里观察两件事:

  • 服务端是否正常处理请求,没有报协议解析错误(说明请求中的中文已按 UTF-8 正确发送)。
  • 响应内容里的中文是否正常显示(说明响应也已按 UTF-8 正确解码)。

如果响应正常,说明已生效。如果还是乱码,下一步检查服务端是不是用了非 UTF-8 编码(比如 GBK)。若确认是 GBK,就把代码里的StandardCharsets.UTF_8改成Charset.forName("GBK"),重新编译打包配置。这个循环过程通常不会超过十分钟。

3.5 方案补充:不写代码也能处理简单报文的 JSR223 方式

如果不想编译 Java 类,或者协议非常简单,也可以使用 JSR223 前置处理器做转换。

在 TCP Sampler 下添加一个 JSR223 前置处理器(语言选 Groovy),脚本里把想要发送的中文字符串,手动转成目标编码的字节序列,然后以字符串形式填到 TCP 取样器的数据字段。核心原理是:用 Groovy 已经把字节转换好了,JMeter 再次转字节时不会再产生额外差异。但这里有个前提,TCPClientImpl 内部还是会按默认字符集把你的字符串再变成字节,所以如果转换后的字符串里含有特殊字符(比如\x形式的控制符),仍然有二次转换风险。

所以我的建议是:JSR223 方案只适合临时应急,或者服务端数据相对规整、报文内不会出现二进制控制字符的场景。

3.6 CSV 参数化文件中的中文乱码陷阱

另外一类很常见的情况:通过 CSV 文件做参数化时,文件里有中文用户名或中文请求参数。如果 CSV 文件的编码和 JMeter 读取时用的编码不一致,即使你用了自定义 TCPClient,中文也依然会乱。

看 CSV 数据文件设置(CSV Data Set Config)面板,有一个fileEncoding字段。如果你留空,JMeter 会使用平台默认编码读取文件。在 Windows 环境下,默认编码是 GBK,而你的 CSV 文件可能保存成了 UTF-8,这就会导致读取乱码;反过来也成立。

确保三处一致:

  • CSV 文件的保存编码
  • CSV 数据文件设置里的fileEncoding
  • 自定义 TCPClient 或服务端约定的通信编码

举例:如果用 Excel 保存 CSV,最稳妥的办法是删除 BOM 头,并且在 CSV Data Set Config 中显式填写UTF-8。有些老版本 Excel 另存为CSV UTF-8时会在文件头加 BOM,虽然显示没问题,但首列第一行会出现一个\ufeff隐形字符,导致第一个用户名开头多出一个乱码符号。这个坑尤其容易在“第一行参数总是失败,后面都正常”的现象中出现。

4. 常见问题排查与避坑技巧

4.1 问题:改了 sampleresult.default.encoding 还是乱码

原因大概率是服务端返回的字节流本身就不是 UTF-8,或者响应里根本不是纯文本。此时可以先跳过查看结果树的显示,改用监听器“保存响应数据”功能,把原始响应写到文件里,再用支持手动切换编码的编辑器打开,判断真实编码是什么。如果编辑器用 UTF-8 打开是乱码,用 GBK 打开正常,说明服务端返回的是 GBK。这类问题不能靠配置修,必须改自定义 TCPClient 的解码字符集。

4.2 问题:请求数据没乱码但响应数据乱码

发送正常只代表发送环节的编码已统一。响应乱码则在read方法里解码不对。检查顺序:

  1. 确认自定义 TCPClient 已生效(可以在代码里加日志输出读到的前几十个字节的 hex)。
  2. 确认服务端返回编码与代码里的charset一致。
  3. 如果响应粘包,也会出现乱码。这个问题和编码无关,而是你一次read可能读到了两个报文或半个报文,导致解码出来的字符串出现异常字符。这种情况需要在代码里根据协议帧边界截取报文,而不是简单读一次。

4.3 问题:Windows 环境正常,Linux 环境跑脚本乱码

这说明脚本本身没问题,出在平台默认字符集上。TCPClientImpl 默认按 JVM 默认字符集编码,Linux 和 Windows 不同,导致同一脚本在不同机器上表现不一致。这也是我坚持用自定义 TCPClient 的关键理由——它把字符集写死在代码里,不再受系统环境影响。

4.4 问题:CSV 参数化第一行数据乱码

这就是前面提到的 BOM 问题。用 Notepad++ 或 VS Code 打开 CSV 文件,将文件编码转为“UTF-8 无 BOM”格式,并确认 CSV Data Set Config 里fileEncoding=UTF-8。如果仍然不行,用十六进制编辑器看一下文件开头,正常 UTF-8 无 BOM 文件应以7B之类的业务字符起始,而不是EF BB BF。

4.5 常见问题速查表

现象可能原因解决方向
请求到服务端后中文变问号发送编码与服务端解码不一致自定义 TCPClient 指定正确编码
响应在结果树里显示乱码响应解码错误或显示配置错误检查 sampleresult.default.encoding 与 read 方法解码
不同机器运行结果不同依赖平台默认字符集统一在代码中指定字符集
CSV 首行首参数乱码文件 BOM 头另存为 UTF-8 无 BOM
响应乱码且内容被截断粘包/半包需要按协议读固定长度或循环读取
使用 Base64 或 hex 编码后中文正常协议可能约定传输 binary 字符串考虑在自定义 TCPClient 内部解码

4.6 独家经验:排查编码问题时先看 hex

这是我想重点分享的一个个人习惯。遇到乱码不要只看字符展示,建议直接在代码里打印收到的原始字节的十六进制形式,比如:

byte[] prefix = new byte[Math.min(len, 32)]; System.arraycopy(buffer, 0, prefix, 0, prefix.length); StringBuilder sb = new StringBuilder(); for (byte b : prefix) { sb.append(String.format("%02X ", b)); } log.info("Received bytes: " + sb.toString());

通过 hex 判断真实编码非常高效。比如中文“测”:

  • UTF-8 编码为E6 B5 8B
  • GBK 编码为B2 E2

只要看到E6 B5 8B,基本可以断定服务端返回的是 UTF-8;看到B2 E2大概率是 GBK。在排查阶段,这种硬性依据比肉眼观察字符串可靠得多。

结尾想说的几句实话

这种问题不复杂,但真的很磨人。我见过不少同事遇到乱码就反复改 JMeter 的配置、加prev.setDataEncoding一等乱七八糟的方法,结果越改越乱。核心思路只有一条:TCP 的字符编码问题必须收敛到发送和接收的字节转换环节,其他操作都是绕路。只要把自定义 TCPClient 写好,编码固定好,再对齐 CSV 文件的编码,这个坑就彻底填平了。压测链路里还有更值得花时间的事,别在中文字符上反复纠缠。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询