☰
Linux网络编程 errno 错误码全解:从 TCP/IP 内核行为到工程排查范式
2026/10/20 6:16:16 网站建设 项目流程

Linux网络编程里,错误码(errno)大概是新手最不重视、老手又最依赖的东西。很多人在调网络程序时,遇到Connection refused就以为对端没启动,遇到Broken pipe就以为是自己代码写错了,其实这些错误码背后藏着完整的TCP/IP行为逻辑。我在日常排查中几乎都是从errno入手反推问题根因的,一个错误码往往能直接告诉你内核在哪一层做了什么样的决定。这篇文章把我在Linux网络编程里高频遇到的错误码全部梳理一遍,包括它们的触发现场、内核行为逻辑、以及真正有效的排查思路和代码处理范式,适合正在写网络服务、或者被各类连接问题折磨过的开发者参考。

1. 连接建立失败:三个最常见错误码的现场特征

1.1 ECONNREFUSED:端口无人认领,却被RST挡了回来

ECONNREFUSED对应的数字是111,字符串是Connection refused。这个错误码的主力触发场景是:客户端发起connect()时,目标IP地址能到达,但目标端口上没有任何进程在监听。内核收到SYN包后,直接回了一个RST包,客户端这边的connect()就会立刻返回ECONNREFUSED。

这里有个容易被忽略的细节:返回ECONNREFUSED不代表对端主机不可达,恰恰说明网络路径是通的。因为RST能回到你这里,说明SYN包已经成功到达对端,只是对端协议栈觉得“这个端口我不认领”,才礼貌地回了拒绝信号。如果对端防火墙直接丢包,你会看到的是超时而不是拒绝。

我遇到过一个比较典型的场景:某服务A依赖服务B的内部RPC端口,某次升级后服务B改了监听端口但配置文件没同步,结果上游服务A全都报ECONNREFUSED。排查时第一反应不是去看服务B是否存活,而是去检查服务B实际监听的端口,一条ss -lntp就能看到答案。

还有个容易被忽视的触发方式:对端防火墙主动发送RST。有些安全策略会对未授权端口直接回RST,这会让客户端表现出的现象和“端口未监听”完全一样。所以看问题不能只看errno本身,还要结合tcpdump确认RST的来源方向。

提示:用connect()探测一个端口是否开放,如果秒回ECONNREFUSED,基本可以判断端口确实没监听或主动被拒;如果卡住不动直到超时,则说明包被防火墙静默丢弃了。

1.2 ETIMEDOUT:静默丢弃比拒绝更折磨人

ETIMEDOUT数字是110,字符串是Connection timed out。它的含义是SYN包发出后,客户端在超时时间内始终没有收到任何回复。Linux内核默认的重传策略会尝试多次,通常第一次SYN发出后1秒左右重传,之后间隔指数增大,整体可能要等一两分钟才返回错误。

ETIMEDOUT最常见的幕后黑手是防火墙静默丢包。有些防火墙策略会把未经允许的SYN包直接丢弃而不回RST,没有RST就没有拒绝信号,客户端只能干等超时。这种问题最难排查,因为从应用层看就是“连接卡住”,tcpdump在客户端上可以看到SYN反复重传但始终没有回应。

另一个ETIMEDOUT场景是跨机房或跨地域建连,中间的网络路径存在丢包,导致SYN包一直没能到达对端。这种情况下可以缩小MTU、检查路由、看是否有中间设备做了限速。还有一次我遇到比较隐蔽的:客户端配置了错误的IP,把包发到了一个黑洞路由(比如内网有个设备配置了null route),结果就是长超时而不是拒绝。

处理ETIMEDOUT的工程思路有几个方向:connect超时设置不能太死,但也不能无限等;对于需要快速失败的场景,可以考虑用非阻塞connect配合定时器,或者直接缩短TCP SYN重传次数:

# 查看当前SYN重传次数上限 cat /proc/sys/net/ipv4/tcp_syn_retries # 调低重传次数,加快失败反馈 sysctl -w net.ipv4.tcp_syn_retries=3

不过这个参数是全局限的,改之前要想清楚会不会影响其他连接。我个人更推荐在应用层做超时控制,而不是轻易动内核参数。

1.3 ENETUNREACH与EHOSTUNREACH:路由层的信号

ENETUNREACH(101,Network is unreachable)表示本地路由表中根本找不到去往目标网络的路径;EHOSTUNREACH(113,No route to host)表示路由能找到网络,但最后一跳(通常是ARP解析)失败,没法定位到具体主机。

