news 2026/9/29 9:11:20

JMeter性能测试全链路:从脚本设计、参数化断言到Linux压测实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JMeter性能测试全链路:从脚本设计、参数化断言到Linux压测实战

刚入行的时候,我拿Jmeter做性能测试就是看教程往线程组里填数字,500个线程、循环10次,一跑起来聚合报告里全是红色的错误,堆了一大堆HttpHostConnectException,当时完全懵了。后来翻了很久的资料,又拿真实接口反复试,才慢慢明白性能测试不是把并发数调大就完事,Jmeter的线程组参数、断言逻辑、录制方式、命令行执行和结果解读,每一步都有它自己的门道。

这篇文章我打算彻底拆一遍Jmeter做性能测试的完整链路,从安装配置、脚本设计、参数化与BeanShell断言、HTTPS代理录制、命令行压测到Linux环境下实时查看响应内容,最后把高频的HttpHostConnectException报错根因也拉出来捋清楚。不管你是刚接触接口测试还是已经能独立写脚本,按着这条线走一遍至少能少踩一半的坑。

1. 环境准备:JDK版本选型与Jmeter安装的常见坑

1.1 Jmeter版本和JDK的匹配关系

很多新手在Jmeter安装上卡住,第一反应是“去官网下载”,结果装了之后一启动就闪退或者报UnsupportedClassVersionError。问题基本都出在JDK版本上。

Jmeter本身是Java应用,底层走的是Swing和Java类库,JDK版本低了或者高了都可能起不来。以目前主流的Jmeter 5.6.3为例,官方的要求是Java 8+,但实际用下来Java 8和Java 11是最稳妥的组合。Java 17也测过,绝大多数功能OK,但个别插件和旧版依赖会出现兼容性问题,比如某些带签名证书的第三方jar加载失败。

如果你去运行jmeter.bat(Windows)或者jmeter.sh(Linux),系统默认会去PATH里找java,如果本机装了多个Java版本,Jmeter未必用的是你希望的那个。所以我的习惯是在启动脚本里显式指定JAVA_HOME。

# Linux下临时指定 export JAVA_HOME=/usr/local/jdk-11.0.21 export PATH=$JAVA_HOME/bin:$PATH ./jmeter.sh -v

校验版本:

java -version ./jmeter.sh -v # 能看到Jmeter版本、Java版本和系统架构信息

1.2 目录结构和启动方式

解压之后不需要专门安装,看几个关键目录就够了:

  • bin/:启动脚本、配置文件(jmeter.properties、log4j2.xml)都在这里
  • lib/:所有依赖jar包,扩展插件也放这里
  • extras/:一些第三方Ant/Maven的集成交互脚本
  • docs/:本地API文档

启动时建议先看一眼bin目录下的jmeter.properties文件,里面有大量默认配置,刚上手不用全改,但有一个参数可以从第一天就改掉——界面语言和字体,避免中文乱码或者窗口拥挤的问题。

Jmeter GUI模式是学习期的主力,压测执行阶段我们基本用命令行模式。记住一个原则:GUI是用来调试脚本和看结果的,不是用来发压力的。

提示:如果启动后没有看到图形界面,多半是Java环境变量配置不对。Windows下直接在bin目录双击jmeter.bat,如果有黑窗一闪而过,在命令行进到bin目录再执行jmeter.bat,错误日志就会留在屏幕上,通常是JAVA_HOME路径写错了。

2. 测试计划的核心骨架:线程组、Sampler与监听器的正确搭法

2.1 线程组三要素的取舍逻辑

打开Jmeter左侧面板,测试计划下面加一个线程组,里面有几个核心参数:线程数、Ramp-up时间、循环次数。这三个参数是性能脚本设计里最让新手纠结的地方。

线程数代表并发用户数,Ramp-up代表多长时间内把线程全部启动起来,循环次数代表每个线程执行完一遍脚本后是否重复执行。

我见过很多“压测翻车现场”都是因为这几个参数没想清楚导致的。比如想模拟100个用户同时抢购,结果把100个线程和Ramp-up=0设在一起,瞬间建立100个连接,目标服务直接被打爆,错误率飙升到80%,最后报告根本没参考价值。

