news 2026/9/28 13:00:43

Tomcat启动失败:80端口被占用的排查与解决指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Tomcat启动失败:80端口被占用的排查与解决指南

相信不少人在部署 Tomcat 时都见过这个熟悉的场面:刚执行完startup.sh,日志里还没翻到 "Server startup",就先看到一行SEVERE,跟着一个BindException,再仔细一看——80 端口已经被占用了。我最早遇到这个问题是好几年以前,当时的反应很干脆,“谁占的端口直接杀掉不就行了”,结果处理完之后才发现事情远没有这么简单。你面对的可能是正在提供服务的 nginx、一个之前没关干净的 java 进程、Windows 上默认跑着的系统服务,甚至是你自己都忘了什么时候起的临时 HTTP 服务。

这篇东西我不打算给你灌一堆“先检查再重启”的大道理,而是把从报错产生的原因、占用进程怎么查、不同场景下怎么处置,到后续如何避免再踩坑,完整走一遍。文章里的命令和配置都是我在 Linux 和 Windows 两种环境下实测过的,如果你正在被 Tomcat 启动失败、80 端口被占用这类问题困住,照着做基本能解决。

1. 这条报错的完整面孔:80 端口绑定失败的典型现场与原因拆解

1.1 报错日志里真正有用的三行

Tomcat 启动时报端口错误,日志输出是有固定套路的。不管你是用catalina.sh run直接在前台跑,还是通过startup.sh后台启动然后去翻catalina.out,核心错误通常长这样:

SEVERE [main] org.apache.catalina.core.StandardService.initInternal Failed to initialize connector [Connector[HTTP/1.1-80]] org.apache.catalina.LifecycleException: Protocol handler initialization failed at org.apache.catalina.connector.Connector.initInternal(Connector.java:1073) ... Caused by: java.net.BindException: Address already in use <null>:80 at java.base/sun.nio.ch.Net.bind0(Native Method) ...

这里真正有用的信息其实就三层:

  • Failed to initialize connector:Tomcat 在初始化 HTTP 连接器时失败了,这个连接器监听的端口就是你配置的 80。
  • LifecycleException: Protocol handler initialization failed:Tomcat 把底层协议处理器的初始化失败包装成了生命周期异常,你只需要知道这里是“连接器没法正常工作”即可。
  • Caused by: BindException: Address already in use :80:这行才是根因。Java 底层调用操作系统的 socket bind() 时,系统返回了“地址已在使用”的错误码,JVM 把它翻译成了这个异常。换句话说,你的 Tomcat 想去监听 80 端口,但 80 端口此刻已经被另一个进程占住了。

如果日志里只看到BindException而没看到Address already in use,而是Permission denied,那要单独处理,原因完全不同。这点我放到 1.2 节专门讲。

1.2 端口占用和权限拒绝是两码事

很多文章把“80 端口无法绑定”统一归为“端口被占用”,这不准确。实际上 Java 的BindException在 Linux 下会出现两条常见消息:

异常消息本质原因常见场景
Address already in use端口已被其他进程 LISTEN,系统返回EADDRINUSEnginx、Apache、另一个 Tomcat 实例已经占用了 80
Permission denied当前用户没有权限绑定该端口,系统返回EACCES以非 root 用户启动 Tomcat,监听 1024 以下的特权端口

80 端口在 Linux 上属于特权端口(1024 以下),普通用户默认没有权限去 bind。这个设计初衷是防止普通用户随意监听常用服务端口、搞伪装服务。所以如果你用tomcat用户启动服务,并且配置了监听 80,即使端口根本没人占用,也可能直接报Permission denied。解决办法要么用 root 启动(不推荐),要么给 Java 进程加CAP_NET_BIND_SERVICE能力(后面第 4 章细说)。

从排查顺序来讲,我建议你先确认到底是Address already in use还是Permission denied,再决定下一步。如果是权限问题,哪怕查半天端口占用也是白费功夫。

1.3 最容易撞车的三位“邻居”

