Administrator
发布于 2025-04-15 / 20 阅读
1
2

Jmeter二次开发--java自定义TCP客户端请求

引言

在如今的软件系统中,各个端存在各种网络协议进行交互,比如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请求已经可以勾选我们自定义的脚本了

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

填入参数

测试脚本请求响应成功


评论