引言
在如今的软件系统中,各个端存在各种网络协议进行交互,比如https、http、TCP、MQtt、UDP等等。
其中,http协议的参数化,比较简单,jmeter自带的http请求已经能适配绝大多数场景了,但是TCP请求,通常由于报文结构复杂、加解密、进制转换等原因,jmeter原生的tcp请求不太适用。因此,本文仅以Jmeter工具java请求为例,基于java请求,使用java代码自定义开发TCP客户端,实现TCP客户端与TCP服务器端交互。
基于jmeter实现tcp客户端以后,就可使用java请求,类比http,使用jmeter客户端对tcp服务器进行压测。
1.目标功能
现有某公司定义了其某个IoT设备的登录请求和响应报文,采用的TCP协议。
假设现在有100万台,需要对TCP服务器进行压测,测试最大能承受多少台设备登录。
设备序列编号从test00000001到test01000000,共100万台。
TCP协议报文如下:
请求报文:
JAYC 000000A41001746573743030303030303031000000000000000001313233343536373839303132333435363738393076312E322E3000000000641A04775C772F01000007E9040D022632EFEF报文解读:

返回报文:
JAYC 000001A6200100007465737430303030303030310107E9040D0226320365752D63656E7472616C2D310000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007465737474657374746573747465737400000000000000005445535454455354544553545445535454455354000000000000000000000000746573747465737474657374746573747465737474657374746573747465737474657374746573740000000000000000000007E9040D022633EFEF报文解读:

2.准备工作
1、环境:Java1.8、Maven 3.x以上、jmeter 5.x以上。
2、开发工具:Windows IDEA。
本人的环境如下图:

jmeter自定义java请求的依赖包有2个,如下:
ApacheJMeter_core
ApacheJMeter_java这2个依赖包在jmeter的lib/ext目录下,如果你的网络环境是内网无法访问Maven官网下载依赖包,那么可通过idea手动引入这3个包。

3.新建项目并配置依赖
新增项目
IDEA新增Maven项目,名称tcpdemo

由于在代码中TextUtils,需要引入org.apache.httpcomponents.httpclient.TextUtils依赖


在pom文件中添加jmeter版本和依赖

完整依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>tcpdemo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<jmeter-version>5.6.3</jmeter-version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_core</artifactId>
<version>${jmeter-version}</version>
</dependency>
<dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_java</artifactId>
<version>${jmeter-version}</version>
</dependency>
<dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_functions</artifactId>
<version>${jmeter-version}</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
</dependencies>
</project>点击刷新按钮,加载依赖

4.代码实现
服务端代码如下:

config代码
package com.example.tcpserverdemo.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import java.util.concurrent.*;
@Component
@Configuration
public class ThreadPoolConfig {
@Value("${spring.task.execution.pool.core-size}")
private int coresize;
@Value("${spring.task.execution.pool.max-size}")
private int maxsize;
@Value("${spring.task.execution.pool.queue-capacity}")
private int queuecapacity;
@Value("${spring.task.execution.pool.keep-alive}")
private int keepalive;
@Value("${spring.task.thread-name-prefix}")
private String nameprefix;
@Bean("iotTaskExecutor")
public ExecutorService iotTaskExecutor() {
return new ThreadPoolExecutor(
coresize,
maxsize,
keepalive,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(queuecapacity),
new ThreadPoolExecutor.AbortPolicy());
}
}
server代码如下:
package com.example.tcpserverdemo.server;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.StringUtils;
@Slf4j
@Component
public class TcpServer {
@Value("${tcp.server.port}")
private int port;
private ServerSocket serverSocket;
private volatile boolean running = false;
@Autowired
@Qualifier("iotTaskExecutor")
private ExecutorService executorService;
@PostConstruct
public void start() {
try {
serverSocket = new ServerSocket(port);
running = true;
log.info("TCP Server started on port {}", port);
new Thread(this::runServer).start();
} catch (IOException e) {
log.error("Failed to start TCP server", e);
}
}
private void runServer() {
while (running && !serverSocket.isClosed()) {
try {
Socket clientSocket = serverSocket.accept();
log.debug("New connection from {}", clientSocket.getRemoteSocketAddress());
executorService.execute(() -> handleClient(clientSocket));
} catch (IOException e) {
if (running) {
log.error("Error accepting connection", e);
}
}
}
}
int REQ_HEAD_LENTH = 20;//读取报文前20位,其最后8位是报文总长度
String headerString,bodyLength,reqContent,rspContent;
private void handleClient(Socket socket) {
try (
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
) {
// 读取数据,读取请求报文的头部长度
int read_len = 0, cur_len = 0; // 记录当前读取的长度
byte[] reqHead_byte = new byte[REQ_HEAD_LENTH];
do {
try {
cur_len = in.read(reqHead_byte, read_len, REQ_HEAD_LENTH - read_len);
read_len += cur_len;
} catch (IndexOutOfBoundsException e) {
e.printStackTrace();
break;
}
} while (read_len < REQ_HEAD_LENTH);
headerString = hexToAscii(BinaryToHexString(reqHead_byte).replace(" ",""));
log.info("读取请求报文的头部: {}" , headerString);
bodyLength = headerString.substring(12);//最后8位是报文总长度
log.info("读取请求报文的长度: {}" , bodyLength);
int reqLen = Integer.valueOf(hexto10(bodyLength)) - 20;//读取请求报文长度
log.info("请求报文的body长度: {}" , reqLen);
// 读取内容
byte[] reqContent_byte = new byte[reqLen];
read_len = 0;
cur_len = 0;
try {
do {
cur_len = in.read(reqContent_byte, read_len, reqLen - read_len);
read_len += cur_len;
} while (read_len < reqLen);
} catch (Exception e) {
e.printStackTrace();
}
//完整请求报文,头部+body
reqContent = headerString + new String(reqContent_byte, "GBK");
log.info("完整请求报文: {}", reqContent);
//组装返回报文
rspContent = processRequest(reqContent);
log.info("返回报文: {}", rspContent);
out.write(rspContent.getBytes("GBK"));
out.flush();
} catch (Exception err) {
err.printStackTrace();
rspContent = err.getMessage();
} finally {
try {
if (socket != null && !socket.isClosed())
socket.close();
} catch (Exception err) {
err.printStackTrace();
}
}
}
//解析请求报文
private String processRequest(String inputString) {
//JAYC 000000A41001746573743030303030303031000000000000000001313233343536373839303132333435363738393076312E322E3000000000641A04775C772F01000007E9040D022632EFEF
/**
* JAYC 000000A4 头部
* 1001 IoT指令
* 7465737430303030303030310000000000000000 设备出厂序列号
* 01 上网方式
* 3132333435363738393031323334353637383930 物联网卡卡号/WiFi MAC地址
* 76312E322E3000000000 设备版本号
* 64 设备当前电量
* 1A 设备环境温度℃
* 04 设备信号强度/上网质量(WiFi信号格数)
* 775C 设备存储空间Total
* 772F 设备存储空间剩余
* 01 设备状态
* 0000 错误码
* 07E9040D022632 时间戳
* EFEF 结束符
* */
String header,command,deviceSeq,network,simCrd,deviceVersion,battery,temperature,signal,spaceTotal,spaceFree,status,errorCode,time,End;
header = inputString.substring(0,20);
command = inputString.substring(20,24);
deviceSeq = hexToAscii(inputString.substring(24,64)).replaceAll(" ","");
network = inputString.substring(64,66);
simCrd = inputString.substring(66,106);
deviceVersion = inputString.substring(106,126);
battery = inputString.substring(126,128);
temperature = inputString.substring(128,130);
signal = inputString.substring(130,132);
spaceTotal = inputString.substring(132,136);
spaceFree = inputString.substring(136,140);
status = inputString.substring(140,142);
errorCode = inputString.substring(142,146);
time = inputString.substring(146,160);
End = inputString.substring(160,164);
//组装tcp返回报文
/**
* JAYC 000001A6200100007465737430303030303030310107E9040D0226320365752D63656E7472616C2D310000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007465737474657374746573747465737400000000000000005445535454455354544553545445535454455354000000000000000000000000746573747465737474657374746573747465737474657374746573747465737474657374746573740000000000000000000007E9040D022633EFEF
* JAYC 000001A6 开始符,报文头部,格式固定为JY加8位空格最后8位是总报文长度(十六进制),即十进制420
* 2001 固定格式,类似http的200返回码,表示报文交互成功,2001登录回复,2002同步参数回复,2003上传图片回复……
* 0000 Result,0000表示无异常,类似于http json报文的{success:true}/{code:0}
* 7465737430303030303030310107E9040D022632 设备登录的token,即设备出厂序列号+状态码+请求时间戳
* 03 Mode上传模式,01表示FTP,02表示阿里OSS,03表示亚马逊OSS S3
* 65752D63656E7472616C2D31000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 UIP,设备的IP地址/OSS的链接地址,右补空格,如果是FTP,则通过IP:端口传图,如果是OSS,则通过对象存储的链接传图,eu-central-1表示亚马逊欧洲区域的OSS地址
* 746573747465737474657374746573740000000000000000 Port,FTP的端口,右补空格,或者bucket,OSS的对象存储桶
* 5445535454455354544553545445535454455354000000000000000000000000 user,FTP的用户名,右补空格或者亚马逊OSS通行证AWS Access Key
* 746573747465737474657374746573747465737474657374746573747465737474657374746573740000000000000000 password,FTP的用户密码,右补空格。或者亚马逊OSS密钥,AWS secret Key
* 0000 undealCommand,待处理指令,0000说明无待处理指令,0000表示无待处理指令,0001表示设备需要同步参数...
* 07E9040D022633 返回时间戳
* EFEF 结束符
* **/
//String rsp = "JAYC 000001A6200100007465737430303030303030310107E9040D0226320365752D63656E7472616C2D310000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007465737474657374746573747465737400000000000000005445535454455354544553545445535454455354000000000000000000000000746573747465737474657374746573747465737474657374746573747465737474657374746573740000000000000000000007E9040D022633EFEF";
String resCommand,result,deviceToken,updateMode,Uip,Port,user,password,undealCommand,rspTime;
String rspContent = null;
if (command.equals("1001")){//如果是1001则返回登录回复的报文
resCommand = "2001";
result = "0000";
deviceToken = asciiToHex(deviceSeq) + status + time;
updateMode = "03";
Uip = "65752D63656E7472616C2D31000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";
Port = "746573747465737474657374746573740000000000000000";
user = "5445535454455354544553545445535454455354000000000000000000000000";
password = "746573747465737474657374746573747465737474657374746573747465737474657374746573740000000000000000";
undealCommand = "0000";
rspTime = geTimeStample();
rspContent = resCommand + result + deviceToken + updateMode + Uip + Port + user + password + undealCommand + rspTime;
}
String rsp="JAYC " + StringUtils.leftPad( Integer.toHexString(rspContent.length() + 24).toUpperCase(),8,"0") + rspContent + End;
return rsp;
}
@PreDestroy
public void stop() {
running = false;
try {
if (serverSocket != null && !serverSocket.isClosed()) {
serverSocket.close();
}
executorService.shutdown();
if (!executorService.awaitTermination(5, TimeUnit.SECONDS)) {
executorService.shutdownNow();
}
log.info("TCP Server stopped");
} catch (IOException | InterruptedException e) {
log.error("Error stopping server", e);
Thread.currentThread().interrupt();
}
}
public static String BinaryToHexString(byte[] bytes) {
String hexStr = "0123456789ABCDEF";
String result = "";
String hex = "";
for (byte b : bytes) {
hex = String.valueOf(hexStr.charAt((b & 0xF0) >> 4));
hex += String.valueOf(hexStr.charAt(b & 0x0F));
result += hex + " ";
}
return result;
}
public static String hexto10(String inputString) {
int decimal = Integer.parseInt(inputString, 16);
//System.out.println("16进制数: " + inputString + " ,对应的10进制数: " + decimal);
return String.valueOf(decimal);
}
public static String hexToAscii(String hexStr) {//Hex16进制字符串转换成ascii
StringBuilder output = new StringBuilder("");
for (int i = 0; i < hexStr.length(); i += 2) {
String str = hexStr.substring(i, i + 2);
output.append((char) Integer.parseInt(str, 16));
}
return output.toString();
}
public static String geTimeStample(){
// 获取当前时间,指定时区为UTC
ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC"));
// 定义时间格式(包含时区信息)
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss").withZone(ZoneId.of("UTC"));
// 格式化当前时间为指定格式(包含时区)
String formattedDateTime = now.format(formatter);
String Year,Month,Day,Hour,Minute,Second;
Year = StringUtils.leftPad(Integer.toHexString(Integer.parseInt(formattedDateTime.substring(0,4))),4,"0");
Month = StringUtils.leftPad(Integer.toHexString(Integer.parseInt(formattedDateTime.substring(4,6))),2,"0");
Day = StringUtils.leftPad(Integer.toHexString(Integer.parseInt(formattedDateTime.substring(6,8))),2,"0");
Hour = StringUtils.leftPad(Integer.toHexString(Integer.parseInt(formattedDateTime.substring(8,10))),2,"0");
Minute = StringUtils.leftPad(Integer.toHexString(Integer.parseInt(formattedDateTime.substring(10,12))),2,"0");
Second = StringUtils.leftPad(Integer.toHexString(Integer.parseInt(formattedDateTime.substring(12,14))),2,"0");
return Year + Month + Day + Hour + Minute + Second;
}
public static String asciiToHex(String asciiStr) {//ascii转换成Hex16进制字符串
char[] chars = asciiStr.toCharArray();
StringBuilder hex = new StringBuilder();
for (char ch : chars) {
hex.append(Integer.toHexString((int) ch));
}
//System.out.println("hex: "+hex.toString());
return hex.toString();
}
}启动类:
package com.example.tcpserverdemo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class TcpserverdemoApplication {
public static void main(String[] args) {
SpringApplication.run(TcpserverdemoApplication.class, args);
}
}
properties文件
tcp.server.port=9900
spring.task.execution.pool.core-size=10
spring.task.execution.pool.max-size=100
spring.task.execution.pool.queue-capacity=500
spring.task.execution.pool.keep-alive=60
spring.task.thread-name-prefix=task客户端代码如下:
package org.example.tcpClient;
import org.apache.http.util.TextUtils;
import org.apache.jmeter.config.Arguments;
import org.apache.jmeter.protocol.java.sampler.AbstractJavaSamplerClient;
import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.log4j.PropertyConfigurator;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Properties;
import org.apache.commons.lang3.StringUtils;
public class TcpClient extends AbstractJavaSamplerClient{
private static void initLog4j() {
Properties prop = new Properties();
prop.setProperty("log4j.rootLogger", "DEBUG, CONSOLE");
prop.setProperty("log4j.appender.CONSOLE", "org.apache.log4j.ConsoleAppender");
prop.setProperty("log4j.appender.CONSOLE.layout", "org.apache.log4j.PatternLayout");
prop.setProperty("log4j.appender.CONSOLE.layout.ConversionPattern", "%d{HH:mm:ss,SSS} [%t] %-5p %C{1} : %m%n");
PropertyConfigurator.configure(prop);
}
@Override
public Arguments getDefaultParameters() {
Arguments params = new Arguments();
params.addArgument("ip", "127.0.0.1");
params.addArgument("port", "9900");
params.addArgument("req", "JAYC 000000A41001746573743030303030303031000000000000000001313233343536373839303132333435363738393076312E322E3000000000641A04775C772F01000007E9040D022632EFEF");
params.addArgument("command", "1001");
params.addArgument("deviceSeq", "test00000001");
params.addArgument("netWork", "01");
params.addArgument("simCard", "test00000001");
params.addArgument("deviceVersion", "v1.2.0");
params.addArgument("deviceBattery", "100");
params.addArgument("deviceTemp", "26");
params.addArgument("deviceSignal", "04");
params.addArgument("deviceSpaceTotal", "30556");
params.addArgument("deviceSpaceFree", "30511");
params.addArgument("deviceStatus", "01");
params.addArgument("deviceErrorCode", "0000");
params.addArgument("timeStample", "20250413012059");
return params;
}
// 每个线程测试前执行一次,做一些初始化工作??
@Override
public void setupTest(JavaSamplerContext arg0) {
initLog4j();
}
// 开始测试,从arg0参数可以获得参数值;
@Override
public SampleResult runTest(JavaSamplerContext arg0) {
/** //请求参数
args1.addArgument("command", "1001");
args1.addArgument("deviceSeq", "test00000001");
args1.addArgument("netWork", "01");
args1.addArgument("simCard", "12345678901234567890");
args1.addArgument("deviceVersion", "v1.2.0");
args1.addArgument("deviceBattery", "100");
args1.addArgument("deviceTemp", "26");
args1.addArgument("deviceSignal", "04");
args1.addArgument("deviceSpaceTotal", "30556");
args1.addArgument("deviceSpaceFree", "30511");
args1.addArgument("deviceStatus", "01");
args1.addArgument("deviceErrorCode", "0000");
args1.addArgument("timeStample", "20250413012059");
StringUtils.rightPad(parameter1,parameter2,parameter3);
*/
String ip = arg0.getParameter("ip");
int port = Integer.valueOf(arg0.getParameter("port"));
String req = arg0.getParameter("req");
String command = arg0.getParameter("command");
String deviceSeq = asciiToHex(StringUtils.rightPad(arg0.getParameter("deviceSeq"),20," "));
String netWork = arg0.getParameter("netWork");
String simCard = asciiToHex(StringUtils.rightPad(arg0.getParameter("simCard"),20," "));
String deviceVersion = asciiToHex(StringUtils.rightPad(arg0.getParameter("deviceVersion"),10," "));
String deviceBattery = Integer.toHexString(Integer.parseInt(arg0.getParameter("deviceBattery")));
String deviceTemp = Integer.toHexString(Integer.parseInt(arg0.getParameter("deviceTemp")));
String deviceSignal = arg0.getParameter("deviceSignal");
String deviceSpaceTotal = Integer.toHexString(Integer.parseInt(arg0.getParameter("deviceSpaceTotal")));
String deviceSpaceFree = Integer.toHexString(Integer.parseInt(arg0.getParameter("deviceSpaceFree")));
String deviceStatus = arg0.getParameter("deviceStatus");
String deviceErrorCode = arg0.getParameter("deviceErrorCode");
String timeStample = arg0.getParameter("timeStample");
StringBuilder sb = new StringBuilder(req);
sb.replace(20,24,command)
.replace(24,64,deviceSeq)
.replace(64,66,netWork)
.replace(66,106,simCard)
.replace(106,126,deviceVersion)
.replace(126,128,deviceBattery)
.replace(128,130,deviceTemp)
.replace(130,132,deviceSignal)
.replace(132,136,deviceSpaceTotal)
.replace(136,140,deviceSpaceFree)
.replace(140,142,deviceStatus)
.replace(142,146,deviceErrorCode)
.replace(146,160,geTimeStampleInput(timeStample));
String content = sb.toString();
content = content.replace("JAYC 000000A4","JAYC " + StringUtils.leftPad(Integer.toHexString(content.length()),8,"0"));
System.out.println("content: " + content.toUpperCase());
SampleResult sr = new SampleResult();
sr.setSamplerData(content);////////
String resultData = "";
try {
sr.sampleStart();
resultData = send_tcp_socket(ip, port, content.toUpperCase());
} catch (Exception err) {
err.printStackTrace();
sr.sampleEnd();
sr.setResponseData("exception:" + err.getMessage(), null);
sr.setDataType(SampleResult.TEXT);
sr.setSuccessful(false);
return sr;
}
sr.sampleEnd();
if (resultData == null) {
System.out.println("result data is null");
sr.setResponseData("result data is null", null);
sr.setDataType(SampleResult.TEXT);
sr.setSuccessful(false);
return sr;
}
sr.setResponseData(resultData, null);
sr.setDataType(SampleResult.TEXT);
//断言
if (resultData.indexOf("result: 0000") > 0) {
sr.setSuccessful(true);
} else {
sr.setSuccessful(false);
}
return sr;
}
// 测试结束时调用;
public void teardownTest(JavaSamplerContext arg0) {
// end = System.currentTimeMillis();
// 总体耗时
// System.err.println("cost time:" + (end - start) + "毫秒");
}
public static String send_tcp_socket(String ip, int port, String msg) throws Exception {
int REQ_HEAD_LENTH = 20;//读取报文前20位,其最后8位是报文总长度
String rspContent = "";
Socket socket = null;
InputStream in;
OutputStream out;
String headerString,bodyLength;
try {
//发送请求
socket = new Socket(ip, port);
socket.setSoTimeout(60000);
socket.setSoLinger(true, 3);
socket.setReuseAddress(true);
socket.setTcpNoDelay(true);
in = socket.getInputStream();
out = socket.getOutputStream();
out.write(msg.getBytes("GBK"));
//out.write(hexStrToBinaryStr(msg));
out.flush();
// 读取数据,读取头部长度
int read_len = 0, cur_len = 0; // 记录当前读取的长度
byte[] reqHead_byte = new byte[REQ_HEAD_LENTH];
do {
try {
cur_len = in.read(reqHead_byte, read_len, REQ_HEAD_LENTH - read_len);
read_len += cur_len;
} catch (IndexOutOfBoundsException e) {
e.printStackTrace();
break;
}
} while (read_len < REQ_HEAD_LENTH);
headerString = hexToAscii(BinaryToHexString(reqHead_byte).replace(" ",""));
System.out.println("读取响应报文的头部: " + headerString);
bodyLength = headerString.substring(12);//最后8位是报文总长度
System.out.println("读取响应报文的长度: " + bodyLength);
int reqLen = Integer.valueOf(hexto10(bodyLength)) - 20;//读取响应报文长度
System.out.println("请求响应的body长度: " + reqLen);
//headerString = BinaryToHexString(reqHead_byte).replace(" ","");
//bodyLength = headerString.substring(12);
//System.out.println(bodyLength);
//int reqLen = Integer.valueOf(hexto10(bodyLength));//读取返回报文总长度
//reqLen = reqLen-20;//读取body长度
// 读取内容
byte[] reqContent_byte = new byte[reqLen];
read_len = 0;
cur_len = 0;
try {
do {
cur_len = in.read(reqContent_byte, read_len, reqLen - read_len);
read_len += cur_len;
} while (read_len < reqLen);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("headerString: " + headerString);
rspContent = headerString + new String(reqContent_byte, "GBK");;//头部+body
} catch (Exception err) {
err.printStackTrace();
rspContent = err.getMessage();
} finally {
try {
if (socket != null && !socket.isClosed())
socket.close();
} catch (Exception err) {
err.printStackTrace();
}
}
System.out.println(rspContent);
return responseToChar(rspContent);
}
public static void main(String[] args) {
TcpClient b2c = new TcpClient();
Arguments args1 = new Arguments();
args1.addArgument("ip", "127.0.0.1");
args1.addArgument("port", "9900");
args1.addArgument("req", "JAYC 000000A41001746573743030303030303031000000000000000001313233343536373839303132333435363738393076312E322E3000000000641A04775C772F01000007E9040D022632EFEF");//报文
/** 报文解析
* 报文:JAYC 000000A41001746573743030303030303031000000000000000001313233343536373839303132333435363738393076312E322E3000000000641A04775C772F01000007E9040D022632EFEF
* JAYC 报文头
* 000000A4 length: 报文长度,A4表示十进制的164,即整个报文长度164
* 1001 CMD: 设备登录
* 7465737430303030303030310000000000000000 设备出厂序列号,即test00000001
* 01 上网方式:01表示设备通过IoT物联网卡上网(即SIM卡) * 02表示设备通过WiFi上网
* 3132333435363738393031323334353637383930 物联网卡卡号/WiFi MAC地址
* 76312E322E3000000000 设备版本号v1.2.0
* 64 设备当前电量,100%
* 1A 设备环境温度℃,26℃
* 04 设备信号强度/上网质量(WiFi信号格数)
* 775C 设备存储空间Total,总存储容量30556MB,即32GB
* 772F 设备存储空间剩余,总存储容量30511MB
* 01 设备状态,01表示在线online * 02表示掉线offline * 03表示设备在线但异常
* 0000 错误码
* 07E9040D022632 时间戳 2025041302380
* EFEF 报文结束符
* */
//请求参数
args1.addArgument("command", "1001");
args1.addArgument("deviceSeq", "test00000001");
args1.addArgument("netWork", "01");
args1.addArgument("simCard", "12345678901234567890");
args1.addArgument("deviceVersion", "v1.2.0");
args1.addArgument("deviceBattery", "100");
args1.addArgument("deviceTemp", "26");
args1.addArgument("deviceSignal", "04");
args1.addArgument("deviceSpaceTotal", "30556");
args1.addArgument("deviceSpaceFree", "30511");
args1.addArgument("deviceStatus", "01");
args1.addArgument("deviceErrorCode", "0000");
args1.addArgument("timeStample", "20250413012059");
JavaSamplerContext jsc = new JavaSamplerContext(args1);
b2c.setupTest(jsc);
SampleResult sr = b2c.runTest(jsc);
System.out.println("response data:" + sr.getResponseDataAsString());
}
public static String responseToChar(String inputString) throws UnsupportedEncodingException {
String header,command,result,token,mode,UIP,Port,
User,Passwd,undealCommand,Year,Month,Day,Hour,Minute,Second,End;
header = inputString.substring(0,20);
command = inputString.substring(20,24);
result = inputString.substring(24,28);
token = inputString.substring(28,68);
mode = inputString.substring(68,70);
UIP = inputString.substring(70,190);
Port = inputString.substring(190,238);
User = inputString.substring(238,302);
Passwd = inputString.substring(302,398);
undealCommand = inputString.substring(398,402);
Year = inputString.substring(402,406);
Month = inputString.substring(406,408);
Day = inputString.substring(408,410);
Hour = inputString.substring(410,412);
Minute = inputString.substring(412,414);
Second = inputString.substring(414,416);
End = inputString.substring(416,420);
return "header: " + header + //头部
"\n" + "CMD: " + command + " 登录回复" + //表示登录回复
"\n" + "result: " + result + //错误码
"\n" + "token: " + token + //设备token(后续其余的命令需要用到)
"\n" + "mode: " + mode + //上传模式(默认OSS)1.FTP 2.OSS 3、AWZ(亚马逊上传)
"\n" + "UIP: " + hexToAscii(UIP) + //1.设备的IP地址 2、OSS的链接地址
"\n" + "Port: " + hexToAscii(Port) + //1.设备的端口 2、OSS下是bucket name
"\n" + "User: " +hexToAscii(User) + //用户名,OSS下是accessKey
"\n" + "Passwd: " + hexToAscii(Passwd) + //密码,OSS下是secret Key
"\n" + "undealCommand: " + hexto10(undealCommand) + //是否有指令0:没有 1:有
"\n" + "Year: " + hexto10(Year) + //年
"\n" + "Month: " + hexto10(Month) + //月
"\n" + "Day: " + hexto10(Day) + //日
"\n" + "Hour: " + hexto10(Hour) + //时
"\n" + "Minute: " + hexto10(Minute) + //分
"\n" + "Second: " + hexto10(Second) + //秒
"\n" + "End: " + End; //结束符
}
public static String hexto10(String inputString) {
int decimal = Integer.parseInt(inputString, 16);
//System.out.println("16进制数: " + inputString + " ,对应的10进制数: " + decimal);
return String.valueOf(decimal);
}
public static String hextochar(String inputString) {
char charValue = (char) Integer.parseInt(inputString,16);
//System.out.println("16进制数: " + inputString + " ,对应的char: " + charValue);
return String.valueOf(charValue);
}
public static String hexToAscii(String hexStr) {//Hex16进制字符串转换成ascii
StringBuilder output = new StringBuilder("");
for (int i = 0; i < hexStr.length(); i += 2) {
String str = hexStr.substring(i, i + 2);
output.append((char) Integer.parseInt(str, 16));
}
return output.toString();
}
public static String tenToAscii(String str){//imei转成ASCII
String result = "";
for (int i = 0; i < str.length(); i++) {
char ch = str.charAt(i); // 获取每个字符
int ascii = (int) ch; // 强制转换成 ASCII 值
System.out.println(ch + " : " + ascii);
result += (char) ascii;
}
return result;
}
public static String asciiToHex(String asciiStr) {//ascii转换成Hex16进制字符串
char[] chars = asciiStr.toCharArray();
StringBuilder hex = new StringBuilder();
for (char ch : chars) {
hex.append(Integer.toHexString((int) ch));
}
//System.out.println("hex: "+hex.toString());
return hex.toString();
}
/**
* 将十六进制的字符串转换成字节数组
*
* @param hexString
* @return
*/
public static byte[] hexStrToBinaryStr(String hexString) {
if (TextUtils.isEmpty(hexString)) {
return null;
}
hexString = hexString.replaceAll(" ", "");
int len = hexString.length();
int index = 0;
byte[] bytes = new byte[len / 2];
while (index < len) {
String sub = hexString.substring(index, index + 2);
bytes[index/2] = (byte)Integer.parseInt(sub,16);
index += 2;
}
return bytes;
}
/**
* 将字节数组转换成十六进制的字符串
*
* @return
*/
public static String BinaryToHexString(byte[] bytes) {
String hexStr = "0123456789ABCDEF";
String result = "";
String hex = "";
for (byte b : bytes) {
hex = String.valueOf(hexStr.charAt((b & 0xF0) >> 4));
hex += String.valueOf(hexStr.charAt(b & 0x0F));
result += hex + " ";
}
return result;
}
public static String geTimeStample(int jetLag){
// 获取当前时间,指定时区为UTC
ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC"));
// 定义时间格式(包含时区信息)
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss").withZone(ZoneId.of("UTC"+jetLag));
// 格式化当前时间为指定格式(包含时区)
String formattedDateTime = now.format(formatter);
String Year,Month,Day,Hour,Minute,Second;
Year = Integer.toHexString(Integer.parseInt(formattedDateTime.substring(0,4)));
Month = Integer.toHexString(Integer.parseInt(formattedDateTime.substring(4,6)));
Day = Integer.toHexString(Integer.parseInt(formattedDateTime.substring(6,8)));
Hour = Integer.toHexString(Integer.parseInt(formattedDateTime.substring(8,10)));
Minute = Integer.toHexString(Integer.parseInt(formattedDateTime.substring(10,12)));
Second = Integer.toHexString(Integer.parseInt(formattedDateTime.substring(12,14)));
return Year + Month + Day + Hour + Minute + Second;
}
public static String geTimeStampleInput(String timeStampleInput){
System.out.println("timeStampleInput: " + timeStampleInput);
String Year,Month,Day,Hour,Minute,Second;
Year = StringUtils.leftPad(Integer.toHexString(Integer.parseInt(timeStampleInput.substring(0,4))),4,"0");
Month = StringUtils.leftPad(Integer.toHexString(Integer.parseInt(timeStampleInput.substring(4,6))),2,"0");
Day = StringUtils.leftPad(Integer.toHexString(Integer.parseInt(timeStampleInput.substring(6,8))),2,"0");
Hour = StringUtils.leftPad(Integer.toHexString(Integer.parseInt(timeStampleInput.substring(8,10))),2,"0");
Minute = StringUtils.leftPad(Integer.toHexString(Integer.parseInt(timeStampleInput.substring(10,12))),2,"0");
Second = StringUtils.leftPad(Integer.toHexString(Integer.parseInt(timeStampleInput.substring(12,14))),2,"0");
return Year + Month + Day + Hour + Minute + Second;
}
}
5.环境部署
服务端:
直接idea启动即可

日志如下:

调试客户端:

客户端打包
直接双击package打包



导入jmeter
复制到jmeter的lib/ext目录下

重启jmeter
任意新增一个java请求

6.成果检验
如下图,java请求已经可以勾选我们自定义的脚本了

代码只做简单断言,如有其他需求也可自定义

填入参数

测试脚本请求响应成功