80 端口被谁占了?根据我这几年的经验,三类角色出镜率最高:

  • Web 服务软件:nginx、Apache httpd、lighttpd,这些服务很多默认就监听 80。你在一台已经跑着 nginx 的服务器上再让 Tomcat 去监听 80,基本必撞。
  • 残留的 Java 进程:之前用startup.sh启动过 Tomcat,但没有执行shutdown.sh就直接关掉了终端窗口。Java 进程还在后台跑着,端口自然没释放。这是二进宫最常见的原因。
  • Windows 系统服务:在 Windows 上,IIS 或 HTTP.sys 驱动会占用 80,这种占用很隐蔽,因为netstat看到的 PID 是 4(System 进程),很多人到这里就卡住了。

搞清楚对方是谁,才能对症下药。下面第 2 章就是完整的排查链路。

2. 揪出占端口的“真凶”:Linux 与 Windows 下的排查链路

2.1 Linux 下先用 ss 再上 lsof

Linux 上排查端口占用,第一选择是ss。它是 iproute2 套件里的工具,几乎所有现代发行版都自带,输出比老旧的netstat干净许多。需要 root 权限加上-p才能看到进程名和 PID,所以直接加sudo:

sudo ss -tlnp | grep ':80'

加几个参数拆开说一下:

  • -t:只看 TCP。
  • -l:只看监听中的 socket(LISTEN 状态)。
  • -n:不解析服务名,直接显示端口数字,避免把 80 显示成 http。
  • -p:显示进程信息。

输出类似:

LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=21513,fd=6))

这一行已经告诉你占用 80 端口的是nginx,PID 是 21513。如果你用普通用户执行,-p参数看不到进程信息,系统会提示让你用 root,所以干脆养成加sudo的习惯。

有些服务器上ss没装,那就用经典组合:

sudo netstat -tlnp | grep ':80'

输出格式类似,同样能看到进程名和 PID。如果netstat也没有,可以用lsof:

sudo lsof -i :80

lsof的输出会把进程的 PID、用户、FD 都列出来,看起来是这样的:

COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME nginx 21513 root 6u IPv4 93236 0t0 TCP *:80 (LISTEN)

注意一点:lsof -i :80会同时显示 LISTEN 和已建立连接的 socket。如果你的系统上有很多客户端连到 80,lsof的输出会很长。这时候加个过滤条件,只看 LISTEN:

sudo lsof -i :80 | grep LISTEN

2.2 根据 PID 反查进程并确认它是什么服务

拿到 PID 之后,光知道进程名还不够。比如进程名是java,你还要确认它到底是哪个项目的进程,是不是残留的 Tomcat。这时候用:

ps -fp 21513

或者看完整启动参数:

ps -ef | grep 21513 | grep -v grep

如果输出里是-Dcatalina.base=/opt/tomcat7、-Dcatalina.home=/opt/tomcat7这类 JVM 参数,那基本可以确定是另一个 Tomcat 实例。

还有一种更狠的验证方式,查看进程的可执行文件真实路径:

sudo ls -l /proc/21513/exe

如果exe指向/usr/sbin/nginx,那就是系统的 nginx;如果指向/usr/lib/jvm/java-.../bin/java,那就是 Java 进程。这种方式能区分出systemd管辖的服务和手动跑起来的进程。再用:

sudo systemctl status 21513

如果进程确实由 systemd 管理,系统会告诉你它对应的服务单元;如果显示not running或者No unit found,那多半是手动启动、不受 systemd 管理的独立进程。

2.3 Windows 下对应的完整操作

Windows 下的排查思路一致,命令换成:

netstat -ano | findstr ":80"

输出里找LISTENING状态的行,最后一列就是 PID。比如:

TCP 0.0.0.0:80 0.0.0.0:0 LISTENING 21513

然后反查 PID 对应的进程名:

tasklist /FI "PID eq 21513"

如果 PID 是 4,进程名是System.exe,那就说明 80 端口被系统 HTTP 服务占用,常见来源是 IIS 的World Wide Web Publishing Service或者 SQL Server Reporting Services。这种时候我一般用net命令查服务占用:先停掉相关服务再确认。你可以打开服务管理器,找到World Wide Web Publishing Service,右键停止。如果不想动 IIS,也可以选择让 Tomcat 改用其他端口。

Windows 上杀进程用:

taskkill /F /PID 21513

/F是强制结束,慎用。能用服务管理器停止的,优先用停止服务的方式,直接taskkill杀掉 IIS 进程有时候会导致服务状态混乱。