这两个错误码在实际生产中出现的频率没有前两个高,但一旦出现,往往意味着配置层面的问题。比如某个服务实例绑定了错误的网段IP,或者容器的网络命名空间里没有配置默认路由,客户端去连接时就会直接返回ENETUNREACH,秒失败,连SYN都不会发出去。

我处理过的一个案例:某服务从物理机迁移到容器后,客户端开始报EHOSTUNREACH。最后发现是容器网络插件没给容器分配正确的网关,容器内的APP去连外部IP时ARP请求无人应答。用ip route查看宿主机和容器内的路由表,立刻能看出差别。

错误码errno值典型含义排查方向
ECONNREFUSED111端口无监听/被RST拒绝目标端口监听状态、防火墙RST策略
ETIMEDOUT110SYN无响应/包被丢弃防火墙静默丢弃、路由丢包、黑洞
EHOSTUNREACH113ARP解析失败/路由不通路由表、网关配置、ARP表
ENETUNREACH101无到目标网络的路由本地路由表、默认网关

2. 连接生命周期中的“断头台”:ECONNRESET、EPIPE与SIGPIPE

2.1 ECONNRESET的多种来源:RST不是随便来的

ECONNRESET(104,Connection reset by peer)表达的意思非常明确:你收到的是一个RST包。RST的产生原因比想象中多,至少有以下几类:

  • 对端进程崩溃或者被强杀,内核在清理socket时会发送RST(关键是,所有未读完的接收队列数据被丢弃,对端协议栈不再为这个连接维护状态)。
  • 对端已经关闭连接,但你还继续往里面写数据,对端收到一个“不属于任何已建立连接”的数据段,就会回RST。
  • 对端应用调用了close()时,接收缓冲区里还有未读取的数据,此时内核会直接发RST而不是正常的FIN握手。
  • 中间设备(比如负载均衡、防火墙)主动注入RST,常见于TCP链接空闲超时后被设备清理。

区分这些来源要靠抓包。tcpdump看到RST包时,注意看RST的序号和方向。如果是写数据后立刻收到RST,检查是不是自己在对端close之后还继续写;如果是长期空闲后收到RST,很可能中间设备主动断链。

这里有个非常经典的坑:对端程序崩溃时,客户端第一次read()调用可能既不返回0也不报错,而是返回ECONNRESET。因为内核崩溃时发送的是RST,而不是正常的FIN。正常的对端关闭是FIN包,read()返回0表示EOF;异常关闭是RST包,read()返回-1且errno为ECONNRESET。我在代码里经常看到有人只处理read() == 0的情况而忽略了read() == -1 && errno == ECONNRESET,结果连接被RST后服务端毫不知情,直到下一次写数据才暴露问题。

2.2 EPIPE与SIGPIPE:写一个已经关闭的socket会发生什么

EPIPE(32,Broken pipe)发生在你对一个“已经收到对端RST”或“对端已经关闭读方向”的socket执行写操作时。内核会把这个错误返回给应用,同时在默认情况下发送SIGPIPE信号给当前进程。如果你没有捕获SIGPIPE,进程会直接退出。

这解释了为什么很多网络服务莫名其妙“挂掉”:主进程或工作进程在某个写操作上触发了SIGPIPE,信号默认动作是终止进程。解决办法有两个层面:

一是全局忽略SIGPIPE:

signal(SIGPIPE, SIG_IGN);

二是写入时使用send()的MSG_NOSIGNAL标志:

ssize_t n = send(fd, buf, len, MSG_NOSIGNAL); if (n < 0 && errno == EPIPE) { // 对端已经关闭,做清理 }

MSG_NOSIGNAL只影响本次调用,不会产生信号,由你去处理EPIPE,这种写法在事件驱动模型里更可控。我在实际代码中两种都会用:全局忽略SIGPIPE做兜底,写操作时再用MSG_NOSIGNAL加精细处理。

2.3 TCP半关闭与连接重置的区别

正常关闭流程是四次挥手:A发FIN,B回ACK,B也发FIN,A再回ACK。这个过程中的关键点在于FIN只是关闭了发送方向,接收方向依然是打开的,这就是所谓的半关闭(half-close)。

很多业务服务用不到半关闭,但对某些协议来说,它非常重要:客户端发送完请求后调用shutdown(SHUT_WR)通知服务端“我不再发数据了,但还能收”,服务端处理完响应后关闭连接。这个模式下,如果客户端在处理完响应后没有正确关闭socket,就可能出现服务端认为连接已结束,而客户端还试图发送数据的情况,进而触发RST。

区分正常关闭和异常关闭,客户端read()返回值是关键信号:

  • read()返回0:收到FIN,对端正常关闭写方向。
  • read()返回-1且errno为ECONNRESET:收到RST,对端异常重置。
  • 一直阻塞没有返回:对端既没关闭也没数据,需要考虑应用层超时。

写代码时,我会把所有socket关闭路径统一收敛到一个函数里,明确区分“正常关闭(shutdown + close)”和“异常关闭(close + 清理资源)”,避免因为关闭顺序问题把正常的FIN流程变成RST。

3. 资源耗尽类错误:EMFILE、ENFILE、ENOBUFS的真实含义

3.1 EMFILE:进程文件描述符上限

EMFILE(24,Too many open files)是最常见的“软资源”耗尽错误。每个进程都有文件描述符上限,可以用ulimit -n查看。这个限制包括所有类型的fd:socket、文件、epoll实例、eventfd、timerfd,等等。

出现EMFILE的典型场景是:高并发服务连接数增长过快,fd分配速度超过回收速度,或者存在fd泄漏(比如某个分支里open了socket但没close)。排查EMFILE的关键动作是看fd都去哪了:

# 查看进程打开的fd数量 ls /proc/<pid>/fd | wc -l # 查看各fd类型分布 ls -l /proc/<pid>/fd

如果发现大量socket处于CLOSE_WAIT状态,说明对端已经关闭,但你的应用还没调用close()。CLOSE_WAIT的堆积几乎都指向应用层没有正确读取对端关闭事件,这在后面第6节的排障实战里会再详细展开。

处理EMFILE有两个方向:一是调大ulimit -n,但这只是“加高水位”,不解决泄漏本质;二是找到泄漏点。我见过一些服务上线后连接数缓慢爬升,重启后回落,典型的fd泄漏特征。

3.2 ENFILE:系统全局文件表溢出

ENFILE(23,Too many open files in system)和EMFILE的区别在于,前者是进程级限制,后者是系统全局限制。系统全局限制由/proc/sys/fs/file-max控制,表示内核整个文件表的大小。

ENFILE在普通业务场景里出现的概率较低,但也不是没有。比如某个主机上跑的容器或进程数量特别多,每个进程各自打开了大量文件,系统级文件表就可能会被打满。另外/proc/sys/fs/file-nr可以实时查看已分配、未使用、总上限的值:

cat /proc/sys/fs/file-nr # 输出格式: 已分配fd数量 未使用fd数量 系统上限

遇到ENFILE时,正确的思路是先确认“分配出去的文件描述符是否合理”。如果确实需求量大,可以临时调大file-max:

sysctl -w fs.file-max=2000000

但如果服务器本身没有超卖,那大概率是某个进程异常打开了海量fd,还是需要回到进程级别去查。

3.3 ENOBUFS:内核缓冲区不足

ENOBUFS(105,No buffer space available)在socket编程里通常在两种情况下出现:

  • send()时系统内存不足,无法分配发送缓冲区。
  • 路由缓存或网络设备缓冲区被耗尽。

曾经遇到过这样的案例:一台机器上批量发送大量UDP包,结果某个时刻send()开始返回ENOBUFS。原因是发得太快,本地发送队列堆积,内核分配不出足够的内存来缓存数据。UDP不像TCP有流控,应用不看对端接收能力一股脑地发,发送缓存很快就满了。

解决UDP发送端ENOBUFS的思路包括:应用层加流控或重试退避;适当调大socket发送缓冲区:

int bufsize = 4 * 1024 * 1024; setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &bufsize, sizeof(bufsize));

但要注意,setsockopt只负责“尽可能分配”,内核可能翻倍后仍然限制在系统上限内。如果系统本身内存吃紧,应该先去解决内存占用问题,而不是一味加大缓冲区。

4. 地址与端口配置错误:EADDRINUSE、EADDRNOTAVAIL的纠缠

4.1 EADDRINUSE与TIME_WAIT的恩怨

