-
Notifications
You must be signed in to change notification settings - Fork 133
Expand file tree
/
Copy pathspeedtest.sh
More file actions
1536 lines (1282 loc) · 51.5 KB
/
speedtest.sh
File metadata and controls
1536 lines (1282 loc) · 51.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/bin/bash
# 中转网络链路测试工具
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
WHITE='\033[1;37m'
NC='\033[0m'
SHORT_CONNECT_TIMEOUT=5
SHORT_MAX_TIMEOUT=7
LONG_CONNECT_TIMEOUT=15
LONG_MAX_TIMEOUT=20
# 全局变量
TARGET_IP=""
TARGET_PORT="5201"
TEST_DURATION="30"
ROLE=""
# 端口冲突处理相关变量
STOPPED_PROCESS_PID=""
STOPPED_PROCESS_CMD=""
STOPPED_PROCESS_PORT=""
# 清理标志位,防止重复执行
CLEANUP_DONE=false
# 异常退出时的清理函数
cleanup_on_exit() {
# 防止重复执行清理
if [ "$CLEANUP_DONE" = true ]; then
return
fi
CLEANUP_DONE=true
# 停止可能运行的iperf3服务
pkill -f "iperf3.*-s" 2>/dev/null || true
# 恢复被临时停止的进程
restore_stopped_process
echo -e "\n${YELLOW}脚本已退出,清理完成${NC}"
}
# 统一下载函数
download_from_sources() {
local url="$1"
local target_path="$2"
if curl -fsSL --connect-timeout $SHORT_CONNECT_TIMEOUT --max-time $SHORT_MAX_TIMEOUT "$url" -o "$target_path"; then
echo -e "${GREEN}✓ 下载成功${NC}" >&2
return 0
else
echo -e "${RED}✗ 下载失败${NC}" >&2
return 1
fi
}
# 全局测试结果数据结构
declare -A TEST_RESULTS=(
# 延迟测试结果
["latency_min"]=""
["latency_avg"]=""
["latency_max"]=""
["latency_jitter"]=""
["packet_sent"]=""
["packet_received"]=""
# TCP上行测试结果
["tcp_up_speed_mbps"]=""
["tcp_up_speed_mibs"]=""
["tcp_up_transfer"]=""
["tcp_up_retrans"]=""
# TCP下行测试结果
["tcp_down_speed_mbps"]=""
["tcp_down_speed_mibs"]=""
["tcp_down_transfer"]=""
["tcp_down_retrans"]=""
# UDP上行测试结果
["udp_up_speed_mbps"]=""
["udp_up_speed_mibs"]=""
["udp_up_loss"]=""
["udp_up_jitter"]=""
# UDP下行测试结果
["udp_down_speed_mbps"]=""
["udp_down_speed_mibs"]=""
["udp_down_loss"]=""
["udp_down_jitter"]=""
)
# 辅助函数:安全设置测试结果
set_test_result() {
local key="$1"
local value="$2"
if [ -n "$value" ] && [ "$value" != "N/A" ]; then
TEST_RESULTS["$key"]="$value"
else
TEST_RESULTS["$key"]=""
fi
}
check_root() {
if [[ $EUID -ne 0 ]]; then
echo -e "${RED}错误: 此脚本需要 root 权限运行${NC}"
exit 1
fi
}
# 工具配置数组 - 定义所有需要的工具
declare -A REQUIRED_TOOLS=(
["iperf3"]="apt:iperf3"
["hping3"]="apt:hping3"
["bc"]="apt:bc"
["nc"]="apt:netcat-openbsd"
)
# 工具状态数组
declare -A TOOL_STATUS=()
# 检查单个工具是否存在
check_tool() {
local tool="$1"
if ! command -v "$tool" >/dev/null 2>&1; then
return 1
fi
return 0
}
# 检测所有工具状态
detect_all_tools() {
for tool in "${!REQUIRED_TOOLS[@]}"; do
if check_tool "$tool"; then
TOOL_STATUS["$tool"]="installed"
else
TOOL_STATUS["$tool"]="missing"
fi
done
}
# 获取缺失的工具列表
get_missing_tools() {
local missing_tools=()
for tool in "${!TOOL_STATUS[@]}"; do
if [ "${TOOL_STATUS[$tool]}" = "missing" ]; then
missing_tools+=("$tool")
fi
done
echo "${missing_tools[@]}"
}
# 安装单个APT工具
install_apt_tool() {
local tool="$1"
local package="$2"
echo -e "${BLUE}🔧 安装 $tool...${NC}"
# 设置非交互模式,防止安装时等待用户确认
if DEBIAN_FRONTEND=noninteractive apt-get install -y "$package" >/dev/null 2>&1; then
echo -e "${GREEN}✅ $tool 安装成功${NC}"
TOOL_STATUS["$tool"]="installed"
return 0
else
echo -e "${RED}✗ $tool 安装失败${NC}"
return 1
fi
}
# 安装缺失的工具
install_missing_tools() {
local missing_tools=($(get_missing_tools))
if [ ${#missing_tools[@]} -eq 0 ]; then
return 0
fi
echo -e "${YELLOW}📦 安装缺失工具: ${missing_tools[*]}${NC}"
# 更新包列表(非交互模式)
DEBIAN_FRONTEND=noninteractive apt-get update >/dev/null 2>&1
local install_failed=false
for tool in "${missing_tools[@]}"; do
local tool_config="${REQUIRED_TOOLS[$tool]}"
local install_type="${tool_config%%:*}"
local package_name="${tool_config##*:}"
case "$install_type" in
"apt")
if ! install_apt_tool "$tool" "$package_name"; then
install_failed=true
fi
;;
*)
echo -e "${RED}✗ 未知的安装类型: $install_type${NC}"
install_failed=true
;;
esac
done
if [ "$install_failed" = false ]; then
echo -e "${GREEN}✅ 工具安装完成${NC}"
fi
}
# 安装所需工具
install_required_tools() {
echo -e "${BLUE}🔍 检测工具状态...${NC}"
# 检测当前工具状态
detect_all_tools
# 安装缺失的工具
install_missing_tools
}
# 验证IP地址格式
validate_ip() {
local ip="$1"
if [[ $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
IFS='.' read -ra ADDR <<< "$ip"
for i in "${ADDR[@]}"; do
if [[ $i -gt 255 ]]; then
return 1
fi
done
return 0
elif [[ $ip =~ ^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
# 域名格式
return 0
else
return 1
fi
}
# 获取本机IP
get_public_ip() {
local ip=""
# 优先使用ipinfo.io
ip=$(curl -s --connect-timeout $SHORT_CONNECT_TIMEOUT --max-time $SHORT_MAX_TIMEOUT "https://ipinfo.io/ip" 2>/dev/null | tr -d '\n\r ')
if validate_ip "$ip"; then
echo "$ip"
return 0
fi
# 备用cloudflare trace
ip=$(curl -s --connect-timeout $SHORT_CONNECT_TIMEOUT --max-time $SHORT_MAX_TIMEOUT "https://www.cloudflare.com/cdn-cgi/trace" 2>/dev/null | grep "ip=" | cut -d'=' -f2 | tr -d '\n\r ')
if validate_ip "$ip"; then
echo "$ip"
return 0
fi
return 1
}
# 验证端口号
validate_port() {
local port="$1"
if [[ $port =~ ^[0-9]+$ ]] && [ "$port" -ge 1 ] && [ "$port" -le 65535 ]; then
return 0
else
return 1
fi
}
# 检测端口占用情况
check_port_usage() {
local port="$1"
local result=""
# 优先使用ss命令
if command -v ss >/dev/null 2>&1; then
result=$(ss -tlnp 2>/dev/null | grep ":$port ")
elif command -v netstat >/dev/null 2>&1; then
result=$(netstat -tlnp 2>/dev/null | grep ":$port ")
else
return 1
fi
if [ -n "$result" ]; then
echo "$result"
return 0
else
return 1
fi
}
# 从端口占用信息中提取进程信息
extract_process_info() {
local port_info="$1"
local pid=""
local cmd=""
# 从ss或netstat输出中提取PID和进程名
if echo "$port_info" | grep -q "pid="; then
# ss格式: users:(("进程名",pid=1234,fd=5))
pid=$(echo "$port_info" | grep -o 'pid=[0-9]\+' | cut -d'=' -f2)
cmd=$(echo "$port_info" | grep -o '(".*"' | sed 's/("//; s/".*//')
else
# netstat格式: 1234/进程名
local proc_info=$(echo "$port_info" | awk '{print $NF}' | grep -o '[0-9]\+/.*')
if [ -n "$proc_info" ]; then
pid=$(echo "$proc_info" | cut -d'/' -f1)
cmd=$(echo "$proc_info" | cut -d'/' -f2)
fi
fi
if [ -n "$pid" ] && [ -n "$cmd" ]; then
echo "$pid|$cmd"
return 0
else
return 1
fi
}
# 临时停止占用端口的进程
stop_port_process() {
local port="$1"
local port_info=$(check_port_usage "$port")
if [ -z "$port_info" ]; then
return 0 # 端口未被占用
fi
local process_info=$(extract_process_info "$port_info")
if [ -z "$process_info" ]; then
echo -e "${YELLOW}⚠️ 无法获取占用进程信息,跳过进程停止${NC}"
return 1
fi
local pid=$(echo "$process_info" | cut -d'|' -f1)
local cmd=$(echo "$process_info" | cut -d'|' -f2)
echo -e "${YELLOW}检测到端口 $port 被占用${NC}"
echo -e "${BLUE}占用进程: PID=$pid, 命令=$cmd${NC}"
echo ""
read -p "是否临时停止该进程以进行测试?测试完成后会自动恢复 (y/N): " confirm
if [[ $confirm =~ ^[Yy]$ ]]; then
# 获取完整的进程命令行用于恢复
local full_cmd=$(ps -p "$pid" -o args= 2>/dev/null | head -1)
if [ -z "$full_cmd" ]; then
full_cmd="$cmd" # 备用方案
fi
# 停止进程
if kill "$pid" 2>/dev/null; then
echo -e "${GREEN}✅ 进程已临时停止${NC}"
# 记录进程信息用于恢复
STOPPED_PROCESS_PID="$pid"
STOPPED_PROCESS_CMD="$full_cmd"
STOPPED_PROCESS_PORT="$port"
# 等待端口释放
sleep 2
# 验证端口是否已释放
if check_port_usage "$port" >/dev/null 2>&1; then
echo -e "${YELLOW}⚠️ 端口可能仍被占用,请手动检查${NC}"
return 1
else
echo -e "${GREEN}✅ 端口 $port 已释放${NC}"
return 0
fi
else
echo -e "${RED}✗ 无法停止进程 (PID: $pid)${NC}"
return 1
fi
else
echo -e "${YELLOW}用户选择不停止进程,请手动处理端口冲突或选择其他端口${NC}"
return 1
fi
}
# 恢复被停止的进程
restore_stopped_process() {
if [ -n "$STOPPED_PROCESS_CMD" ] && [ -n "$STOPPED_PROCESS_PORT" ]; then
echo -e "${BLUE}正在恢复被停止的进程...${NC}"
echo -e "${YELLOW}恢复命令: $STOPPED_PROCESS_CMD${NC}"
# 在后台启动进程
nohup $STOPPED_PROCESS_CMD >/dev/null 2>&1 &
local new_pid=$!
# 等待进程启动
sleep 3
# 检查进程是否成功启动并占用端口
if check_port_usage "$STOPPED_PROCESS_PORT" >/dev/null 2>&1; then
echo -e "${GREEN}✅ 进程已成功恢复 (新PID: $new_pid)${NC}"
else
echo -e "${YELLOW}⚠️ 进程恢复可能失败,请手动检查${NC}"
echo -e "${YELLOW} 原始命令: $STOPPED_PROCESS_CMD${NC}"
fi
# 清空记录
STOPPED_PROCESS_PID=""
STOPPED_PROCESS_CMD=""
STOPPED_PROCESS_PORT=""
fi
}
# 测试连通性
test_connectivity() {
local ip="$1"
local port="$2"
if nc -z -w3 "$ip" "$port" >/dev/null 2>&1; then
return 0
else
return 1
fi
}
# 服务端模式 - 启动服务端
landing_server_mode() {
clear
echo -e "${GREEN}=== 服务端 (开放测试) ===${NC}"
echo ""
# 输入监听端口
while true; do
read -p "监听测试端口 [默认5201]: " input_port
if [ -z "$input_port" ]; then
TARGET_PORT="5201"
elif validate_port "$input_port"; then
TARGET_PORT="$input_port"
else
echo -e "${RED}无效端口号,请输入1-65535之间的数字${NC}"
continue
fi
# 检测端口冲突并处理
echo -e "${YELLOW}检查端口 $TARGET_PORT 占用情况...${NC}"
if check_port_usage "$TARGET_PORT" >/dev/null 2>&1; then
if stop_port_process "$TARGET_PORT"; then
echo -e "${GREEN}✅ 端口 $TARGET_PORT 可用${NC}"
break
else
echo -e "${RED}端口 $TARGET_PORT 冲突未解决,请选择其他端口${NC}"
continue
fi
else
echo -e "${GREEN}✅ 端口 $TARGET_PORT 可用${NC}"
break
fi
done
echo ""
echo -e "${YELLOW}启动服务中...${NC}"
# 停止可能存在的iperf3进程
pkill -f "iperf3.*-s.*-p.*$TARGET_PORT" 2>/dev/null
# 启动iperf3服务端
if iperf3 -s -p "$TARGET_PORT" -D >/dev/null 2>&1; then
echo -e "${GREEN}✅ iperf3服务已启动 (端口$TARGET_PORT)${NC}"
# 只在服务运行期间设置临时trap
trap 'pkill -f "iperf3.*-s.*-p.*$TARGET_PORT" 2>/dev/null; restore_stopped_process; exit' INT TERM
else
echo -e "${RED}✗ iperf3服务启动失败${NC}"
# 恢复被临时停止的进程
restore_stopped_process
exit 1
fi
# 获取本机IP
local local_ip=$(get_public_ip || echo "获取失败")
echo -e "${BLUE}📋 服务端信息${NC}"
echo -e " IP地址: ${GREEN}$local_ip${NC}"
echo -e " 端口: ${GREEN}$TARGET_PORT${NC}"
echo ""
echo -e "${YELLOW}💡 请在客户端输入服务端IP: ${GREEN}$local_ip${NC}"
echo -e "${YELLOW} 请到客户端选择1. 客户端 (本机发起测试)...${NC}"
echo ""
echo -e "${WHITE}按任意键停止服务${NC}"
# 等待用户按键
read -n 1 -s
# 清除临时trap
trap - INT TERM
# 停止服务
pkill -f "iperf3.*-s.*-p.*$TARGET_PORT" 2>/dev/null
echo ""
echo -e "${GREEN}iperf3服务已停止${NC}"
# 恢复被临时停止的进程
restore_stopped_process
}
# 执行延迟测试
run_latency_tests() {
echo -e "${YELLOW}🟢 延迟测试${NC}"
echo ""
# 使用hping3进行TCP延迟测试
if check_tool "hping3"; then
echo -e "${GREEN}🚀 TCP应用层延迟测试 - 目标: ${TARGET_IP}:${TARGET_PORT}${NC}"
echo ""
# 后台执行测试,前台显示进度条
local temp_result=$(mktemp)
(hping3 -c "$TEST_DURATION" -i 1 -S -p "$TARGET_PORT" "$TARGET_IP" > "$temp_result" 2>&1) &
local test_pid=$!
show_progress_bar "$TEST_DURATION" "TCP延迟测试"
# 等待测试完成
wait $test_pid
local exit_code=$?
if [ $exit_code -eq 0 ]; then
local result=$(cat "$temp_result")
echo ""
echo -e "${BLUE}📋 测试数据:${NC}"
echo "$result"
# 解析TCP延迟统计和包统计
local stats_line=$(echo "$result" | grep "round-trip")
local packet_line=$(echo "$result" | grep "packets transmitted")
if [ -n "$stats_line" ] && [ -n "$packet_line" ]; then
# 提取延迟数据: min/avg/max
local stats=$(echo "$stats_line" | awk -F'min/avg/max = ' '{print $2}' | awk '{print $1}')
local min_delay=$(echo "$stats" | cut -d'/' -f1)
local avg_delay=$(echo "$stats" | cut -d'/' -f2)
local max_delay=$(echo "$stats" | cut -d'/' -f3)
# 提取包统计数据
local transmitted=$(echo "$packet_line" | awk '{print $1}')
local received=$(echo "$packet_line" | awk '{print $4}')
local loss_percent=$(echo "$packet_line" | grep -o '[0-9-]\+%' | head -1)
# 计算重复包数量
local duplicate_count=0
if [ "$received" -gt "$transmitted" ]; then
duplicate_count=$((received - transmitted))
fi
# 计算延迟抖动 (最高延迟 - 最低延迟)
local jitter=$(awk "BEGIN {printf \"%.1f\", $max_delay - $min_delay}")
# 提取TTL范围
local ttl_values=$(echo "$result" | grep "ttl=" | grep -o "ttl=[0-9]\+" | grep -o "[0-9]\+" | sort -n | uniq)
local ttl_min=$(echo "$ttl_values" | head -1)
local ttl_max=$(echo "$ttl_values" | tail -1)
local ttl_range="${ttl_min}"
if [ "$ttl_min" != "$ttl_max" ]; then
ttl_range="${ttl_min}-${ttl_max}"
fi
# 验证提取结果
if [ -n "$min_delay" ] && [ -n "$avg_delay" ] && [ -n "$max_delay" ]; then
echo -e "${GREEN}TCP应用层延迟测试完成${NC}"
echo -e "使用指令: ${YELLOW}hping3 -c $TEST_DURATION -i 1 -S -p $TARGET_PORT $TARGET_IP${NC}"
echo ""
echo -e "${BLUE}📊 测试结果${NC}"
echo ""
echo -e "TCP延迟: ${YELLOW}最低${min_delay}ms / 平均${avg_delay}ms / 最高${max_delay}ms${NC}"
# 构建收发统计信息
local packet_info="${transmitted} 发送 / ${received} 接收"
if [ "$duplicate_count" -gt 0 ]; then
packet_info="${packet_info} (含 ${duplicate_count} 个异常包)"
fi
echo -e "收发统计: ${YELLOW}${packet_info}${NC} | 抖动: ${YELLOW}${jitter}ms${NC} | TTL范围: ${YELLOW}${ttl_range}${NC}"
# 收集延迟测试数据
set_test_result "latency_min" "$min_delay"
set_test_result "latency_avg" "$avg_delay"
set_test_result "latency_max" "$max_delay"
set_test_result "latency_jitter" "$jitter"
set_test_result "packet_sent" "$transmitted"
set_test_result "packet_received" "$received"
HPING_SUCCESS=true
else
echo -e "${RED}❌ 数据提取失败${NC}"
HPING_SUCCESS=false
fi
else
echo -e "${RED}❌ 未找到统计行${NC}"
HPING_SUCCESS=false
fi
else
echo -e "${RED}❌ 测试执行失败 (可能需要管理员权限)${NC}"
HPING_SUCCESS=false
fi
rm -f "$temp_result"
echo ""
else
echo -e "${YELLOW}⚠️ hping3工具不可用,跳过TCP延迟测试${NC}"
HPING_SUCCESS=false
fi
}
# 显示进度条
show_progress_bar() {
local duration=$1
local test_name="$2"
echo -e "${BLUE}🔄 ${test_name} 进行中...${NC}"
for ((i=1; i<=duration; i++)); do
printf "\r ⏱️ %d/%d秒" $i $duration
sleep 1
done
echo ""
}
# 获取系统和内核信息
get_system_kernel_info() {
# 获取系统信息
local system_info="未知"
if [ -f /etc/os-release ]; then
. /etc/os-release
system_info="$NAME $VERSION_ID"
fi
# 获取内核信息
local kernel_info=$(uname -r 2>/dev/null || echo "未知")
echo "${system_info} | 内核: ${kernel_info}"
}
# 获取TCP缓冲区信息
get_tcp_buffer_info() {
# 获取接收缓冲区
local rmem="未知"
if [ -f /proc/sys/net/ipv4/tcp_rmem ]; then
rmem=$(cat /proc/sys/net/ipv4/tcp_rmem 2>/dev/null || echo "未知")
fi
# 获取发送缓冲区
local wmem="未知"
if [ -f /proc/sys/net/ipv4/tcp_wmem ]; then
wmem=$(cat /proc/sys/net/ipv4/tcp_wmem 2>/dev/null || echo "未知")
fi
echo "rmem:$rmem|wmem:$wmem"
}
# 获取本机TCP拥塞控制算法和队列信息
get_local_tcp_info() {
# 获取拥塞控制算法
local congestion=$(cat /proc/sys/net/ipv4/tcp_congestion_control 2>/dev/null || echo "未知")
# 获取队列算法 ip命令
local qdisc="未知"
local default_iface=$(ip route show default 2>/dev/null | awk '{print $5; exit}')
if [ -n "$default_iface" ]; then
qdisc=$(ip link show "$default_iface" 2>/dev/null | grep -o "qdisc [^ ]*" | awk '{print $2}' | head -1 || echo "未知")
fi
echo "${congestion}+${qdisc}"
}
# 解析iperf3输出数据
parse_iperf3_data() {
local line="$1"
local data_type="$2"
case "$data_type" in
"transfer")
# MBytes和GBytes,统一转换为MBytes
local transfer_data=$(echo "$line" | grep -o '[0-9.]\+\s*[MG]Bytes' | head -1)
if [ -n "$transfer_data" ]; then
local value=$(echo "$transfer_data" | grep -o '[0-9.]\+')
local unit=$(echo "$transfer_data" | grep -o '[MG]Bytes')
if [ "$unit" = "GBytes" ]; then
# GBytes转换为MBytes (1 GB = 1024 MB)
awk "BEGIN {printf \"%.1f\", $value * 1024}"
else
echo "$value"
fi
fi
;;
"bitrate")
# 提取Mbits/sec数值
echo "$line" | grep -o '[0-9.]\+\s*Mbits/sec' | head -1 | grep -o '[0-9.]\+'
;;
"retrans")
echo "$line" | grep -o '[0-9]\+\s*sender$' | grep -o '[0-9]\+' || echo "0"
;;
"jitter")
echo "$line" | grep -o '[0-9.]\+\s*ms' | head -1 | grep -o '[0-9.]\+'
;;
"loss")
echo "$line" | grep -o '[0-9]\+/[0-9]\+\s*([0-9.]\+%)' | head -1
;;
"cpu_local")
echo "$line" | grep -o 'local/sender [0-9.]\+%' | grep -o '[0-9.]\+%'
;;
"cpu_remote")
echo "$line" | grep -o 'remote/receiver [0-9.]\+%' | grep -o '[0-9.]\+%'
;;
esac
}
# TCP上行测试
run_tcp_single_thread_test() {
echo -e "${GREEN}🚀 TCP上行带宽测试 - 目标: ${TARGET_IP}:${TARGET_PORT}${NC}"
echo ""
# 后台执行iperf3,前台显示倒计时
local temp_result=$(mktemp)
(iperf3 -c "$TARGET_IP" -p "$TARGET_PORT" -t "$TEST_DURATION" -f m > "$temp_result" 2>&1) &
local test_pid=$!
show_progress_bar "$TEST_DURATION" "TCP单线程测试"
# 等待测试完成
wait $test_pid
local exit_code=$?
# 首次失败快速重试一次(针对首连接冷关闭问题)
if [ $exit_code -ne 0 ]; then
sleep 0.5
: > "$temp_result"
(iperf3 -c "$TARGET_IP" -p "$TARGET_PORT" -t "$TEST_DURATION" -f m > "$temp_result" 2>&1) &
local test_pid2=$!
show_progress_bar "$TEST_DURATION" "TCP单线程测试"
wait $test_pid2
exit_code=$?
fi
if [ $exit_code -eq 0 ]; then
local result=$(cat "$temp_result")
echo ""
echo -e "${BLUE}📋 测试数据:${NC}"
# 过滤杂乱信息,保留核心测试数据
echo "$result" | sed -n '/\[ *[0-9]\]/,/^$/p' | sed '/^- - - - -/,$d' | sed '/^$/d'
# 解析最终结果
local final_line=$(echo "$result" | grep "sender$" | tail -1)
local cpu_line=$(echo "$result" | grep "CPU Utilization" | tail -1)
if [ -n "$final_line" ]; then
local final_transfer=$(parse_iperf3_data "$final_line" "transfer")
local final_bitrate=$(parse_iperf3_data "$final_line" "bitrate")
# 提取重传次数
local final_retrans=$(echo "$final_line" | awk '{print $(NF-1)}')
# CPU使用率
local cpu_local=""
local cpu_remote=""
if [ -n "$cpu_line" ]; then
cpu_local=$(parse_iperf3_data "$cpu_line" "cpu_local")
cpu_remote=$(parse_iperf3_data "$cpu_line" "cpu_remote")
fi
echo -e "${GREEN}TCP上行测试完成${NC}"
echo -e "使用指令: ${YELLOW}iperf3 -c $TARGET_IP -p $TARGET_PORT -t $TEST_DURATION -f m${NC}"
echo ""
echo -e "${YELLOW}📊 测试结果${NC}"
echo ""
# 计算Mbps,MB/s直接使用MBytes/sec值
local mbps="N/A"
local mb_per_sec="N/A"
if [ -n "$final_bitrate" ] && [[ "$final_bitrate" =~ ^[0-9]+\.?[0-9]*$ ]]; then
mbps=$(awk "BEGIN {printf \"%.0f\", $final_bitrate}")
mb_per_sec=$(awk "BEGIN {printf \"%.1f\", $final_bitrate / 8}")
fi
echo -e "平均发送速率 (Sender): ${YELLOW}${mbps} Mbps${NC} (${YELLOW}${mb_per_sec} MB/s${NC}) 总传输数据量: ${YELLOW}${final_transfer:-N/A} MB${NC}"
# 显示重传次数(不计算重传率,避免估算误差)
echo -e "重传次数: ${YELLOW}${final_retrans:-0} 次${NC}"
# CPU负载
if [ -n "$cpu_local" ] && [ -n "$cpu_remote" ]; then
echo -e "CPU 负载: 发送端 ${YELLOW}${cpu_local}${NC} 接收端 ${YELLOW}${cpu_remote}${NC}"
fi
echo -e "测试时长: ${YELLOW}${TEST_DURATION} 秒${NC}"
# 收集TCP上行测试数据
set_test_result "tcp_up_speed_mbps" "$mbps"
set_test_result "tcp_up_speed_mibs" "$mb_per_sec"
set_test_result "tcp_up_transfer" "$final_transfer"
set_test_result "tcp_up_retrans" "$final_retrans"
# 保存TCP Mbps值,四舍五入到10的倍数,用于UDP的-b参数
if [ "$mbps" != "N/A" ]; then
# 复用已计算的mbps值,避免重复计算
TCP_MBPS=$(awk "BEGIN {printf \"%.0f\", int(($mbps + 5) / 10) * 10}")
else
TCP_MBPS=100 # 默认值
fi
TCP_SINGLE_SUCCESS=true
else
echo -e "${RED}❌ 无法解析测试结果${NC}"
TCP_SINGLE_SUCCESS=false
fi
else
echo -e "${RED}❌ 测试执行失败${NC}"
TCP_SINGLE_SUCCESS=false
fi
rm -f "$temp_result"
echo ""
}
# 带宽测试
run_bandwidth_tests() {
echo -e "${YELLOW}🟢 网络带宽性能测试${NC}"
echo ""
# 检查工具
if ! check_tool "iperf3"; then
echo -e "${YELLOW}⚠️ iperf3工具不可用,跳过带宽测试${NC}"
TCP_SUCCESS=false
UDP_SINGLE_SUCCESS=false
UDP_DOWNLOAD_SUCCESS=false
return
fi
# 连通性检查
if ! nc -z -w3 "$TARGET_IP" "$TARGET_PORT" >/dev/null 2>&1; then
echo -e " ${RED}无法连接到目标服务器${NC}"
echo -e " ${YELLOW}请确认目标服务器运行: iperf3 -s -p $TARGET_PORT${NC}"
TCP_SUCCESS=false
UDP_SINGLE_SUCCESS=false
UDP_DOWNLOAD_SUCCESS=false
echo ""
return
fi
# 预热:快速建立控制通道,提升首项成功率(输出丢弃,不影响报告)
iperf3 -c "$TARGET_IP" -p "$TARGET_PORT" -t 1 -f m >/dev/null 2>&1 || true
sleep 1
# TCP上行
run_tcp_single_thread_test
echo ""
sleep 2
# UDP上行
run_udp_single_test
echo ""
sleep 2
# TCP下行
run_tcp_download_test
echo ""
sleep 2
# UDP下行
run_udp_download_test
}
# UDP上行测试
run_udp_single_test() {
echo -e "${GREEN}🚀 UDP上行性能测试 - 目标: ${TARGET_IP}:${TARGET_PORT}${NC}"
echo ""
# 根据TCP测试结果设置UDP目标带宽
local udp_bandwidth="30M" # 默认值
if [ "$TCP_SINGLE_SUCCESS" = true ] && [ -n "$TCP_MBPS" ]; then
# 直接使用TCP测试的Mbps值作为UDP目标带宽
udp_bandwidth="${TCP_MBPS}M"
fi
# 后台执行iperf3,前台显示倒计时
local temp_result=$(mktemp)
(iperf3 -c "$TARGET_IP" -p "$TARGET_PORT" -u -b "$udp_bandwidth" -t "$TEST_DURATION" -f m > "$temp_result" 2>&1) &
local test_pid=$!
show_progress_bar "$TEST_DURATION" "UDP单线程测试"
# 等待测试完成
wait $test_pid
local exit_code=$?
if [ $exit_code -eq 0 ]; then
local result=$(cat "$temp_result")
echo ""
echo -e "${BLUE}📋 测试数据:${NC}"
# 过滤杂乱信息,保留核心测试数据
echo "$result" | sed -n '/\[ *[0-9]\]/,/^$/p' | sed '/^- - - - -/,$d' | sed '/^$/d'
# 解析最终结果
local sender_line=$(echo "$result" | grep "sender$" | tail -1)
local receiver_line=$(echo "$result" | grep "receiver$" | tail -1)
if [ -n "$sender_line" ]; then
local final_transfer=$(parse_iperf3_data "$sender_line" "transfer")
local final_bitrate=$(parse_iperf3_data "$sender_line" "bitrate")
echo -e "${GREEN}UDP上行测试完成${NC}"
echo -e "使用指令: ${YELLOW}iperf3 -c $TARGET_IP -p $TARGET_PORT -u -b $udp_bandwidth -t $TEST_DURATION -f m${NC}"
echo ""
echo -e "${YELLOW}📡 传输统计${NC}"
echo ""
# 解析接收端信息和CPU信息
local cpu_line=$(echo "$result" | grep "CPU Utilization" | tail -1)
local cpu_local=""
local cpu_remote=""
if [ -n "$cpu_line" ]; then
cpu_local=$(parse_iperf3_data "$cpu_line" "cpu_local")
cpu_remote=$(parse_iperf3_data "$cpu_line" "cpu_remote")
fi
if [ -n "$receiver_line" ]; then
local receiver_transfer=$(parse_iperf3_data "$receiver_line" "transfer")
local receiver_bitrate=$(parse_iperf3_data "$receiver_line" "bitrate")
local jitter=$(parse_iperf3_data "$receiver_line" "jitter")
local loss_info=$(parse_iperf3_data "$receiver_line" "loss")
# receiver_bitrate格式Mbits/sec
local recv_mbps="N/A"
local recv_mb_per_sec="N/A"
if [ -n "$receiver_bitrate" ] && [[ "$receiver_bitrate" =~ ^[0-9]+\.?[0-9]*$ ]]; then
recv_mbps=$(awk "BEGIN {printf \"%.1f\", $receiver_bitrate}") # 直接使用Mbits/sec值
recv_mb_per_sec=$(awk "BEGIN {printf \"%.1f\", $receiver_bitrate / 8}") # 转换为MB/s
fi
# 计算目标速率显示(与-b参数一致)
local target_mbps=$(echo "$udp_bandwidth" | sed 's/M$//')
echo -e "有效吞吐量 (吞吐率): ${YELLOW}${recv_mbps} Mbps${NC} (${YELLOW}${recv_mb_per_sec} MB/s${NC})"
echo -e "丢包率 (Packet Loss): ${YELLOW}${loss_info:-N/A}${NC}"
echo -e "网络抖动 (Jitter): ${YELLOW}${jitter:-N/A} ms${NC}"
# 显示CPU负载
if [ -n "$cpu_local" ] && [ -n "$cpu_remote" ]; then
echo -e "CPU负载: 发送端 ${YELLOW}${cpu_local}${NC} 接收端 ${YELLOW}${cpu_remote}${NC}"
fi
echo -e "测试目标速率: ${YELLOW}${target_mbps} Mbps${NC}"
# 收集UDP上行测试数据
set_test_result "udp_up_speed_mbps" "$recv_mbps"
set_test_result "udp_up_speed_mibs" "$recv_mb_per_sec"
set_test_result "udp_up_loss" "$loss_info"
set_test_result "udp_up_jitter" "$jitter"
else
echo -e "有效吞吐量 (吞吐率): ${YELLOW}N/A${NC}"
echo -e "丢包率 (Packet Loss): ${YELLOW}N/A${NC}"
echo -e "网络抖动 (Jitter): ${YELLOW}N/A${NC}"
echo -e "CPU负载: ${YELLOW}N/A${NC}"
echo -e "测试目标速率: ${YELLOW}N/A${NC}"
fi
UDP_SINGLE_SUCCESS=true
else
echo -e "${RED}❌ 无法解析测试结果${NC}"
UDP_SINGLE_SUCCESS=false
fi
else
echo -e "${RED}❌ 测试执行失败${NC}"
UDP_SINGLE_SUCCESS=false
fi
rm -f "$temp_result"
echo ""
}
# 执行TCP下行带宽测试
run_tcp_download_test() {
echo -e "${GREEN}🚀 TCP下行带宽测试 - 目标: ${TARGET_IP}:${TARGET_PORT}${NC}"
echo ""
# 后台执行测试,前台显示进度条
local temp_result=$(mktemp)
(iperf3 -c "$TARGET_IP" -p "$TARGET_PORT" -t "$TEST_DURATION" -f m -R > "$temp_result" 2>&1) &
local test_pid=$!
show_progress_bar "$TEST_DURATION" "TCP下行测试"
# 等待测试完成
wait $test_pid
local exit_code=$?
if [ $exit_code -eq 0 ]; then
local result=$(cat "$temp_result")
echo ""
echo -e "${BLUE}📋 测试数据:${NC}"
echo "$result" | sed -n '/\[ *[0-9]\]/,/^$/p' | sed '/^- - - - -/,$d' | sed '/^$/d'
# 解析最终结果 - 下行测试需要使用receiver行数据