2.4 Docker 场景的额外提醒

还有一种常见情况,报错看起来像 Tomcat 的问题,实际上跟 Tomcat 一点关系都没有。如果你是用 Docker 启动容器,执行的是类似:

docker run -p 80:8080 tomcat

然后 Docker 报bind: address already in use,那是因为宿主机上的 80 端口被占了。容器内部 Tomcat 监听的是 8080,是宿主机把 80 映射进来的。这种场景下排查思路完全一样,在宿主机上查 80 端口占用,处理掉占用方后再启动容器,或者改映射端口,比如-p 8088:8080。

3. 对症下药:避让、停服务与反向代理三种处置路线

找到占用者之后,怎么处理就取决于你的真实需求。我下面把场景拆成三种,你对着自己的情况选。

3.1 方案一:调整 Tomcat 监听端口,主动避让

如果业务上不强制要求必须用 80 端口,最简单的做法是让 Tomcat 换一个端口。修改$CATALINA_BASE/conf/server.xml,找到 Connector 配置:

<Connector port="80" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />

把port="80"改成你规划好的端口,比如 8080、8081、8088 都可以:

<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />

这里有个容易被忽略的点:redirectPort="8443"保留不动。它的意思是,当 Tomcat 发现客户端请求需要走 HTTPS 时,会把请求重定向到 8443 端口。这个配置跟 HTTP 监听端口是独立的,不要顺手把 8443 也改成 8080,否则两个 Connector 端口重复,Tomcat 连启动都过不了。

改完之后,确认端口配置是否生效,可以等服务起来后执行:

ss -tlnp | grep ':8080'

看到java进程监听 8080 就说明 OK 了。这种方案的代价是,对外访问地址会多一个端口号,比如http://服务器IP:8080/。如果公司内部访问没有特殊要求,这其实是成本最低、风险最小的路径。

3.2 方案二:停掉占用进程,把 80 端口让出来

如果你必须让 Tomcat 监听 80,并且占用端口的是可以停掉的服务,那就处理掉占用者。

拿 nginx 举例。如果你的 nginx 没有在跑业务,或者这台服务器本来就是专门给 Tomcat 用的,那先确认再用 systemd 优雅停止:

sudo systemctl stop nginx

停止后再确认端口释放:

sudo ss -tlnp | grep ':80'

没有输出,说明端口空出来了,直接启动 Tomcat。

如果占用进程是残留的 java 进程,情况稍微复杂一点。理论上正规关闭方式是先执行对应 Tomcat 的shutdown.sh:

/opt/tomcat/bin/shutdown.sh

但很多时候shutdown.sh执行完,进程还没立刻退出,因为可能有非守护线程卡住了 JVM 的退出过程。这时候等几秒钟,再用:

ps -ef | grep java

看看那个残留进程还在不在。如果在,可以kill让它正常退出,再不行才kill -9:

kill PID sleep 3 kill -9 PID # 仅在仍未退出时使用

杀完进程之后同样确认端口已释放。这里我的经验是:把shutdown.sh当成一个礼貌的请求,而不是立即生效的命令。它只是向 JVM 发出了关闭信号,JVM 能不能按时退出取决于当前有没有正在执行的请求、有没有卡死的线程。所以别发完命令就以为完事了。

3.3 方案三:Nginx 接管 80,Tomcat 挪到内网端口

第三种场景在实践中最多见:服务器上已经跑着 nginx,并且 nginx 还承载着其他站点,你不能停掉它。这时候还硬要 Tomcat 监听 80 就是不合理的想法了,更合理的架构是:nginx 监听 80,Tomcat 监听一个内网端口,由 nginx 把外部请求转发给 Tomcat。

先修改 Tomcat 的server.xml,把端口改成 8080:

<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />

然后在 nginx 配置里添加一个 server 块,做一个标准的反向代理。以独立配置文件/etc/nginx/conf.d/tomcat_proxy.conf为例:

server { listen 80; server_name yourdomain.com; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }

配置里三行proxy_set_header建议都带上。它们的作用是把客户端的原始请求头透传给 Tomcat,尤其是X-Forwarded-For,Tomcat 拿不到它的话,应用里获取到的用户 IP 永远是127.0.0.1,这样排查问题、做日志分析的时候会非常难受。