正确考虑逻辑是这样的:

  1. 线程数给的是目标负载值,不是一个可以随便拍脑袋的数字。在压测前你先要知道这个接口的预估QPS能力或者线上实际峰值,然后倒推线程数。

  2. Ramp-up建议设置成一个“抬升加载”的过程,比如线程数100,Ramp-up=100秒,意思就是每秒增加1个线程,这样你可以从聚合报告的Sample Time走势里看到性能是平滑下降还是突然拐点。

  3. 循环次数设置为“永远”还是固定次数,取决于你想压多久。用固定持续时间代替循环次数会更科学——Jmeter在线程组里有“持续时间”选项,设置600秒就是压10分钟。

建议新人默认做法:先用线程数=1,跑一遍脚本确认没有错误;再逐步加线程数到目标值的四分之一,观察响应时间;最后再拉到满负载。不要一上来就满并发。

2.2 HTTP请求Sampler填写思路

线程组里加Sampler,最常用的就是HTTP请求。这里的配置信息必须包含协议、服务器名称或IP、端口、方法、路径和请求体。

有几个容易忽略的细节:

  • 协议默认是http,如果你的服务是https,必须手动改成https,否则会报错。
  • 服务器名称或IP,不要带http://前缀,只需要填域名或者IP。
  • 端口,如果是80和443可以留空,其他情况必须显式填上。
  • Content-Type,在“参数”或者“消息体数据”中切换。如果是POST提交JSON,记得在HTTP头管理器里加一行Content-Type: application/json。

把请求从浏览器里抓出来填到Jmeter里的时候,新手最常见的错误是漏掉请求头中的Cookie、Token或者Accept字段。Cookie和Token可以直接在“HTTP头管理器”里硬编码,但更好的做法是把Token参数化,这个我们后面细说。

关于“跟随重定向”和“自动重定向”的坑:Jmeter的HTTP请求里有两个重定向选项。“自动重定向”只对GET和HEAD有效,按协议栈自动处理;“跟随重定向”会记录重定向过程中的每次请求。我做接口测试时一般勾选“跟随重定向”,因为可以看到重定向的完整路径,排查问题时信息更全。

2.3 监听器怎么选

监听器是压测结束后你分析报告的关键。初学者喜欢把每一个监听器都加一遍,看结果树、聚合报告、表格查看结果、图形结果全堆在一块。一旦压测时间久、sample多了,监听器本身会成为Jmeter的性能瓶颈,因为每个监听器都在内存里积累数据。

我的建议是:

  • 调试阶段用“查看结果树”,确认每个请求体和响应体是否符合预期
  • 压测执行阶段去掉结果树或者只加一个“简单数据写入器”,把原始sample输出成jtl文件
  • 压测结束用“聚合报告”和“Summary Report”看统计指标

监听器不是越多越好,压测阶段保留一个聚合报告就够了,其余数据从jtl或html报告里拿。

3. 参数化与断言:让脚本具备读写能力的两个关键

3.1 参数化的三种常见方式

只用一个账号、一条数据反复压接口,结果当然也能跑,但说服力不够强。因为真实场景里每个用户的数据都是独立的,服务端可能有用户维度的缓存,拿同一条数据压出来的TPS并不等于真实容量。

Jmeter的参数化有三个层次:

第一层:用户自定义变量。适用场景是配置一些全局静态参数,比如host、端口、公共请求头。放在“用户定义的变量”里,用${变量名}引用。注意这个变量在线程组启动时就固定了,每个线程取值相同。

第二层:CSV数据文件设置。这是最核心的参数化方式。准备一个csv文件,每一行是一条用户数据,配置CSV Data Set Config之后,每个线程每次循环都会取一行数据。

# users.csv username,password,token tester01,pass123,abc001 tester02,pass123,abc002 tester03,pass123,abc003

CSV Data Set Config里有两个容易搞混的参数:线程共享模式和EOF遇到文件结束符时行为。默认共享模式会让所有线程按顺序读同一份文件,如果希望每个线程独占一行数据,可以改成“当前线程组”。文件读完后默认可能直接结束循环,所以配置不当会导致线程提前跑完。

