图 1:本文封面,SYN 到达未监听端口,协议栈直接回 RST。
先说明属性:这是一篇笔试真题复盘,题目出自本地历史笔试资料,与任何公司的当前招聘无关。题面:向一台运行正常、但 80 端口没有进程监听的机器发送 TCP SYN 报文,会收到什么响应?答案:对方协议栈回一个 RST 报文,连接立即失败。注意题面里「机器运行正常」这个限定——它排除了主机宕机、网络中断这些干扰项,把问题聚焦到协议栈的行为上。
为什么是 RST:协议规范的直接规定。
TCP 规范 RFC 9293 在「复位生成」一节写得明白:如果连接不存在(处于 CLOSED 状态),对任何到达的报文段(RST 本身除外)都应回一个 RST;不匹配任何已有连接的 SYN,正是用这种方式被拒绝的。机器上没有任何套接字监听 80 端口,对内核来说这个 SYN 就不属于任何连接,于是按规范回 RST。
报文细节也值得说:由于入方向的 SYN 不带 ACK 位,规范要求回包序号置 0、ACK 位置位、确认号填「SYN 序号 + 报文长度」——所以精确地讲,回去的是一个带 ACK 标志的 RST。发送方收到后连接直接进入 CLOSED,connect 调用立刻以「连接被拒绝」返回,不重传、不等待。
一个 SYN 的三种命运。
把题面放进完整图景里看,一个 SYN 发出后只有三种结局。场景一,端口已监听:收到 SYN+ACK,客户端再回 ACK,三次握手完成,连接进入 ESTABLISHED。场景二,端口未监听(本题考点):协议栈回 RST,连接立刻失败——这是对端的明确表态。场景三,防火墙静默丢包:什么回复都没有,客户端只能按指数退避重传 SYN,几次之后超时放弃。
场景二和场景三必须分清:收到 RST 说明「主机在线、协议栈在工作、只是这个端口没人接」;超时无响应说明「包在路上被丢了,或被防火墙拦了」,对端主机可能完全正常。排障时把「拒绝」误读成「不通」,方向就错了。
端口扫描工具正是靠这三种结局给端口分类的:发出 SYN 后,收到 SYN+ACK 判 open,收到 RST 判 closed,迟迟无响应(或收到 ICMP 不可达)判 filtered。理解了这道题,扫描报告里每一行的含义就都能看懂了。
本地实测:拒绝来得有多快。
用 Node.js 在本机对比两种场景:向未监听的 18081 端口发起连接,1 毫秒内收到 ECONNREFUSED 错误——这就是 RST 到达后内核给应用层的答复;再向已起好监听服务的 18080 端口发起连接,握手成功、连接建立。代码如下:
import net from 'node:net';
function probe(port, label) {
return new Promise((resolve) => {
const sock = net.connect({ host: '127.0.0.1', port });
const t0 = Date.now();
sock.on('connect', () => {
console.log(`${label}: 三次握手成功,耗时 ${Date.now() - t0}ms`);
sock.end(); resolve();
});
sock.on('error', (err) => {
console.log(`${label}: ${err.code},耗时 ${Date.now() - t0}ms`);
resolve();
});
});
}
const server = net.createServer((s) => s.end());
await new Promise((r) => server.listen(18080, '127.0.0.1', r));
await probe(18081, '未监听端口');
await probe(18080, '已监听端口');
server.close();
实测输出:未监听端口 1ms 报 ECONNREFUSED,已监听端口握手成功。拒绝与成功的差异,就是对方内核里有没有一个监听套接字的差异。同一个 RST,换到别的语言栈里是 Java 的 ConnectException、Python 的 ConnectionRefusedError——报错文案各不相同,背后的报文是同一个。
还有一点容易误解:RST 并不只出现在建连期。RFC 9293 的复位规则覆盖整个状态机,对已建立连接的异常报文、服务端重启后收到的旧连接数据,同样会触发 RST——它是 TCP 表达「这个报文不属于任何现存连接」的通用手段,本题的「SYN 到未监听端口」只是其中最经典的一种。
图 2:三种命运对照,以及本题用到的三个 TCP 标志位。
面试现场的答题结构。
先一句话给结论:收到 RST(精确说带 ACK 位的 RST),连接立即失败。然后给出规范依据:连接不存在时对到达报文段回 RST,SYN 不匹配任何已有连接即被拒绝。接着主动扩展到三种命运对照,把 RST 与超时区分开——这是这题从「背答案」到「懂排障」的分水岭。最后落到工程语义:健康检查看到 connection refused,含义是「机器活着、端口没开」,而不是网络故障,排查方向应该指向服务进程为什么没起来。你在抓包或排障时遇到过哪些意料之外的 RST?欢迎在评论区分享当时是怎么定位的。