配合反向代理,Tomcat 端最好再加一个 Valve,让 Tomcat 能识别代理转发过来的协议和 IP 头。在server.xml的 Host 节点里加上:

<Valve className="org.apache.catalina.valves.RemoteIpValve" internalProxies="127\.0\.0\.1" remoteIpHeader="x-forwarded-for" protocolHeader="x-forwarded-proto" />

这个配置不至于必须加,但如果你想在 Tomcat 的访问日志里看到用户真实 IP,而不是每一行都来自 nginx 的转发地址,那就值得加。

配置完成后依次执行:

nginx -t sudo systemctl reload nginx sudo systemctl restart tomcat

nginx -t测试配置语法是必须的一步,别省。语法不对直接 reload 会让 nginx 拒绝加载新配置,甚至可能影响线上服务。

这架构的好处很明显:nginx 可以继续承担静态资源处理、HTTPS 证书终结、负载均衡这些活儿,Tomcat 专心跑 Java 应用,各得其所。这也是我目前最推荐的一种长期方案。

4. 端口让路之后的暗坑:权限、防火墙与残留进程

4.1 Linux 下非 root 启动 Tomcat 监听 80 的权限问题

如果按第 3 章的方案二,把端口抢回来之后给 Tomcat 配置port="80",然后以普通用户启动,你会遇到一个跟占用无关的新坑——Permission denied。

前面说过,Linux 上 1024 以下的端口只有 root 才有权限绑定。直接su root去启动 Tomcat 确实能解决问题,但让 Tomcat 以 root 身份运行是个非常差的安全习惯。Java 应用的攻击面本来就大,一旦被利用,入侵者拿到的就是 root 权限的进程。所以我不推荐这种用法。

更好的做法是给 Java 可执行文件单独赋予cap_net_bind_service能力,让非 root 用户也能绑定低端口:

sudo setcap 'cap_net_bind_service=+ep' /usr/lib/jvm/java-11-openjdk-amd64/bin/java

注意路径要换成你机器上实际的 Java 路径,可以用which java先确认。执行后验证:

sudo getcap /usr/lib/jvm/java-11-openjdk-amd64/bin/java

输出显示cap_net_bind_service=ep就说明成功了。这个方案只给 Java 这一个程序开了低端口绑定权限,不影响系统整体安全边界。不过也有个副作用:如果以后升级 JDK、替换了java文件,这个 capability 会丢失,需要重新设置。如果你用 systemd 管理 Tomcat,并且在服务单元里配置了User=tomcat,还有一种更干净的写法,在 systemd 单元里加:

AmbientCapabilities=CAP_NET_BIND_SERVICE

这样不需要动 Java 二进制文件,系统权限由 systemd 统一管理。这个我放到第 5 章一起给完整配置。

4.2 防火墙放行端口,别让改完端口反而连不上

端口冲突解决了,Tomcat 启动也成功了,结果浏览器还是访问不了——这种情况我遇到太多次了,原因通常出在防火墙。你把 Tomcat 从 80 换到了 8080,但防火墙只放行了 80,8080 在外面根本进不来。

CentOS/RHEL 系用 firewalld:

sudo firewall-cmd --permanent --zone=public --add-port=8080/tcp sudo firewall-cmd --reload

Ubuntu/Debian 系用 ufw:

sudo ufw allow 8080/tcp

查看当前端口放行策略:

sudo firewall-cmd --list-all

如果服务器在云环境(阿里云、腾讯云、AWS 等),还要记得去云控制台的安全组/安全策略里同步放行端口。云安全组是独立于操作系统防火墙的一层,很多人只调了系统防火墙,忘了安全组,结果从外网还是连不上。这一点没法从服务器内部用命令解决,只能登录云控制台操作。

4.3 多实例 Tomcat 的端口混淆与进程残留

同一台服务器上跑多个 Tomcat 实例,端口冲突的概率会直线上升。典型场景是:你下载了 Tomcat 8 和 Tomcat 9 两个不同版本,分别解压到/opt/tomcat8和/opt/tomcat9,想同时跑两个应用。由于两个实例默认都配置监听 8080,第二个启动时必然报端口占用。

排查这种多实例问题,第一步是把所有 Java 进程列出来:

ps -ef | grep java | grep -v grep