第三层:函数助手。Jmeter自带很多简单但实用的函数,比如${__time()}拿当前时间戳,${__Random(1,1000)}生成随机数,${__UUID()}生成UUID。这个在构造唯一标识、时间戳参数的时候特别好用,不需要额外写代码。

3.2 响应断言与Duration断言

压测脚本里如果没有断言,基本等于盲测,再大的错误率你也看不出来。Jmeter的断言体系分成两个核心部分:响应断言和Duration断言。

响应断言的本质是校验响应内容。可以按“文本”模式匹配关键词,比如登录接口返回JSON里带"success":true,那就在“响应文本”模式里填"success":true,匹配规则选“包括”。

我还要建议加上状态码校验。做法有两种,一种是在响应断言里加一个“响应码”匹配200;另一种是用HTTP取样器自带的“响应状态码”监听点,但那个不作为断言触发失败逻辑。实际使用中,服务端偶尔会返回301/302甚至401,所以断言还是得手动加。

Duration断言是用来控制响应时间上限的。例如你设置的阈值是3000毫秒,一旦某次sample响应时间超过这个值,该请求会被标记为失败。这个断言在性能压测里的意义比功能测试大得多,因为它能自动帮你标出“响应时间超过容忍度的请求”,在报告里直接看错误率就知道SLA是否达标。

3.3 BeanShell断言的实战写法

近年来Jmeter社区里BeanShell断言是高频搜索词,因为有些业务校验无法用简单文本匹配搞定,比如需要校验时间戳是否过期、签名是否正确、响应里的某个数值是否满足范围。这个时候就需要脚本断言。

BeanShell是Java语法精简版脚本,Jmeter里可以直接塞一段逻辑去访问之前请求的响应内容和变量。常用写法如下:

import org.json.JSONObject; // 获取上一个取样器的响应字符串 String response = prev.getResponseDataAsString(); // 将字符串转为JSON JSONObject obj = new JSONObject(response); // 提取code字段 String code = obj.getString("code"); // 提取msg字段 String msg = obj.optString("msg", ""); if (!"200".equals(code)) { // 标记当前断言失败,并输出失败原因 Failure = true; FailureMessage = "业务状态码异常,code=" + code + ", msg=" + msg; } // 顺便把token提取出来,存成变量给后续请求用 String token = obj.optString("token", ""); vars.put("token", token);

注意上面用了prev,这是Jmeter的BeanShell断言上下文里预置的变量,代表上一个取样器结果对象。vars是上下文变量对象,可以存值也可以取值。

BeanShell断言里判断失败的关键是设置Failure = true,而FailureMessage是你要展示的错误信息。这跟JSR223中AssertionResult.setFailure()的写法不一样,很多人第一次用BeanShell断言失败就是因为只写了return之类的逻辑,没有真正把Failure标记为true。

Jmeter 5.6.3版本里,BeanShell脚本仍然能用,但官方更推荐JSR223+Groovy的模式,因为Groovy脚本的执行性能和资源占用比BeanShell好不少。不过BeanShell脚本的语法对Java后端出身的人来说几乎零成本,测试团队内部做快速验证时很顺手,这也是它至今高频出现在搜索热词里的原因。

提示:写断言脚本时,一定先开着“查看结果树”调试,逐步打印中间变量。不要相信一遍就能写对,JSON解析最常报的就是某个字段不存在导致的空指针。

4. 代理录制HTTPS脚本:证书体系与录制流程全拆解

4.1 录制还是手写,这是策略问题

如果你面对的是几十个接口的复杂业务流程,比如登录、购物车、下单、支付、退款,全手写HTTP Sampler费时费力还容易漏字段。Jmeter的HTTP代理服务器提供了一个自动录制方案:启动一个本地代理端口,浏览器走这个代理访问业务站点,所有请求都会被Jmeter捕获并自动生成取样器脚本。Jmeter代理录制HTTPS脚本的配置流程也因此成了很多人搜索的高频主题。