EADDRINUSE(98,Address already in use)出镜率极高,主要出现在bind()和listen()阶段。当一个TCP连接正常关闭后,主动关闭方的socket会进入TIME_WAIT状态,持续2MSL(默认约60秒)。在这段时间内,如果程序立刻重启并尝试绑定同一个端口,就会命中EADDRINUSE,除非设置了SO_REUSEADDR。

这个错误让不少新手困惑:明明连接已经关闭了,为什么端口还被占用?ss -tan可以清楚看到一堆TIME_WAIT状态的连接:

ss -tan | grep TIME_WAIT

TIME_WAIT是TCP协议的主动性设计:它保证延迟到达的报文段不会污染新的连接。主动关闭方必须等待足够长的时间,确保网络上的重复数据段彻底消亡。理解了这一点,就不会觉得TIME_WAIT“多余”,而是能意识到它是一道安全屏障。

4.2 SO_REUSEADDR与SO_REUSEPORT的正确用法

SO_REUSEADDR解决的是TIME_WAIT状态下的端口复用问题。在bind()之前设置它,允许端口在TIME_WAIT状态下重新绑定:

int opt = 1; setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

这个设置针对的是主动关闭方。如果进程因为崩溃重启,之前有大量服务端出站的连接处于TIME_WAIT,不设SO_REUSEADDR就会全部卡死,直到TIME_WAIT过期。

SO_REUSEPORT则解决的是另一个问题:多个socket绑定同一个端口,内核在接收数据时做负载均衡。典型用途是多进程/多线程模型下,每个工作进程独立创建监听socket,都绑定同一个端口:

int opt = 1; setsockopt(listen_fd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));

需要注意的是,SO_REUSEPORT要求所有绑定同一端口的socket都必须设置该选项,否则后续绑定会失败。而且它的负载均衡算法在不同内核版本里略有差异,老内核是简单的哈希取模,新内核引入了更精细的哈希扰动。如果对分发的均匀性要求很高,还是建议在应用层做一致性分配,而不是完全依赖内核。

4.3 EADDRNOTAVAIL与绑定非本地地址

EADDRNOTAVAIL(99,Cannot assign requested address)常见于两种场景:

  • bind的IP不是本机任何网卡上配置的IP。
  • connect的目标IP在本地路由表中不可达(和ENETUNREACH有重合)。

第一种场景很典型:服务配置了某个VIP或者内网IP,但该IP没有实际绑定到网卡上(比如keepalived的VIP在备机上不存在),你在备机上启动服务并bind这个VIP时,就会得到EADDRNOTAVAIL。解决办法是先确认该IP确实在当前主机的网卡上:

ip addr show

如果确实需要在没有本地IP的情况下“抢注”某个地址,Linux提供了ip_nonlocal_bind内核参数:

sysctl -w net.ipv4.ip_nonlocal_bind=1

这个参数在透明代理或负载均衡场景下比较有用,普通应用不建议随便开启,否则可能导致服务监听了不属于本机的IP,造成流量路由混乱。

5. 非阻塞编程的“伪错误”:EAGAIN、EINTR与事件驱动误判

5.1 EAGAIN/EWOULDBLOCK:不是错误,是节奏

EAGAIN(11,Resource temporarily unavailable)大概是被误解最多的错误码。在Linux上它和EWOULDBLOCK是完全相等的值,都是11。这个错误出现在非阻塞模式下:当socket的发送缓冲区已满或接收缓冲区为空时,read()/write()/accept()不会阻塞等待,而是立刻返回-1并设置errno为EAGAIN。

很多刚接触非阻塞编程的人,一看到EAGAIN就当成“出错了”,在日志里疯狂记录错误,甚至直接关闭连接。实际上EAGAIN是非阻塞模式下的正常反馈,意思是“现在缓冲区状态不允许,请稍后再试”。

正确的处理方式是事件驱动模型配合epoll:注册可读/可写事件,被事件唤醒后再去read/write,如果返回EAGAIN,就回到事件循环等待下一次通知。而不是在一个循环里忙轮询直到非阻塞调用成功,那会把CPU打满。