重点看每个进程的-Dcatalina.base参数指向哪个目录。比如:

tomcat 12345 ... -Dcatalina.base=/opt/tomcat8 ... tomcat 12346 ... -Dcatalina.base=/opt/tomcat9 ...

如果两个实例确实都需要独立监听端口,就把其中一方的server.xml改成不同的端口。比如 tomcat8 用 8080,tomcat9 用 8081。

多实例场景还有一个很隐蔽的坑:你把两个 Tomcat 都配成了同一个端口,其中一个启动后,另一个启动报错。你顺手改了其中一个的server.xml里的 HTTP 端口,但忘了 AJP 端口默认也是 8009,两个实例依然会因为 8009 冲突报错。所以如果你要调整多个 Connector 端口,别只改 HTTP 那个,检查一遍server.xml里所有port属性。Tomcat 实例里常见的端口包括 HTTP Connector、HTTPS Connector、AJP Connector,以及 shutdown 端口(默认 8005)。逐一确认没有重复,才算真正解决。

5. 把端口冲突的概率压到最低:一套接地气的端口管理习惯

5.1 启动之前先探测端口状态

与其等 Tomcat 启动报错再排查,不如把检查前置。我习惯在启动脚本里加一段端口探测逻辑。举个例子,一个精简版启动脚本:

#!/bin/bash CATALINA_HOME=/opt/tomcat PORT=8080 if ss -tln | grep -q ":${PORT} " ; then echo "Error: port ${PORT} already in use, current process:" ss -tlnp | grep ":${PORT} " exit 1 fi ${CATALINA_HOME}/bin/startup.sh

这样端口被占时,启动脚本会直接把占用进程信息打印出来,然后拒绝启动。虽然只是一个小小的前置检查,但能避免很多“启动后立刻崩掉”的尴尬。

5.2 给服务器做一张端口规划表

很多端口冲突本质上是端口规划混乱导致的。你的服务器上到底跑了哪些服务,各自监听哪些端口,应该有一份文档明确登记。尤其是一个团队共用服务器的时候,没有规划表,今天张三把 Tomcat 改成 8080,明天李四的另一个应用也默认 8080,撞车只是时间问题。

表格不需要花哨,能记录关键信息就行,大致长这样:

端口服务/应用占用进程特征负责人备注
80nginxnginx运维对外入口,禁止业务直接绑定
8080订单系统 Tomcatjava张三内网端口
8081用户中心 Tomcatjava李四内网端口
8443HTTPS 重定向java张三HTTPS 端口

有了这份表,再有人要新增服务,第一件事就是先查表确认哪些端口能选。哪怕只是写在团队文档里,也比每次靠猜强。

5.3 用 systemd 管理 Tomcat,告别手工残留

我见过太多人是靠startup.sh手工启动 Tomcat 的,然后就会遇到一个经典问题:关闭终端窗口时,Tomcat 进程没跟着退出,或者后来找不到哪个终端里启动的,没法shutdown.sh,只能kill -9硬杀。

用 systemd 管理以后,这些混乱能大幅减少。一份 Tomcat 的 systemd 服务单元文件可以这样写,创建/etc/systemd/system/tomcat.service:

[Unit] Description=Apache Tomcat Web Application Container After=network.target [Service] Type=simple User=tomcat Group=tomcat Environment="JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64" Environment="CATALINA_PID=/opt/tomcat/temp/tomcat.pid" Environment="CATALINA_HOME=/opt/tomcat" Environment="CATALINA_BASE=/opt/tomcat" Environment="CATALINA_OPTS=-Xms512M -Xmx1024M" Environment="JAVA_OPTS=-Djava.security.egd=file:///dev/urandom" ExecStart=/opt/tomcat/bin/catalina.sh run ExecStop=/opt/tomcat/bin/shutdown.sh ExecReload=/bin/kill -s HUP $MAINPID SuccessExitStatus=143 Restart=on-failure RestartSec=10 [Install] WantedBy=multi-user.target

关键点有两个。

第一,ExecStart我用的是catalina.sh run而不是startup.sh。startup.sh会 fork 出一个独立进程然后立即返回,systemd 会误以为服务启动完成,但实际上 Tomcat 主进程变成了一个脱离了 systemd 控制的孤儿进程,之后systemctl stop也会失效。catalina.sh run让 Tomcat 在前台运行,systemd 能完整掌控它的生命周期。