录制模式的本质是“中间人代理”。你的浏览器把HTTPS请求发给Jmeter,Jmeter再转发给目标服务器。这里牵扯到一个根问题——浏览器不信任Jmeter的根证书,所以HTTPS临时会话根本建立不起来。

解决思路就是让浏览器信任这个根证书。步骤分两段:

  1. 用Jmeter的keytool生成一个SSL根证书
  2. 把根证书导入到浏览器的受信任证书列表里

4.2 证书生成与导入的完整过程

Jmeter自己带了一个工具类,不用手工敲keytool命令。首次启动HTTP代理服务器时,Jmeter会在bin目录下生成一个ApacheJMeterTemporaryRootCA.crt文件。如果你的jmeter.properties里没有特殊配置,这个证书文件生成一次后长期有效。

Windows导入证书的路径:

  • 控制面板 → Internet选项 → 内容 → 证书 → 受信任的根证书颁发机构 → 导入
  • 选择ApacheJMeterTemporaryRootCA.crt,确定即可

Mac下是钥匙串访问,导入后将该证书信任级别改为“始终信任”。

Linux下可能需要通过update-ca-certificates或者直接放到系统证书目录,看浏览器类型,Chrome和Firefox的管理入口不同。

证书导入失败会怎样?表现是录制的时候页面打不开,浏览器提示“您的连接不是私密连接”。这基本说明证书没进受信任列表,重新导入一次即可。

4.3 代理录制的配置流程

在Jmeter GUI里做如下配置:

  • 测试计划下右键 → 添加 → 非测试元件 → HTTP代理服务器
  • 端口填默认的8888,目标控制器选“测试计划 > 线程组”
  • 分组设置为“每个组放入一个新的控制器”,这样录制出的请求会按事务分组,后续调整脚本效率高
  • 点击启动,保持代理在运行状态

浏览器端配置:

  • 设置HTTP代理,指向127.0.0.1:8888(或Jmeter所在机器的IP)
  • 有些时候还要指HTTPS代理,因为现代浏览器默认代理设置是分开的
  • 关闭PAC代理脚本,否则不走本地代理

录完以后,必须做的事是关闭浏览器代理并停掉Jmeter代理服务器。好多人在录完直接打开其他网页,结果把一堆无关请求录进了脚本,测试计划变得一塌糊涂。

录出来的脚本有一个通病:它会录进很多静态资源请求(图片、CSS、JS)。如果是纯接口压测,这类资源请求要删掉或者用正则表达式把URL过滤掉。代理服务器里可以直接填入排除模式:

.*\.(js|css|png|jpg|jpeg|gif|ico).*

这样录制的脚本就干净很多。

提示:代理录制只是生成脚本的辅助手段。录制完成后的脚本,仍需手动加上断言、参数化和思考时间,才能用于压测。直接拿裸脚本去压,数据说服力很弱。

5. 命令行压测与结果分析:从GUI到无头模式的必走路径

5.1 为什么正式压测不能用GUI

GUI模式下Jmeter需要渲染图形界面的实时结果,监听器一边接收数据一边画图,本身就要消耗大量CPU和内存。当并发数超过一定量级,Jmeter自身的资源消耗会干扰压测结果,甚至导致线程卡死、测试中断。正式压测一定要用命令行模式。

另外一个原因是在Linux服务器上做压测时,根本没有图形环境。压测机部署在哪,就直接在那个环境跑命令行。

命令行执行的基础参数:

./jmeter.sh -n -t /path/to/script.jmx -l /path/to/result.jtl -j /path/to/jmeter.log
  • -n:非GUI模式
  • -t:指定jmx测试脚本
  • -l:输出原始结果到jtl文件
  • -j:指定日志路径
  • -e:脚本执行完成后生成HTML报告(依赖-o)
  • -o:HTML报告输出目录

如果要指定并发数或者持续时间覆盖脚本里的设定,可以用-J参数传入属性,比如:

./jmeter.sh -n -t script.jmx -Jthreads=200 -Jduration=600 -l result.jtl -e -o report