ssize_t n = recv(fd, buf, sizeof(buf), 0); if (n < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { // 数据还没到,等待epoll的EPOLLIN事件 return; } // 其他错误,比如ECONNRESET,需要处理 }

一个值得注意的细节:EAGAIN不只是read上会碰到,accept()和connect()也会碰到。非阻塞listen socket的accept()在“当前没有新连接”时返回EAGAIN;非阻塞connect()在没有立即建立连接时同样返回EAGAIN,此时需要监听socket的可写事件来判断连接是否完成。很多人在这里踩坑,把connect的EAGAIN当成了失败。

5.2 EINTR:信号打断与重启

EINTR(4,Interrupted system call)表示阻塞的系统调用被信号打断。Linux下如果一个进程设置了信号处理器,当信号到达时,正在阻塞中的系统调用会提前返回,errno设置为EINTR。

EINTR在老代码里经常是个坑:人们以为调用失败,直接返回错误,导致程序在运行中被信号轻轻一碰就断开连接。处理EINTR的标准姿势是重试被中断的调用,很多系统调用提供了SA_RESTART标志来自动重启,但并不是所有的调用都支持(比如epoll_wait就不一定会被自动重启)。

我更推荐在代码里显式处理EINTR:

ssize_t n; do { n = read(fd, buf, sizeof(buf)); } while (n < 0 && errno == EINTR);

这样写不会丢失任何数据,逻辑也清晰。顺便说一句,在使用epoll时,epoll_wait()也经常会被信号打断,必须同样处理EINTR。

5.3 epoll_wait返回EPOLLERR/EPOLLHUP的排查

epoll的epoll_wait()返回事件时,经常出现EPOLLERR或EPOLLHUP标志,但errno往往是0。这时不能依赖errno,而是要自己主动去探测socket状态。

EPOLLERR表示socket上发生了错误,通常伴随ECONNRESET等底层错误。EPOLLHUP表示对端挂断,通常是对方关闭了连接。实践中这两个标志经常一起出现。处理方式是:一旦收到EPOLLERR或EPOLLHUP,主动调用recv或getsockopt(SO_ERROR)去获取具体错误,然后关闭连接、清理资源。

有一个隐蔽的坑:收到EPOLLHUP时,socket的接收缓冲区里可能还有未读完的数据。如果直接close,数据就丢了。更稳妥的做法是先尝试读完剩余数据再关闭。读取时如果返回ECONNRESET,再彻底清理。

if (events & (EPOLLERR | EPOLLHUP)) { // 先尝试把还能读到的数据读完 for (;;) { ssize_t n = recv(fd, buf, sizeof(buf), 0); if (n > 0) { handle_data(buf, n); } else if (n < 0 && errno == EAGAIN) { break; } else { // n == 0 或错误 break; } } close(fd); }

6. 一次基于错误码逆推根因的排障全记录

6.1 现象:连接成功率突降

某天值班时接到告警,某内部服务的连接成功率从99.9%掉到85%,大量请求失败,报错要么是Connection reset by peer(ECONNRESET),要么是Broken pipe(EPIPE)。我当时的反应经验是:这类错误不像超时那样指向网络路径问题,更像是在“连接建立后很快被对端重置”。

先看的不是服务日志,而是连接的TCP状态分布。在接入网关的机器上执行:

ss -tan | awk '{print $1}' | sort | uniq -c | sort -rn

结果让我注意到一个异常:CLOSE_WAIT数量从平时的几十涨到了几千。CLOSE_WAIT的意思是“连接对端(客户端)已经关闭,但本端还没调用close()”。几千个CLOSE_WAIT堆积,说明后端服务进程里有大量socket没有被正确关闭。

6.2 tcpdump与strace双线取证

为了确认RST的来源和触发顺序,我在网关和后端两台机器上同时抓包:

tcpdump -i eth0 port <后端端口> -w reset.pcap

抓包结果很直观:客户端发送请求,后端返回响应,然后客户端发送FIN正常关闭(因为客户端走的是短连接模式,请求完就关),但后端没有回FIN,也没有回ACK去推进关闭流程,导致连接挂在CLOSE_WAIT。过了一段时间,可能是客户端或者中间设备等不及,注入了RST,后端读到RST。

同时用strace观察后端进程:

strace -p <pid> -e trace=close,read,recvfrom -f

结果发现,后端进程里接收请求的线程在某个早期分支return之后,socket fd被直接丢弃了,没有进入统一的close路径。一旦请求满足某个条件,处理线程提前返回,fd就泄漏了。这正是CLOSE_WAIT堆积的根因:对端已经close,本端却永远不close。

6.3 根因与修复

问题定位清楚后,修复分了两步:

第一步是给所有提前返回的分支统一补上close(fd),确保无论什么路径,连接都能被释放。代码评审时专门约定了一条铁律:所有socket fd的创建和释放必须成对出现在同一个函数栈内,除非显式转移所有权,这样可以靠code review把类似的泄漏消灭在源头。

第二步是给服务加上CLOSE_WAIT监控。CLOSE_WAIT的堆积速度非常快,但平时不容易被注意到,等到报错大批量出现时,往往已经堆积了很长时间。加一个监控脚本,定时统计ss -tan里的CLOSE_WAIT数量,超过阈值就报警。CLOSE_WAIT是“应用层没有close”的高特异性指标,一旦涨起来,几乎肯定有泄漏。

这次排障给我的经验是:ECONNRESET和EPIPE只是表象,真正的问题藏在socket生命周期管理里。错误码能告诉你在哪个环节出了问题,但排查深度决定了你只是修了表象还是彻底解决了病根。

7. errno的工程化处理:从打印到防御性编程

7.1 errno的线程安全与拷贝时机

errno是一个线程局部变量,理论上每个线程有自己的errno,这在多线程编程里是安全的。但有一个很隐蔽的坑:errno只在系统调用返回-1的那个瞬间有意义,之后任何一次库函数调用都可能覆盖它。比如:

if (connect(fd, ...) < 0) { printf("errno=%d\n", errno); // 可能已经变成了printf内部系统调用的errno }

printf本身也可能触发系统调用并改写errno。正确的做法是先保存再使用:

if (connect(fd, ...) < 0) { int saved_errno = errno; // 后续用saved_errno }

7.2 strerror_r的安全使用

打印错误信息时,strerror()是不线程安全的(它返回一个静态缓冲区地址),多线程环境下可能出现字符串被覆盖的竞态。Linux提供了线程安全版本strerror_r(),但它的返回类型因标准不同有差异(XSI版本返回int,GNU版本返回char*)。

为了避免行为随编译环境变化,我通常自己封装一层,只关心结果存到用户缓冲区:

static void format_errno(char *buf, size_t len, int err) { if (buf == NULL || len == 0) return; #if defined(__GLIBC__) && defined(_GNU_SOURCE) char *p = strerror_r(err, buf, len); if (p != buf && p != NULL) { snprintf(buf, len, "%s", p); } #else (void)strerror_r(err, buf, len); #endif }

实际开发中,多数日志系统自带errno格式化功能,能用就用,自己写反而容易埋雷。

7.3 错误处理代码范式:把错误留在边界

网络层错误处理的最高原则是:函数内部只关注自己职责内的错误,其余的错误要原封不动地带到上层去处理。不要在一个底层函数里又是打日志又是关连接,那是上层的事情。

我个人习惯的做法是:底层网络库的每个函数返回-1时设置errno,同时约定调用方负责检查errno并决定策略。在业务处理边界统一做错误分类:

  • 可重试错误:ETIMEDOUT、EAGAIN(针对非阻塞)、EINTR。对应的策略是退避重试或继续等待事件。
  • 致命连接错误:ECONNRESET、EPIPE、ECONNREFUSED。对应的策略是关闭连接、清理状态、上报统计。
  • 资源错误:EMFILE、ENFILE、ENOBUFS。对应的策略是暂停接受新连接、触发资源回收或告警。

这样一个清晰的三分类,能避免错误处理散落在各处,也让排障时只需要看一个统一的日志入口就能知道错误分布。

enum err_class { ERR_RETRYABLE, ERR_FATAL, ERR_RESOURCE }; enum err_class classify_errno(int err) { switch (err) { case EINTR: case EAGAIN: case ETIMEDOUT: return ERR_RETRYABLE; case ECONNRESET: case EPIPE: case ECONNREFUSED: case EHOSTUNREACH: return ERR_FATAL; case EMFILE: case ENFILE: case ENOBUFS: return ERR_RESOURCE; default: return ERR_FATAL; } }

当然,分类不是死的,具体业务可以调整。比如客户端场景下ECONNREFUSED可能对应“服务端尚未就绪”,重试几次也无妨;服务端场景下EMFILE则是必须立刻处理的资源告警。关键是把“错误码—行为策略”的对应关系显式化,而不是散落在if-else里。

我在实际项目里还习惯给每个错误码加一条采集指标:每分钟各errno出现次数。这样线上出现异常时,看监控曲线就能快速判断是哪个环节出了问题,不用再翻半天日志。网络编程的最终竞争力不全在协议上,而在于把错误码背后的内核意图理解透,再用工程手段把这种理解固化到代码和监控里。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询