第二,如果你想在 systemd 服务里用非 1024 以下端口,其实不需要特殊配置。但如果 Tomcat 确实需要监听 80,并且你又不想用 root 用户,可以在[Service]段加一行:

AmbientCapabilities=CAP_NET_BIND_SERVICE

这样 Tomcat 以tomcat用户运行也能绑定 80 端口,比setcap和直接 root 都干净。

配置写好后执行:

sudo systemctl daemon-reload sudo systemctl start tomcat sudo systemctl status tomcat

之后日常操作全部交给 systemd:

sudo systemctl restart tomcat sudo systemctl enable tomcat

从启动、停止、重启到开机自启,一条命令全部解决。最直观的收益是:你再也不用担心手动启动留下的 Java 进程残留在后台悄悄占着 80 端口了。

最后再分享一个我自己的小习惯。改完端口、清完进程之后,我会顺手把catalina.out里这次启动的关键日志保存一份,包括端口初始化和启动完成的时间点。下次如果再出端口问题,翻一下历史日志,能很快判断是从什么时候开始端口归属发生变化的。排错这种事,信息多一步,就能少绕一圈弯路。

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

海康机器人三款工业相机选型指南:从参数到实战

工业相机选型这件事&#xff0c;说简单也简单&#xff0c;说复杂也复杂。简单在于&#xff0c;你只要把分辨率、帧率、接口、传感器尺寸这几个核心参数对齐需求&#xff0c;基本就能圈定范围&#xff1b;复杂在于&#xff0c;实际项目里光照条件、被测物特征、节拍要求、预算限…

作者头像 李华
网站建设 2026/9/28 12:58:53

OJ刷题第五天:13-15题如何突破边界条件与精度陷阱

刷 OJ 到第五天&#xff0c;13 到 15 题正好卡在一个门槛上。前几天你还在练“输入两个整数求 ab”这种热身题&#xff0c;到了这个阶段&#xff0c;题目开始真正考察你写程序的稳定性&#xff1a;逻辑要一次想对&#xff0c;边界条件要想全&#xff0c;提交后面对红色状态要能…

作者头像 李华
网站建设 2026/9/28 12:58:06

C盘爆满不用慌:十招深度清理与空间迁移实战指南

C盘又飘红了&#xff1f;别急着重装系统&#xff0c;也别一上来就下载各种“清理大师”。我在给同事、朋友救急的过程中总结了一套从排查到清理的完整打法&#xff0c;这篇文章把十招都给你拆开讲透&#xff0c;每一招都附上操作细节和避坑提醒&#xff0c;照着做基本能把C盘从…

作者头像 李华
网站建设 2026/9/28 12:56:56

OCC场景下分段Clock Tree设计与Innovus实现

1. 项目概述&#xff1a;为什么分段长clock tree是OCC场景下绕不开的硬骨头SoC芯片设计里&#xff0c;时序问题从来不是靠堆资源能解决的&#xff0c;尤其当OCC&#xff08;On-Chip Clocking&#xff09;电路遇上高扇出、长距离、多电压域的复杂布局时&#xff0c;传统单根cloc…

作者头像 李华
网站建设 2026/9/28 12:56:38

nRF52840 Dongle BLE抓包全攻略:固件烧录到Wireshark实战

BLE 抓包这件事&#xff0c;说难不难&#xff0c;说简单也真能卡住人。我见过太多人买了 nRF52840 Dongle&#xff0c;插上电脑发现设备管理器里是个未知设备&#xff0c;或者 Wireshark 里根本找不到 nRF Sniffer 接口&#xff0c;折腾一晚上连个广播包都没抓到。这篇就把从固…

作者头像 李华
网站建设 2026/9/28 12:56:38

KMeans聚类算法原理、Python实现与实战避坑指南

简介&#xff1a;KMeans聚类算法是无监督学习中的经典方法&#xff0c;这份资源面向机器学习初学者与数据分析人员&#xff0c;结合Python与scikit-learn完整演示了从数据加载、标准化、模型训练、预测到可视化的流程&#xff0c;并讨论初始质心选择、K值设定等关键问题。压缩包…

作者头像 李华