
你十有八九遇到过这类问题SSH连服务器连不上、网页突然502、FTP登录成功但目录刷不出来。表面看是三个完全不同的软件故障仔细一挖会发现它们全都卡在了同一个地方——TCP连接。先说结论SSH、HTTP、FTP虽然长相完全不一样但它们全都建立在TCP之上理解这一点等于掌握了排查这类网络问题的总开关。这篇文章会把三层关系讲明白TCP负责什么、HTTP/SSH/FTP各自在TCP上做了什么、以及你在真机环境里最常碰到的那些报错到底是什么意思。适合刚入门网络基础的开发、运维以及所有被远程连接和文件传输折磨过的人。1. TCP到底干了什么三个协议共用的传输层地基1.1 先分层HTTP/SSH/FTP只是“信纸”TCP是“挂号信服务”要理解SSH、HTTP、FTP为什么都基于TCP先得把网络分层这件事搞明白。网络上传输数据不是一个大文件直接丢过去而是按层拆解。最常用的比喻是寄信你写一封信应用层也就是HTTP、SSH、FTP干的事把信交给邮局邮局决定用挂号信还是平邮寄传输层TCP/UDP干的事信封上写地址交给分拣系统网络层IP干的事最后实际运送靠公路铁路链路层。应用层协议负责“信的内容长什么样”HTTP规定请求行、请求头、响应状态码的格式SSH规定加密协商、认证、终端会话的格式FTP规定用户名密码、文件列表、上传下载命令的格式。但它们都不管“这封信能不能可靠送到”这件事TCP全包了。我经常跟刚入门的朋友说一句话你抓包看HTTP流量看到的其实是TCP段里装着一块HTTP数据。TCP的作用是把字节流切成一个个段加上序号、确认号然后在不可靠的IP网络上实现可靠的传输。所谓“基于TCP”意思是这些应用层的报文最终都要先交给TCP去封装TCP再把整个数据交给IP去寻址。没有TCP这一层你的HTTP请求发出去可能半路就丢了SSH命令敲进去可能缺字节FTP传个大文件更是会传出一堆乱码。1.2 三次握手的本质一个可靠的“开始通话”确认TCP最常见的考点就是三次握手但很多人背了流程却不理解为什么。我拆开来讲。建立连接前客户端和服务端其实谁也不知道对方是否在线、收发能力是否正常。三次握手做的一件事就是让双方都确认“你能收到我的消息我也能收到你的消息”。流程严格来说是这样的客户端发SYN包seqx意思是“我要建立连接我的初始序号是x”。服务端回SYNACK包seqyackx1意思是“收到你的请求我的初始序号是y下次我期待收到你序号为x1的数据”。客户端发ACK包seqx1acky1意思是“收到你的确认下次我期待收到你序号为y1的数据”。为什么偏偏要三次而不是两次因为网络是不可靠的有可能客户端第一次发的SYN因为网络拥塞被卡了很久客户端以为丢了就重发结果两个SYN都到了。如果只握手两次服务端收到第一个SYN就建立连接等这个连接不用了那个老的SYN又冒出来服务端就会误以为这是新连接请求白开一个资源。有了第三次握手客户端发现这个老SYN对应的连接自己早就不要了就不会回应ACK服务端收不到第三次ACK自然也不会建立连接。实际排查时了解这个很有用。用tcpdump抓包看到只有SYN没有SYNACK说明包发出去了但对方没回应大概率是对方服务没起或者被防火墙拦截看到SYNACK发出去了但客户端没有回ACK多半是客户端这边有安全软件拦截或者NAT表项异常。我经常用这个判断连接建立到哪一步断了比瞎猜效率高得多。1.3 可靠性不是白给的确认、重传、滑动窗口TCP和UDP最大的区别就是可靠性。但这可靠性不是玄学是靠几个具体机制撑起来的。确认与重传是最基础的。发送方发出一个段会启动一个定时器在规定时间内没收到对方的ACK就重发。Nagle算法会把小包合并发送减少小包数量Delayed ACK让接收方攒几个包一起确认这些细节在日常使用中你感知不到但能直接影响交互式协议的手感。滑动窗口则负责流量控制。TCP不是发一个包等一个确认而是一次可以发一批窗口大小决定了“没收到确认前最多还能发多少”。接收方会在ACK里带上自己的窗口大小如果它处理不过来窗口会变小发太快甚至会变成0告诉发送方“你先停下来”。SSH、HTTP在弱网环境下卡顿很多时候不是应用层的问题而是TCP窗口在动态调整把发送速率压下来了。明白这一点你就不会在弱网环境下一味去调应用超时时间而是先看TCP重传率和窗口变化。还有一个拥塞控制这也是TCP被设计得“保守”的原因。TCP从慢启动开始拥塞窗口翻倍增长遇到丢包就减半甚至回归初始值。所以我一直建议做网络排查的人先学会看TCP层的指标——重传率、乱序率、RTT——再看应用层指标很多疑难杂症瞬间就有了答案。2. HTTP与TCP浏览器背后那条看不见的连接2.1 一次网页请求在TCP层面到底发生了什么你在浏览器输入一个网址回车表面上只是页面刷出来了背后发生了一串动作。先DNS解析出IP然后TCP三次握手建立连接从 tcpdump 能看到 SYN、SYN-ACK、ACK 三个包再发送HTTP请求服务端返回HTTP响应如果是HTTP/1.0老协议请求完还会立刻断开连接HTTP/1.1之后默认保持连接为的是让后续多个请求复用这条TCP隧道。这里有个容易混淆的概念HTTP本身是无状态的每个请求是独立的但它传输的载体TCP是面向连接的。上次提到的热词“http连接复用”就是这么来的。HTTP/1.1的Keep-Alive机制允许同一个TCP连接上连续发送多个HTTP请求节省了反复握手的开销。HTTP/2更激进直接在一条TCP连接上多路复用多个并发流一个慢请求不会阻塞后面所有请求。实际工作中我发现很多人排查HTTP慢上来就查数据库、查接口逻辑其实先把TCP层看一遍更有效。比如RTT很高、有大量重传那接口再快也没用数据到不了浏览器。用 curl -v https://example.com 能看到连接建立耗时和TLS握手耗时用 curl -w 还能把各阶段时间拆出来。这些是HTTP问题排查的第一手资料。2.2 连接复用与502/503TCP连接状态直接决定你看到的报错HTTP报错里最常见的就是502和503很多人误以为是应用挂了其实根子在TCP连接状态。502 Bad Gateway是网关Nginx、API网关、本地代理向后端发起TCP连接时失败或者建立后后端异常关闭503一般对应服务过载或主动拒绝连接。有个非常典型的场景本地跑了一个代理服务监听127.0.0.1:1572某个程序请求 http://127.0.0.1:1572 时报 “unexpected status 502 bad gateway: unknown error”。这个报错的关键在于“连接一个本地端口失败”。先确认端口有没有监听Linux下用 ss -lntp | grep 1572Windows下用 netstat -ano | findstr 1572。如果端口没监听那是上游服务没起来如果端口在监听但返回502那可能是代理程序自己连接上游失败或者它向后端发出的TCP连接被拒绝。还有一种情况是后端服务处于半连接状态比如数据库连接池爆了进程还在accept但已经不处理请求或者服务端因为某些原因主动关闭了keep-alive连接客户端还傻傻地复用于是收到RST浏览器里就表现为“连接被重置”或网关报502。排查这种问题的标准路径是看网关日志里的upstream status看后端服务日志再用curl从网关本机请求后端地址判断是TCP层不通还是应用层报错。每一层都有每一层的日志别一上来就重启服务。2.3 HTTPS到底改了什么还是TCP只是中间多了一层加密很多人以为HTTPS是另一个协议其实它的底层仍然是TCP。HTTPS在HTTP和TCP之间加了一层TLS/SSL。TCP仍然负责可靠传输数据TLS只负责给数据加密、校验完整性、验证身份。所以你可以把HTTPS理解成“把信纸装进了一个带锁的保险信封然后仍然用挂号信寄出”。这个结构会导致一个常见的性能问题建立HTTPS连接需要TCP三次握手1次RTT再加TLS握手至少1次RTT如果协商新密钥可能要2次RTT。所以在线事务较多的场景大家会把TLS会话缓存、会话票据session ticket开起来减少TLS握手的开销。HTTP/2和HTTPS几乎锁死因为HTTP/2的多路复用优势在TLS之上依然成立而且主流浏览器都要求HTTP/2跑在TLS上。排查HTTPS问题时要区分TCP层和TLS层。比如 openssl s_client -connect example.com:443 能完整看到TCP连接、TLS握手证书链、协议版本。如果TCP能通但TLS握手失败多半是证书问题、协议版本不匹配、或者中间设备比如防火墙干扰了TLS握手。我之前遇到过一台服务器应用本身没问题但curl始终报证书错误最后发现是系统时间差了几天证书校验因为时间不在有效期内而失败。TCP没问题、TLS报错这种“一层层剥洋葱”的思路在排障时非常管用。3. SSH与TCP远程管理为什么必须走“可靠通道”3.1 SSH和TCP的分工握手只是入场券后面还有加密和认证SSH是Secure Shell的缩写跑在TCP 22端口上。它的核心特征是先通过TCP三次握手建立一条可靠通道然后在这个通道里协商加密算法、交换密钥、完成认证之后才允许你敲命令。TCP连接只是“入场券”真正干活的加密和认证发生在TCP已经建立之后的应用层会话里。为什么SSH一定要用TCP而不是UDP因为SSH是一交互式终端协议你敲下的每一个字符、服务器回显的每一帧输出都不能丢。如果走UDP网络一抖动就可能丢字符终端命令回显不完整你用起来会抓狂。TCP的确认、重传、按序到达机制保证了终端数据的完整性和顺序性。顺带一提很多人听过的MoshMobile Shell确实使用UDP但它不是替代SSH认证的它还是在SSH认证成功之后建立会话用UDP来做本地回显和漫游属于“SSH都建立好了之后再加一层优化”的方案和SSH本身基于TCP并不冲突。SSH连接的详细过程TCP三次握手完成后客户端和服务端交换版本字符串然后进行密钥交换常见算法curve25519-sha256、ecdh-sha2-nistp256生成会话密钥之后才是用户认证阶段——密码认证或公钥认证。这也是为什么你SSH连不上的时候要分阶段排查TCP层通不通、SSH服务有没有监听、密钥协商是否成功、认证是否通过。每一阶段失败的表现都不一样。3.2 免密登录实操群晖、Git、VSCode背后的密钥配置逻辑热词里提到“群晖上配置好ssh密钥我走免密登录”这是SSH公钥认证的标准玩法。我把通用步骤整理出来不管是群晖、Ubuntu还是其他Linux发行版都适用。先在本地生成密钥对ssh-keygen -t ed25519 -C 你的备注一路回车就会在 ~/.ssh/ 下生成 id_ed25519私钥和 id_ed25519.pub公钥。公钥可以随便给别人私钥绝对不要泄露。然后把公钥放到服务器的 authorized_keys 文件里ssh-copy-id userserver_ip如果没有 ssh-copy-id就手动追加cat ~/.ssh/id_ed25519.pub | ssh userserver_ip mkdir -p ~/.ssh chmod 700 ~/.ssh cat ~/.ssh/authorized_keys chmod 600 ~/.ssh/authorized_keys注意权限非常关键。服务器端 ~/.ssh 目录要700authorized_keys 文件要600私钥本机也要600否则客户端会拒绝使用。之后 ssh userserver_ip 就直接免密登录。Git的SSH配置也是同一个逻辑本地生成密钥把公钥粘贴到GitHub/GitLab/Gitee后台然后测试 ssh -T gitgithub.com通了就可以用git协议推送代码。VSCode远程连接服务器时把公钥放到目标机器的authorized_keys里再在 ~/.ssh/config 里配置好Host别名、主机名、用户、私钥路径编辑器里就能一键连接。我踩过一个坑公钥明明放上去了服务器还是让你输密码。查一下SSH服务端日志如果提示“Authentication refused: bad ownership or modes”基本就是authorized_keys或家目录权限不对。另外一个冷门坑是SELinux开启了会阻止sshd读取新写入的authorized_keys需要用 restorecon -R -v ~/.ssh 恢复上下文。3.3 Ubuntu SSH连不上怎么办一套固定的排查路径“Ubuntu SSH无法连接”也是高频问题这里给一套固定排查顺序。第一步确认sshd服务在跑systemctl status sshd没起来就 systemctl start sshd。第二步确认22端口在监听ss -lntp | grep 22第三步本机测试回环ssh localhost如果本机都连不上问题在sshd配置或密钥权限如果本机能连、外部不能连基本是防火墙问题。Ubuntu默认可能没开ufw但如果你之前启用过记得放行22端口sudo ufw allow 22/tcp sudo ufw status还有一种隐蔽情况云服务器的安全组规则拦掉了22端口但本机防火墙完全没问题。我建议先看安全组再用 nc -vz server_ip 22 测试端口是否通最后再判断应用层。热词里有“网络攻击 ssh大量连接怎么办”这个也不少见。如果 /var/log/auth.log 里全是同一个IP反复尝试登录说明遭遇暴力破解。对策是禁止密码登录只留公钥认证把 /etc/ssh/sshd_config 里的 PasswordAuthentication 改成 no、修改默认端口虽然治标不治本、安装fail2ban自动封禁频繁失败的IP。普通小服务器做到这三步基本能挡住95%的扫描。4. FTP与TCP协议设计里最特殊的“双连接”4.1 一个协议两个TCP连接控制连接和数据连接怎么配合FTP是文件传输协议的老前辈但它里面藏着一个让很多人头疼的设计一个FTP会话同时使用两个TCP连接。控制连接默认走21端口负责传命令和响应比如用户名、密码、LIST、RETR、STOR这些指令数据连接负责传实际的文件内容或目录列表。所谓主动模式Active Mode下控制连接建立后客户端用PORT命令告诉服务器自己开了哪个端口服务器主动从20端口去连客户端的那个端口。问题在于如果客户端在NAT后面服务器根本连不进来。所以后来主流都改用被动模式Passive Mode客户端发PASV命令服务器返回一个随机端口客户端主动去连这个端口。这样对NAT友好得多。两种模式对比模式数据连接发起方优点缺点主动模式PORT服务器连客户端20端口服务器配置简单客户端在NAT后基本无法使用被动模式PASV客户端连服务器随机端口对NAT友好易穿透需要服务器防火墙放行端口范围记住这句话FTP控制连接永远由客户端发起但数据连接的发起方向随模式变化。很多FTP传不了文件的问题根子就在这里。4.2 能登录却传不了文件90%出在数据连接被拦截最经典的现象就是“FTP可以登录但没法传文件”用户名密码正确目录列表能出来一旦上传下载就卡死或者报错。因为控制连接通了能登录但数据连接被拦了传不了文件。防火墙和NAT是最常见的元凶。看到FTP的21端口开放就以为万事大吉忽略了被动模式还需要开放一批高位端口比如阿里云、腾讯云安全组默认不会放行1024以上端口。在服务器端配置vsftpd时固定被动端口范围是个好习惯pasv_enableYES pasv_min_port30000 pasv_max_port31000然后把防火墙和安全组的30000-31000也一起放行。如果是主动模式失败则要让服务器能主动连客户端但客户端在NAT后就很难支持我自己的建议是能不用主动模式就不用主动模式直接切被动模式。另外一个隐藏坑服务器返回给客户端的PASV地址是内网地址导致客户端拿到内网IP去连。这种情况需要在vsftpd里配置 pasv_address让服务器返回自己的公网IP例如pasv_address你的公网IP如果服务器本身在NAT后面做端口映射这一步必须做否则客户端永远连不上数据端口。热词里的“ftp可以登录无法传文件”基本都能归类到这几类原因。4.3 两台电脑通过FTP传文件完整实操和选型建议热词里有“两台电脑怎么通过ftp传文件”我直接给你一套最小可用方案。假设局域网内A电脑要传文件给B电脑把A当FTP服务器B当客户端。服务端最简单的方式是Windows自带IIS的FTP功能或者装FileZilla Server。我推荐FileZilla Server轻量又好配。安装后设置监听端口21创建用户并指定主目录Windows防火墙放行21端口和被动模式端口范围。客户端装FileZilla Client填A的IP、用户名、密码连接后就可以双向传输。不过这里我要多说一句如果只是为了临时传文件FTP未必是最优解。同一局域网用网线或WiFi共享、用python起个HTTP服务python3 -m http.server 8000甚至用scp都更简单。FTP在这类场景里的优势是断点续传和权限管理成熟但在公网上明文传输密码和数据是个大问题热词里也有“ftp 安全登录 ssh”和“ftp 0x800ffff”这样的衍生问题。我个人的建议是局域网内部传文件FTP够用跨公网传敏感数据选SFTP走SSH22端口加密或FTPSFTP over TLS。顺带提一句FileZilla客户端报“不支持的服务器”“不支持ftp over tls”这说明服务器端只提供普通FTP而客户端默认要求强制TLS加密。如果确认是自己搭建的测试环境可以在站点管理器里把加密方式改成“仅使用普通FTP”但公网上传任何重要文件我都不会这么做。4.4 SFTP不是FTP别再混淆这两兄弟很多人以为SFTP是FTP的安全版本这是个流传很久的误解。SFTP的全称是SSH File Transfer Protocol它建立在SSH协议之上复用SSH的22端口和加密通道而不是标准FTP的21端口加TLS。换句话说SFTP属于SSH协议族它和FTP只是名字长得像机制上完全不同。这也是为什么SFTP在NAT环境下通常更好用它和SSH一样只用一个TCP连接不像FTP那样需要动态开第二个数据连接。也因为这个原因我遇到“FTP无法穿透NAT”的问题时往往直接建议用SFTP替代。如果你只是想在两台电脑之间安全传文件而恰好已经在跑SSH服务直接用SFTP就行FileZilla客户端原生支持几乎是零成本迁移。5. TCP连接的“善后工作”拆分、挥手与端口占用的坑5.1 四次挥手和TIME_WAIT为什么端口会被“粘住”TCP连接建立要三次握手断开却要四次挥手原因在于TCP支持半关闭状态。第一次挥手主动关闭方发FIN表示“我的数据发完了”被动方回ACK表示“收到你的FIN”如果被动方还有数据没发完它会继续发等数据发完了被动方再发FIN表示“我这边也结束了”最后主动方回ACK确认。所以被动方的ACK和FIN往往不能合并成一次这就是四次而不是三次的原因。四次挥手带来一个实际影响主动关闭方会进入TIME_WAIT状态要等2MSL报文最大生存时间的两倍Linux上大概60秒才会释放连接。这个设计的初衷是怕最后一个ACK丢了对方没收到FIN的确认会重发FIN同时也为了等网络中的旧报文彻底消失避免它干扰新连接。TIME_WAIT本身是正常设计但在高并发短连接场景下大量连接处于TIME_WAIT会导致“端口被占住”的感觉——你明明没看到程序还在跑但端口就是起不来。这就是热词里那个报错的来源error: listen tcp 127.0.0.1:11434: bind: only one usage of each socket addrLinux上对应“Address already in use”。程序崩溃重启时端口还在TIME_WAIT或端口没被释放于是边界错误提示“该套接字地址只能使用一次”。解决方式有几种改监听端口、等60秒、或者在程序里设置SO_REUSEADDR。对于服务端监听端口SO_REUSEADDR允许重用处于TIME_WAIT的连接这是很多网络程序默认会做的设置。但对运维来说看到大量TIME_WAIT不代表有问题只要数量没有持续增长到把端口耗尽就不用过度紧张。排查时用 ss -s 看当前连接状态分布一眼就知道TIME_WAIT多不多。5.2 端口占用与超时两个高频报错的现场排查端口占用类报错有一个统一排查路径。Windows下netstat -ano | findstr 11434 tasklist | findstr PIDLinux下ss -lntp | grep 11434 lsof -i:11434找到占用进程后要么改配置换端口要么停掉旧进程。注意还有一种情况是服务实际上已经退出了但端口还处于TIME_WAIT如果是监听端口会自动释放如果是操作系统保留的高位端口没释放完等一下或用 sysctl 调整 net.ipv4.tcp_tw_reuse客户端连接可复用TIME_WAIT与 net.ipv4.tcp_tw_recycle老版本内核不建议开启。“TCP connect超时”也是高频问题。connect超时和连接拒绝是两回事连接拒绝是对方回了RST报错很快超时是SYN发出去后一直没人应答防火墙把包悄悄丢弃是常见原因。排查思路是分层测试先 ping 测通不通不通就是网络层问题通的话再 telnet IP 端口测TCP端口放没放。如果 ping 通但 telnet 端口超时极大可能是防火墙拦了目标端口而不是服务本身的问题。这类经验对排查SSH、HTTP、FTP的问题都通用因为它们的底层全是TCP。6. 一张速查表与我的排障心得6.1 SSH/HTTP/FTP常见问题速查表我把文章里涉及的问题整理成一张速查表方便你遇到具体报错时直接对号入座。现象可能原因优先排查动作SSH连不上提示超时22端口被防火墙/安全组拦截nc -vz IP 22查看安全组规则SSH免密登录仍要密码authorized_keys权限不对或内容有误查看sshd日志检查目录权限HTTP请求报502网关连不上后端TCP端口或后端异常关闭在网关本机curl后端地址看后端日志HTTP连接被重置服务端关闭了keep-alive连接客户端复用旧连接抓包看是否有RST关闭连接复用测试FTP能登录但无法列目录/传文件数据连接被防火墙拦截或PASV地址错误切换被动模式放行端口范围配置pasv_addressFTP登录报错0x800ffff客户端路径或连接方式问题用FileZilla代替资源管理器确认端口和模式bind报Address already in use端口被占用或处于TIME_WAITss/netstat查占用进程或等待释放TCP connect超时防火墙drop包服务未监听ping确认网络telnet确认端口这张表我建议你保存在手边遇到问题先对位再动手。很多时候不是不会排查而是排查顺序乱东看一眼西看一眼反而把问题搞复杂。6.2 学会抓包比什么教程都管用接入了TCP这个层面之后我想认真建议你去学抓包。理解“SSH、HTTP、FTP基于TCP”这件事只看文字很难有体感但抓到一次包就全通了。最常用的抓包工具是tcpdump和Wiresharktcpdump适合在服务器上快速验证Wireshark适合图形化分析。看一次完整的三次握手可以这样tcpdump -nn -i any tcp port 22然后另开一个终端执行 ssh userserver。你能看到三个包SYN、SYN-ACK、ACK。再看一次HTTP请求把端口换成80或443你能看到TCP握手之后紧跟着HTTP GET请求HTTP报文被TCP段完整包在里面。看FTP则更明显控制连接上能看到USER、PASS、LIST这些命令数据连接打开时会另有一个TCP连接在这个端口上建立。判断某个问题出在TCP层还是应用层我有个习惯性的二分法抓包里能看到TCP握手成功说明基础通道没问题再看应用层协议交互握手都没成功就别去看应用配置先把网络层和防火墙查明白。很多难缠的问题都是这两层混在一起说的最后用抓包把责任划分清楚问题自然就清晰了。6.3 从“协议三兄弟”延伸出去更多基于TCP的常见场景理解了SSH、HTTP、FTP基于TCP你会发现自己其实掌握了一个通用框架。因为基于TCP的协议远不止这三个热词里还有不少例子。比如modbus tcp是工业自动化设备通信常用的协议它同样是把Modbus报文封装在TCP里靠TCP的可靠性保证指令一定要送达又比如嵌入式场景里的ESP01S模块很多教程让你“用TCP连接到服务器”本质就是先用AT指令建立TCP连接再在上面跑MQTT或自定义协议再比如Ubuntu下用socat做UDP转TCP是因为有些应用只接受TCP连接但设备端只发UDP需要在中间做一个协议转换。从TCP的视角看这些场景你会发现它们共享同一个底层机制连接建立、可靠传输、连接断开。区别只在于应用层把字节流解释成什么格式。这也是我写这篇文章想传达的核心把TCP这块地基搞明白你就不会再被各种看似无关的报错搞得晕头转向。每一条错误信息背后都是一个TCP连接在某个阶段出了问题。最后分享一个我自己的习惯遇到任何网络协议相关的疑难杂症第一件事永远是先抓包第二件事是看TCP连接状态第三才是查应用日志。按这个顺序走绝大多数问题都能在半小时内定位。如果你还没有抓包习惯可以找个周末自己架一台虚拟机跑一个HTTP服务、一个SSH服务、一个FTP服务然后分别抓包看看三种协议在TCP之上的不同长相。抓过一遍之后再遇到生产问题你心里会踏实很多。