这要求脚本里已经用${__P(threads)}读取了J属性,相当于把脚本固定成模板,并发数通过命令行动态传入。

5.2 测试报告生成(以Jmeter 5.6.3为例)

Jmeter从3.x版本开始自带HTML报告生成器,5.6.3版本的报告模板已经相对成熟。执行语句:

./jmeter.sh -n -t api_test.jmx -l result.jtl -e -o ./html_report

报告里包含了很多维度的图表,最常见的看这几个:

  • APDEX指数:应用性能指数,0到1之间,越接近1越好,低于0.9就要警惕
  • 吞吐量:每秒处理的请求数,这个数字直接换算成服务能力
  • 响应时间各分位数:90th pct、95th pct、99th pct分别代表不同压力下的尾延迟,99th pct更能反映极端情况下的用户感知

需要注意:-e -o报告是基于jtl文件生成的,jtl文件必须在压测过程中完整记录所有sample。如果压测停了才发现jtl文件不存在,那就只能重跑一次。所以压测执行前,一定要确认-l参数指定的结果文件路径可写。

5.3 聚合报告参数解读的视角

聚合报告里的字段新手常犯的错误是重点盯着Average响应时间看。这个数字受极值影响非常大,一个长时间超时的请求能把平均值拉得很高,掩盖大部分请求本来很快的事实。更科学的观察顺序是:

  1. 先看Error%是否为0,不为0就要去结果树或jtl里定位错误类型
  2. 再看Throughput,判断当前并发是否达到预期容量
  3. 然后看90% Line、99% Line,这两个是用户体感的真实上限
  4. 最后才看Average和Median,配合TPS走势做整体判断

表格里同期对比一下同一个接口在50并发、100并发、200并发下的指标:

并发数平均响应时间90% Line99% Line错误率TPS
5080ms120ms200ms0%480
100180ms280ms450ms0.2%510
200420ms650ms1200ms5%480

从这个表能一眼看出来,200并发时错误率陡增、99%响应时间飙升到1.2秒,说明系统瓶颈已经出现,这个接口的合理承载上限大概在100并发附近。

6. Linux命令行压测:排查响应内容与定位瓶颈的实战方法

6.1 压测过程中实时查看压测接口的响应内容

很多压测场景是在Linux服务器上执行的,命令行模式下没法直接看“查看结果树”。但你仍然可以实时看到每个请求的响应内容,办法有两个。

方法一:通过jtl文件实时跟踪尾部

压测执行时-l指定的jtl文件是持续写入的。在另一个终端里tail这个文件,能看到最新的sample记录时间戳、线程名、请求URL、响应码和字节数。

tail -f /path/to/result.jtl

如果觉得jtl格式太乱,可以在脚本里加一个“简单数据写入器”,配置只输出关键字段。这样tail出来的内容就是紧凑的表格行。

方法二:在脚本里加一个JSR223后置监听器,把响应内容写到日志

对特定接口,在HTTP请求下面加一个“JSR223 后置处理程序”,配合log4j2把响应内容输出到jmeter.log:

log.info("当前请求响应: " + prev.getResponseDataAsString());

这样在压测过程中,用tail -f jmeter.log就能看到特定接口的响应体。这个方法在排查“响应内容不符合预期但不报错”的场景特别有用,比如接口返回了200,但返回体是一个HTML错误提示页。

6.2 HttpHostConnectException的高频报错排查链路

热词里频繁出现的org.apache.http.conn.httphostconnectexception: connect to ...,几乎是每个性能测试新手都会碰到的一条错误。

这条报错的完整链路拆开看,核心是Jmeter所在机器和Nginx/应用服务器建立TCP连接失败。常见原因按出现频率排序:

  1. 目标端口没监听:应用没启动起来,或者端口写错了。用telnet或者nc测一下:
telnet 192.168.1.10 8080

