# socket - 常见错误

最近在试着使用代理 ip 的时候,经常会遇到一些错误代码,查阅资料后了解到是 Socket 的错误。记录一下。

# ECONNRESET

远程主机重置连接请求。

该错误被描述为“connection reset by peer”,即“对方复位连接”,这种情况一般发生在服务进程较客户进程提前终止。当服务进程终止时会向客户 TCP 发送 FIN 分节,客户 TCP 回应 ACK,服务 TCP 将转入 FIN_WAIT2 状态。此时如果客户进程没有处理该 FIN (如阻塞在其它调用上而没有关闭 Socket 时),则客户 TCP 将处于 CLOSE_WAIT 状态。当客户进程再次向 FIN_WAIT2 状态的服务 TCP 发送数据时,则服务 TCP 将立刻响应 RST。一般来说,这种情况还可以会引发另外的应用程序异常,客户进程在发送完数据后,往往会等待从网络IO接收数据,很典型的如 read 或 readline 调用,此时由于执行时序的原因,如果该调用发生在 RST 分节收到前执行的话,那么结果是客户进程会得到一个非预期的 EOF 错误。此时一般会输出“server terminated prematurely”-“服务器过早终止”错误。

可能的原因:

  • socket 的 ECONNRESET 错误一般是因为TCP连接的一方突然关闭连接。http agent不能自己吞下(swallowed)这个错误,因为吞下后,比如你就不能通过这个错误分清由unclean termination造成的empty reply。
  • socket 抛出未处理的 ECONNRESET,在不同版本的node中,有些版本这个错误是silent的,有些则直接抛出。silent是指这个错误不会向上抛出,比如uncaughtException不能捕获到这个错误。另外,http上是捕获不到这个错误的,必须在socket上监听。
  • ECONNRESET是因为连接的一方突然关闭连接。为什么要关闭的原因有很多,可能是因为各种应用层协议错误,可能是达到硬件/软件支持的连接上限等等。

资料:

# ECONNABORTED

客户端在服务器接受客户端请求的连接之前发送TCP重置(RST)。

该错误被描述为“software caused connection abort”,即“软件引起的连接中止”。原因在于当服务和客户进程在完成用于 TCP 连接的“三次握手”后,客户 TCP 却发送了一个 RST (复位)分节,在服务进程看来,就在该连接已由 TCP 排队,等着服务进程调用 accept 的时候 RST 却到达了。POSIX 规定此时的 errno 值必须 ECONNABORTED。源自 Berkeley 的实现完全在内核中处理中止的连接,服务进程将永远不知道该中止的发生。服务器进程一般可以忽略该错误,直接再次调用accept。

考虑这种情况: TCP连接被客户端夭折,即在服务器调用accept之前,客户端主动发送RST终止连接,导致刚刚建立的连接从就绪队列中移出 ,如果套接口被设置成阻塞模式,服务器就会一直阻塞在accept调用上,直到其他某个客户建立一个新的连接为止。但是在此期间,服务器单纯地阻塞在accept调用上,就绪队列中的其他描述符都得不到处理。

解决办法是把监听套接口设置为非阻塞,当客户在服务器调用accept之前中止某个连接时,accept调用可以立即返回-1,这时源自Berkeley的实现会在内核中处理该事件,并不会将该事件通知给epool,而其他实现把errno设置为ECONNABORTED或者EPROTO错误,我们应该忽略这两个错误。

就是 socket 刚建立,客户端立即发送了 RST ?不明白哪里发送了 RST,不过客户端遇到这种情况可以延时后直接重发请求。

以 axios 为栗,添加拦截器:

const axios = require('axios');
// 设置全局的请求次数,请求的间隙
// axios.defaults.retry = 1; 原栗子是直接设置 defaults ,现在版本 `axios@0.19.2` 并不生效。
// 折中方法,设置在 headers 里面 [custom config are now filtered out in v0.19](https://github.com/axios/axios/issues/164#issuecomment-524756674)
axios.defaults.headers.common.retry = 2;
axios.defaults.headers.common.retryDelay = 1000;

axios.interceptors.response.use(undefined, function axiosRetryInterceptor (err) {
  const { config } = err;
  const { headers } = config;
  // 设置变量跟踪重试次数
  config.__retryCount = config.__retryCount || 0;

  // 如果不是 `ECONNABORTED`,或者 headers 不存在或未设置重试选项,或者已经达到最大重试总次数,抛出错误信息
  if (err.code !== 'ECONNABORTED' || !headers || !headers.retry || config.__retryCount >= headers.retry) {
    return Promise.reject(err);
  }

  // 增加请求重试次数
  config.__retryCount += 1;

  // 创建新的异步请求
  const backOff = new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, headers.retryDelay || 1);
  });

  // 返回一个新的请求
  return backOff.then(() => {
    return axios(config);
  });
});

// 使用
axios.get('/some/endpoint', { retry: 5, retryDelay: 1000 })
  .then(function(res) {
    console.log('success', res.data);
  })
  .catch(function(err) {
    console.log('failed', err);
  });
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

# EPROTO

协议错误。

# EPIPE

错误被描述为“broken pipe”,即“管道破裂”,这种情况一般发生在客户进程不理会(或未及时处理)Socket 错误,继续向服务 TCP 写入更多数据时,内核将向客户进程发送 SIGPIPE 信号,该信号默认会使进程终止(此时该前台进程未进行 core dump)。结合上边的 ECONNRESET 错误可知,向一个 FIN_WAIT2 状态的服务 TCP(已 ACK 响应 FIN 分节)写入数据不成问题,但是写一个已接收了 RST 的 Socket 则是一个错误。

# ECONNREFUSED

拒绝连接

1、一般发生在连接建立时。拔服务器端网线测试,客户端设置keep alive时,recv较快返回0, 先收到ECONNREFUSED (Connection refused)错误码,其后都是ETIMEOUT。 2、an error returned from connect(), so it can only occur in a client (if a client is defined as the party that initiates the connection

# ETIMEOUT

1、操作超时。一般设置了发送接收超时,遇到网络繁忙的情况,就会遇到这种错误。 2、服务器做了读数据做了超时限制,读时发生了超时。 3、错误被描述为“connect time out”,即“连接超时”,这种情况一般发生在服务器主机崩溃。此时客户 TCP 将在一定时间内(依具体实现)持续重发数据分节,试图从服务 TCP 获得一个 ACK 分节。当最终放弃尝试后(此时服务器未重新启动),内核将会向客户进程返回 ETIMEDOUT 错误。如果某个中间路由器判定该服务器主机已经不可达,则一般会响应“destination unreachable”-“目的地不可达”的ICMP消息,相应的客户进程返回的错误是 EHOSTUNREACH 或ENETUNREACH。当服务器重新启动后,由于 TCP 状态丢失,之前所有的连接信息也不存在了,此时对于客户端发来请求将回应 RST。如果客户进程对检测服务器主机是否崩溃很有必要,要求即使客户进程不主动发送数据也能检测出来,那么需要使用其它技术,如配置 SO_KEEPALIVE Socket 选项,或实现某些心跳函数。

# 参考

上次更新: 6/2/2020, 5:06:17 PM