如果端口不通,优先检查服务进程是否存在以及防火墙规则。

  1. 防火墙拦截了压测来源IP:Linux服务器上firewalld或iptables配置了放行规则。尤其在公司内网环境,压测机的IP经常被误当成外网攻击来源给禁IP。

  2. 后端反向代理的连接数打满了:Nginx的worker_connections达到上限,或者Tomcat/应用进程的acceptCount满了,新来的TCP连接直接被内核挂在队列里甚至丢弃。此时压测机这边看到的错误就是connect超时。

  3. 网络层的问题:跨网段压测时出现偶发的connect失败而本机压测不失败,多数是交换机或安全设备的连接跟踪表满了。

排查链路建议这样走:

  • 第一步:先复现错误,确定错误影响的比例
  • 第二步:在压测机上telnet目标端口,确认TCP链路是否通
  • 第三步:在目标服务器上执行netstat -antp | grep 端口,看TCP连接状态是否大量出现SYN_RECV、TIME_WAIT
  • 第四步:检查应用服务的连接数配置和队列长度
  • 第五步:如果是偶发间歇性失败,建议看压测机的dmesg有没有nf_conntrack表满的提示

提示:还有一种很常见的“假报错”是并发过高导致Jmeter自己这边的线程池排队超时。可以先在小并发下压测确认无误,再逐步加压,如果错误率随并发升高而升高,优先怀疑的就是连接数或服务端排队。

6.3 压测过程中系统资源监控的配合

单看Jmeter的聚合报告,只能说明“请求发出去后的结果”,却说不清楚瓶颈到底卡在哪个环节。所以正式压测时,目标服务器的系统资源监控必须同步启动。

推荐在目标机器上用这三条命令组合监控:

top -b -d 2 | grep java # 每2秒刷新一次Java进程CPU和内存占用 vmstat 2 # 查看CPU、内存、IO的完整统计 ss -s # 查看socket连接统计

如果压测到100并发时Java进程CPU已经吃到90%以上,说明瓶颈在应用层,需要优化代码或增加实例;如果CPU只有20%,但TPS上不去,那瓶颈可能在网络、数据库连接池或者线程阻塞,需要往更下层排查。

做性能测试的人,最怕的就是只会跑脚本拿报告,不结合服务端资源数据做交叉分析。没有资源数据的性能测试,报告写得再漂亮也站不住脚。

说起来,我在Linux下压测出现过一次很尴尬的情况:自己电脑用GUI跑脚本一切正常,放到Linux服务器上一跑全是Connect refused,排查了半天才意识到是测试计划里填的服务器地址是localhost,服务器上根本没有这个服务。所以脚本里尽量把host参数化,执行前先检查环境和目标地址,这个习惯能省下大把排查时间。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/29 9:11:13

计算机网络自顶向下习题答案PDF怎么用?版本对齐+OCR检索复习法

简介:《计算机网络:自顶向下方法》是计算机专业经典教材,这份配套习题答案(中文版)以单份PDF文档呈现,适合本科阶段学习计算机网络的学生、备考研友及需要巩固基础的自学者使用。答案按教材章节顺序组织&am…

作者头像 李华
网站建设 2026/9/29 9:10:13

嵌入式开发实战指南:从学习路线到性能优化与面试进阶

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/29 9:08:49

STM32CubeMX下载安装全流程:环境准备、固件包配置与故障排查

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/29 9:05:09

Prettier配置实战指南:终结代码风格之争,打造自动化格式化流程

前端项目里最没意义又最耗精力的争论,大概就是“代码风格”。用两个空格还是四个空格、字符串单引还是双引、尾逗号加不加,这些讨论一旦进入 Code Review,体感就像在泥潭里摔跤。我接手过不少前后端项目,几乎每一个都把大量 revie…

作者头像 李华
网站建设 2026/9/29 9:04:34

小米手机格机后NV损坏的硬件修复:I²C上拉电阻替换指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/29 9:03:34

智能车竞赛十六年赛题全梳理:从电磁循迹到多车协同的备赛门道

智驾竞速十六年:我把历届智能车竞赛赛题清单梳理了一遍,发现了很多备赛门道从2006年首届比赛到现在,全国大学生智能汽车竞赛已经走过了十几个年头。我一直觉得,这个比赛最迷人的地方不在于最终谁拿了国一,而在于每一届…

作者头像 李华