04 | 挥手:Nginx日志报connection reset by peer是怎么回事?

5 篇文章 2 订阅
订阅专栏

今天,我们要通过实际的案例,来学习下 TCP 挥手的知识,在实战中加深对这些知识的理解。

我们在做一些应用排查的时候,时常会在日志里看到跟 TCP 有关的报错。比如在 Nginx 的日志里面,可能就有 connection reset by peer 这种报错。“连接被对端 reset(重置)”,这个字面上的意思是看明白了。但是,心里不免发毛:

这个 reset 会影响我们的业务吗,这次事务到底有没有成功呢?

这个 reset 发生在具体什么阶段,属于 TCP 的正常断连吗?

我们要怎么做才能避免这种 reset 呢?

要回答这类追问,Nginx 日志可能就不够用了。

事实上,网络分层的好处是在于每一层都专心做好自己的事情就行了。而坏处也不是没有,这种情况就是如此:应用层只知道操作系统告诉它,“喂,你的连接被 reset 了”。但是为什么会被 reset 呢?应用层无法知道,只有操作系统知道,但是操作系统只是把事情处理掉,往内部 reset 计数器里加 1,但也不记录这次 reset 的前后上下文。

所以,为了搞清楚 connection reset by peer 时具体发生了什么,我们需要突破应用层这口井,跳出来看到更大的网络世界。

在应用层和网络层之间搭建桥梁

首先,需要理解下 connection reset by peer 的含义。熟悉 TCP 的话,你应该会想到这大概是对端(peer)回复了 TCP RST(也就是这里的 reset),终止了一次 TCP 连接。其实,这也是我们做网络排查的第一个要点:把应用层的信息,“翻译”成传输层和网络层的信息。

或者说,我们需要完成一件有时候比较有挑战的事情:把应用层的信息,跟位于它下面的传输层和网络层的信息联系起来。

这里说的“应用层信息”,可能是以下这些:

应用层日志,包括成功日志、报错日志,等等;

应用层性能数据,比如 RPS(每秒请求数),transaction time(处理时间)等;

应用层载荷,比如 HTTP 请求和响应的 header、body 等。

而“传输层 / 网络层信息”,可能是以下种种:

传输层:TCP 序列号(Sequence Number)、确认号(Acknowledgement Number)、MSS(Maximum Segment Size)、接收窗口(Receive Window)、拥塞窗口(Congestion Window)、时延(Latency)、重复确认(DupAck)、选择性确认(Selective Ack)、重传(Retransmission)、丢包(Packet loss)等。

网络层:IP 的 TTL、MTU、跳数(hops)、路由表等。

可见,这两大类(应用 vs 网络)信息的视角和度量标准完全不同,所以几乎没办法直接挂钩。而这,也就造成了问题排查方面的两大鸿沟。

应用现象跟网络现象之间的鸿沟:你可能看得懂应用层的日志,但是不知道网络上具体发生了什么。

工具提示跟协议理解之间的鸿沟:你看得懂 Wireshark、tcpdump 这类工具的输出信息的含义,但就是无法真正地把它们跟你对协议的理解对应起来。

也就是说,你需要具备把两大鸿沟填平的能力,有了这个能力,你也就有了能把两大类信息(应用信息和网络信息)联通起来的“翻译”的能力。这正是网络排查的核心能力。

既然是案例实战,这些知识从案例里面学,是最高效的方法了。接下来,一起看两个案例吧。

案例 1:connection reset by peer?

前几年,有个客户也是反馈,他们的 Nginx 服务器上遇到了很多 connection reset by peer 的报错。他们担心这个问题对业务产生了影响,希望我们协助查清原因。客户的应用是一个普通的 Web 服务,架设在 Nginx 上,而他们的另外一组机器是作为客户端,去调用这个 Nginx 上面的 Web 服务。

架构简图如下: 

前面说过,单纯从应用层日志来看的话,几乎难以确定 connection reset by peer 的底层原因。所以,我们就展开了抓包工作。具体做法是:

我们需要选择一端做抓包,这次是客户端;

检查应用日志,发现没几分钟就出现了 connection reset by peer 的报错;

对照报错日志和抓包文件,寻找线索。

先看一下,这些报错日志长什么样子:

2015/12/01 15:49:48 [info] 20521#0: *55077498 recv() failed (104: Connection reset by peer) while sending to client, client: 10.255.252.31, server: manager.example.com, request: "POST /WebPageAlipay/weixin/notify_url.htm HTTP/1.1", upstream: "http:/10.4.36.207:8080/WebPageAlipay/weixin/notify_url.htm", host: "manager.example.com"
2015/12/01 15:49:54 [info] 20523#0: *55077722 recv() failed (104: Connection reset by peer) while sending to client, client: 10.255.252.31, server: manager.example.com, request: "POST /WebPageAlipay/app/notify_url.htm HTTP/1.1", upstream: "http:/10.4.36.207:8080/WebPageAlipay/app/notify_url.htm", host: "manager.example.com"
2015/12/01 15:49:54 [info] 20523#0: *55077710 recv() failed (104: Connection reset by peer) while sending to client, client: 10.255.252.31, server: manager.example.com, request: "POST /WebPageAlipay/app/notify_url.htm HTTP/1.1", upstream: "http:/10.4.36.207:8080/WebPageAlipay/app/notify_url.htm", host: "manager.example.com"
2015/12/01 15:49:58 [info] 20522#0: *55077946 recv() failed (104: Connection reset by peer) while sending to client, client: 10.255.252.31, server: manager.example.com, request: "POST /WebPageAlipay/app/notify_url.htm HTTP/1.1", upstream: "http:/10.4.36.207:8080/WebPageAlipay/app/notify_url.htm", host: "manager.example.com"
2015/12/01 15:49:58 [info] 20522#0: *55077965 recv() failed (104: Connection reset by peer) while sending to client, client: 10.255.252.31, server: manager.example.com, request: "POST /WebPageAlipay/app/notify_url.htm HTTP/1.1", upstream: "http:/10.4.36.207:8080/WebPageAlipay/app/notify_url.htm", host: "manager.example.com"

补充:因为日志涉及客户数据安全和隐私,已经做了脱敏处理。

看起来最“显眼”的,应该就是那句 connection reset by peer。另外,其实也可以关注一下报错日志里面的其他信息,这也可以帮助我们获取更全面的上下文。

recv() failed:这里的 recv() 是一个系统调用,也就是 Linux 网络编程接口。它的作用呢,看字面就很容易理解,就是用来接收数据的。我们可以直接 man recv,看到这个系统调用的详细信息,也包括它的各种异常状态码。

104:这个数字也是跟系统调用有关的,它就是 recv() 调用出现异常时的一个状态码,这是操作系统给出的。在 Linux 系统里,104 对应的是 ECONNRESET,也正是一个 TCP 连接被 RST 报文异常关闭的情况。

upstream:在 Nginx 等反向代理软件的术语里,upstream 是指后端的服务器。也就是说,客户端把请求发到 Nginx,Nginx 会把请求转发到 upstream,等后者回复 HTTP 响应后,Nginx 把这个响应回复给客户端。注意,这里的“客户端 <->Nginx”和“Nginx<->upstream”是两条独立的 TCP 连接,也就是下图这样:

补充:你可能觉得奇怪,明明数据是从外面进入到里面的,为什么里面的反而叫 upstream?其实是这样的:在网络运维的视角上,我们更关注网络报文的流向,因为 HTTP 报文是从外部进来的,那么我们认为其上游(upstream)是客户端;但是在应用的视角上,更关注的是数据的流向,一般来说 HTTP 数据是从内部往外发送的,那么在这种视角下,数据的上游(upstream)就是后端服务器了。同样,在 HTTP 协议规范 RFC 中,upstream 也是指服务端。

 Nginx、Envoy 都属于应用网关,所以在它们的术语里,upstream 指的是后端环节。这里没有对错之分,只要知道并且遵照这个约定就好了。

到这里,既然已经解读清楚报错日志了,接下来就进入到抓包文件的分析里吧。

先写过滤器

虽然在上节课,也使用 Wireshark 对握手相关的案例做了不少分析,但对它的使用还是相对简单的。那今天这节课开始,就要深度使用 Wireshark 了。比如在接下来的内容里,会用到很多 Wireshark 的过滤器(也可以叫过滤表达式或者过滤条件)。因为步骤稍多,所以会多花一些时间来讲解。

一般来说,在抓到的原始抓包文件里,真正关心的报文只占整体的一小部分。那么,如何从中定位跟问题相关的报文,就是个学问了。

就当前这个案例而言,既然有应用层日志,也有相关的 IP 地址等明确的信息,这些就为我们做报文过滤创造了条件。我们要写一个过滤器,这个过滤器以 IP 为条件,先从原始文件中过滤出跟这个 IP 相关的报文

在 Wireshark 中,以 IP 为条件的常用过滤器语法,主要有以下几种: 

ip.addr eq my_ip:过滤出源IP或者目的IP为my_ip的报文
ip.src eq my_ip:过滤出源IP为my_ip的报文
ip.dst eq my_ip:过滤出目的IP为my_ip的报文

不过,这还只是第一个过滤条件,仅通过它过滤的话,出来的报文数量仍然比我们真正关心的报文要多很多。我们还需要第二个过滤条件,也就是要找到 TCP RST 报文。这就要用到另外一类过滤器了,也就是 tcp.flags,而这里的 flags,就是 SYN、ACK、FIN、PSH、RST 等 TCP 标志位。

对于 RST 报文,过滤条件就是:

tcp.flags.reset eq 1

可以选中任意一个报文,注意其 TCP 的 Flags 部分:

打开抓包文件,输入这个过滤条件:

ip.addr eq 10.255.252.31 and tcp.flags.reset eq 1

会发现有很多 RST 报文:

在 Wirershark 窗口的右下角,就有符合过滤条件的报文个数,这里有 9122 个,占所有报文的 4%,确实是非常多。由此推测,日志里的很多报错估计应该就是其中一些 RST 引起的。我们选一个先看一下。

在第 2 讲的时候,就学习了如何在 Wireshark 中,基于一个报文,找到它所在的整个 TCP 流的所有其他报文。这里呢,我们选择 172 号报文,右单击,选中 Follow -> TCP Stream,就找到了它所属的整个 TCP 流的报文:

咦,这个 RST 处在握手阶段?由于这个 RST 是握手阶段里的第三个报文,但它又不是期望的 ACK,而是 RST+ACK,所以握手失败了。

不过,你也许会问:这种握手阶段的 RST,会不会也跟 Nginx 日志里的 connection reset by peer 有关系呢

要回答这个问题,就要先了解应用程序是怎么跟内核的 TCP 协议栈交互的。一般来说,客户端发起连接,依次调用的是这几个系统调用:

socket()

connect()

而服务端监听端口并提供服务,那么要依次调用的就是以下几个系统调用:

socket()

bind()

listen()

accept()

服务端的用户空间程序要使用 TCP 连接来接收请求,首先要获得上面最后一个接口,也就是 accept() 调用的返回。而 accept() 调用能成功返回的前提呢,是正常完成三次握手。

你看,这次客户端在握手中的第三个包不是 ACK,而是 RST(或者 RST+ACK),握手不是失败了吗?那么自然地,这次失败的握手,也不会转化为一次有效的连接了,所以 Nginx 都不知道还存在过这么一次失败的握手。

当然,在客户端日志里,是可以记录到这次握手失败的。这是因为,客户端是 TCP 连接的发起方,它调用 connect(),而 connect() 失败的话,其 ECONNRESET 返回码,还是可以通知给应用程序的。

再来看一下这张系统调用跟 TCP 状态关系的示意图:

所以,上面这个虽然也是 RST,但并不是我们要找的那种“在连接建立后发生的 RST”。

继续打磨过滤器

看来,我们还需要进一步打磨一下过滤条件,把握手阶段的 RST 给排除。要做到这一点,首先要搞清楚:什么是握手阶段的 RST 的特征呢?

我们关注一下上面的截图,其实会发现:这个 RST 的序列号是 1,确认号也是 1。因此,我们可以在原先的过滤条件后面,再加上这个条件:

tcp.seq eq 1 and tcp.ack eq 1

于是过滤条件变成:

ip.addr eq 10.255.252.31 and tcp.flags.reset eq 1 and !(tcp.seq eq 1 and tcp.ack eq 1)

注意,这里的(tcp.seq eq 1 and tcp.ack eq 1)前面是一个感叹号(用 not 也一样),起到“取反”的作用,也就是排除这类报文。

让我们看下,现在过滤出来的报文是怎样的:

我们又发现了序列号为 2 的很多 RST 报文,这些又是什么呢?我们选包号 115,然后 Follow -> TCP Stream 看一下:

原来这是挥手阶段的 RST,并且没有抓取到数据交互阶段,那跟日志里的报错也没关系,也可以排除。这样的话,我们可以把前面的过滤条件中的 and 改成 or,就可以同时排除握手阶段和挥手阶段的 RST 报文了。我们输入过滤器:

ip.addr eq 10.255.252.31 and tcp.flags.reset eq 1 and !(tcp.seq eq 1 or tcp.ack eq 1)

得到下面这些报文:

虽然排除了握手阶段的 RST 报文,但是剩下的也还是太多,我们要找的“造成 Nginx 日志报错”的 RST 在哪里呢?

为了找到它们,需要再增加一些明确的搜索条件。还记得提到过的两大鸿沟吗?一个是应用现象跟网络现象之间的鸿沟,一个是工具提示跟协议理解之间的鸿沟。

现在为了跨越第一个鸿沟,我们需要把搜索条件落实具体,针对当前案例来说,就是基于以下条件寻找数据包:

既然这些网络报文跟应用层的事务有直接关系,那么报文中应该就包含了请求相关的数据,比如字符串、数值等。当然,这个前提是数据本身没有做过特定的编码,否则的话,报文中的二进制数据,跟应用层解码后看到的数据就会完全不同。

补充:编码的最典型的场景就是 TLS。如果我们不做解密,那么直接 tcpdump 或者 Wireshark 抓取到的报文就是加密过的,跟应用层(比如 HTTP)的数据完全不同,这也给排查工作带来了不小的困难。关于如何对 TLS 抓包数据进行解密,在“实战二”的 TLS 排查的课程里会提到。

这些报文的发送时间,应该跟日志的时间是吻合的。

对于条件 1,我们可以利用 Nginx 日志中的 URL 等信息;对于条件 2,我们就要利用日志的时间。其实,在开头部分展示的 Nginx 日志中,就有明确的时间(2015/12/01 15:49:48),虽然只是精确到秒,但很多时候已经足以帮助我们进一步缩小范围了。

那么,在 Wireshark 中搜索“特定时间段内的报文”,又要如何做到呢?这就是我要介绍的又一个搜索技巧:使用 frame.time 过滤器。比如下面这样:

frame.time >="dec 01, 2015 15:49:48" and frame.time <="dec 01, 2015 15:49:49"

这就可以帮助我们定位到跟上面 Nginx 日志中,第一条日志的时间匹配的报文了。为了方便理解,直接把这条日志复制到这里参考:

2015/12/01 15:49:48 [info] 20521#0: *55077498 recv() failed (104: Connection reset by peer) while sending to client, client: 10.255.252.31, server: manager.example.com, request: "POST /WebPageAlipay/weixin/notify_url.htm HTTP/1.1", upstream: "http:/10.4.36.207:8080/WebPageAlipay/weixin/notify_url.htm", host: "manager.example.com"

再结合前面的搜索条件,就得到了下面这个更加精确的过滤条件:

frame.time >="dec 01, 2015 15:49:48" and frame.time <="dec 01, 2015 15:49:49" and ip.addr eq 10.255.252.31 and tcp.flags.reset eq 1 and !(tcp.seq eq 1 or tcp.ack eq 1)

好长的一个过滤器!不过没关系,人读着觉得长,Wireshark 就未必这么觉得了,也许还觉得很顺眼呢。就好比机器语言,人读着感觉是天书,机器却觉得好亲近,“这可是我的母语啊!”

好,这次我们终于非常成功地锁定到只有 3 个 RST 报文了:

接下来要做的事情就会简单很多:只要把这三个 RST 所在的 TCP 流里的应用层数据(也就是 HTTP 请求和返回)跟 Nginx 日志中的请求和返回进行对比,就能找到是哪个 RST 引起了 Nginx 报错了。

对问题报文的深入分析

先来看看,11393 号报文所属的流是什么情况?

然后来看一下 11448 号报文所属的 TCP 流。

原来,11448 跟 11450 是在同一个流里面的。现在清楚了,3 个 RST,分别属于 2 个 HTTP 事务。

再仔细对比一下两个图中的红框部分,是不是不一样?它们分别是对应了一个 URL 里带“weixin”字符串的请求,和一个 URL 里带“app”字符串的请求。那么,在这个时间点(15:49:48)对应的日志是关于哪一个 URL 的呢?

2015/12/01 15:49:48 [info] 20521#0: *55077498 recv() failed (104: Connection reset by peer) while sending to client, client: 10.255.252.31, server: manager.example.com, request: "POST /WebPageAlipay/weixin/notify_url.htm HTTP/1.1", upstream: "http:/10.4.36.207:8080/WebPageAlipay/weixin/notify_url.htm", host: "manager.example.com"

你只要往右拖动一下鼠标,就能看到 POST URL 里的“weixin”字符串了。而包号 11448 和 11450 这两个 RST 所在的 TCP 流的请求,也是带“weixin”字符串的,所以它们就是匹配上面这条日志的 RST!

如果还没有完全理解,这里小结一下,为什么我们可以确定这个 TCP 流就是对应这条日志的,主要三点原因:

时间吻合;

RST 行为吻合;

URL 路径吻合。

通过解读上面的 TCP 流,终于跨过了这道“应用现象跟网络报文”之间的鸿沟:

再进一步,画一下这个 HTTP 事务的整体过程,进一步搞清楚为什么这个 RST,会引起 Nginx 记录 connection reset by peer 的报错:

也就是说,握手和 HTTP POST 请求和响应都正常,但是客户端在对 HTTP 200 这个响应做了 ACK 后,随即发送了 RST+ACK,而正是这个行为破坏了正常的 TCP 四次挥手。也正是这个 RST,导致服务端 Nginx 的 recv() 调用收到了 ECONNRESET 报错,从而进入了 Nginx 日志,成为一条 connection reset by peer。

这个对应用产生了什么影响呢?对于服务端来说,表面上至少是记录了一次报错日志。但是有意思的是,这个 POST 还是成功了,已经被正常处理完了,要不然 Nginx 也不会回复 HTTP 200。

对于客户端呢?还不好说,因为我们并没有客户端的日志,也不排除客户端认为这次是失败,可能会有重试等等。

把这个结论告诉给了客户,他们悬着的心稍稍放下了:至少 POST 的数据都被服务端处理了。当然,他们还需要查找客户端代码的问题,把这个不正常的 RST 行为给修复掉,但是至少已经不用担心数据是否完整、事务是否正常了。

现在,回到我们开头的三连问:

这个 reset 会影响我们的业务吗,这次事务到底有没有成功呢?

这个 reset 发生在具体什么阶段,属于 TCP 的正常断连吗?

我们要怎么做才能避免这种 reset 呢?

我们现在就可以回答了:

这个 reset 是否影响业务,还要继续查客户端应用,但服务端事务是成功被处理了。

这个 reset 发生在事务处理完成后,但不属于 TCP 正常断连,还需要继续查客户端代码问题。

要避免这种 reset,需要客户端代码进行修复。

补充:客户端用 RST 来断开连接并不妥当,需要从代码上找原因。比如客户端在 Receive Buffer 里还有数据未被读取的情况下,就调用了 close()。对应用的影响究竟如何,就要看具体的应用逻辑了。

网络中的环节很多,包括客户端、服务端、中间路由交换设备、防火墙、LB 或者反向代理等等。如何在这么多环节中定位到具体的问题节点,一直以来是很多工程师的痛点。比如,网络不稳定,或者防火墙来几个 RST,也都有可能导致类似的 connection reset by peer 的问题。

通过抓包分析,我们抽丝剥茧,定位到具体的问题环节不在 Nginx,也不在网络本身,而是在客户端代码这里。也正因为有了这样的分析,写代码的同学就可以专心做代码修复,而不用一直怀疑问题在其他环节了。

好,讨论完 RST,你可能会问了:TCP 挥手一般是用 FIN 的,这个知识点还没讨论呢。别急,这第二个案例就是关于 FIN 的。

案例 2:一个 FIN 就完成了 TCP 挥手?

你应该知道,TCP 挥手是“四次”,这几乎也是老生常谈的知识点了。来看一下常规的四次挥手的过程: 

在图上没有用“客户端”和“服务端”这种名称,而是叫“发起端”和“接收端”。这是因为,TCP 的挥手是任意一端都可以主动发起的。也就是说,挥手的发起权并不固定给客户端或者服务端。这跟 TCP 握手不同:握手是客户端发起的。或者换个说法:发起握手的就是客户端。在握手阶段,角色分工十分明确。

另外,FIN 和 ACK 都各有两次,这也是十分明确的。

可是有一次,一个客户向我报告这么一个奇怪的现象:他们偶然发现,他们的应用在 TCP 关闭阶段,只有一个 FIN,而不是两个 FIN。这好像不符合常理啊。我也觉得有意思,就一起看了他们这个抓包文件:

确实奇怪,真的只有一个 FIN。这两端的操作系统竟然能容忍这种事情发生?瞬间感觉“塌房”了:难道一向严谨的 TCP,它的分手也可以这么随意吗?“当初是你要分开,分开就分开,一个 FIN,就足够,眼泪落下来”?

很快,就意识到还有一种可能性。在上节课介绍 TCP 握手的时候提到过,TCP 里一个报文可以搭另一个报文的顺风车(Piggybacking),以提高 TCP 传输的运载效率。所以,TCP 挥手倒不是一定要四个报文,Piggybacking 后,就可能是 3 个报文了。看起来就类似三次挥手:

那这次的案例,我们在 Wireshark 中看到了后两个报文,即接收端回复的 FIN+ACK 和发起端的最后一个 ACK。那么,第一个 FIN 在哪里呢?从 Wireshark 的截图中,确实看不出来。

当然,从 Wireshark 的图里,我们甚至可以认为,这次连接是服务端发起的,它发送了 FIN+ACK,而客户端只回复了一个 ACK,这条连接就结束了。这样的解读更加诡异,却也符合 Wireshark 的展示。

但是,Wireshark 的主界面还有个特点,就是当它的 Information 列展示的是应用层信息时,这个报文的 TCP 层面的控制信息就不显示了。所以,上面的 POST 请求报文,其 Information 列就是 POST 方法加上具体的 URL。它的 TCP 信息,包括序列号、确认号、标志位等,都需要到详情里面去找。

先选中这个 POST 报文,然后到界面中间的 TCP 详情部分去看看:

原来,第一个 FIN 控制报文,并没有像常规的那样单独出现,而是合并(Piggybacking)在 POST 报文里!所以,整个挥手过程,其实依然十分标准,完全遵循了协议规范。仅仅是因为 Wireshark 的显示问题,带来了一场小小的误会。虽然还有一个“为什么没有 HTTP 响应报文”的问题,但是 TCP 挥手方面的问题,已经得到了合理的解释了。

这也提醒我们,理解 TCP 知识点的时候需要真正理解,而不是生搬硬套。这一方面需要对协议的仔细研读,另一方面也离不开实际案例的积累和融会贯通,从量变引起质变。

我们自己也要有个态度:大部分时候,当看到 TCP 有什么好像“不合规的行为”,我们最好先反思自己是不是对 TCP 的掌握还不够深入,而不是先去怀疑 TCP,毕竟它也久经考验,它正确的概率比我们高得多,那我们做“自我检讨”,其实是笔划算的买卖,基本“稳赢”。

小结

通过回顾案例,把 TCP 挥手的相关技术细节给梳理了一遍。在案例 1 里面,用抓包分析的方法,打通了“应用症状跟网络现象”以及“工具提示与协议理解”这两大鸿沟,可以再重点关注一下这里面用到的推进技巧:

首先根据应用层的表象信息,抽取出 IP 和 RST 报文这两个过滤条件,启动了报文过滤的工作。

分析第一遍的过滤结果,得到进一步推进的过滤条件(在这个案例里是排除握手阶段的 RST)。

结合日志时间范围,继续缩小范围到 3 个 RST 报文,这个范围足够小,我们可以展开分析,最终找到报错相关的 TCP 流。这种“迭代式”的过滤可以反复好几轮,直到定位到问题报文。

在这个 TCP 流里,结合对 TCP 协议和 HTTP 的理解,定位到问题所在。

此外,通过这个案例,也介绍了一些 Wireshark 的使用技巧,特别是各种过滤器:

通过 ip.addr eq my_ip ip.src eq my_ip,再或者 ip.dst eq my_ip,可以找到跟 my_ip 相关的报文。

通过 tcp.flags.reset eq 1 可以找到 RST 报文,其他 TCP 标志位,依此类推。

通过 tcp.ack eq my_num 可以找到确认号为 my_num 的报文,对序列号的搜索,同理可用 tcp.seq eq my_num

一个过滤表达式之前加上“!”或者 not 起到取反的作用,也就是排除掉这些报文。

通过 frame.time >="dec 01, 2015 15:49:48"这种形式的过滤器,可以根据时间来过滤报文。多个过滤条件之间可以用 and 或者 or 来形成复合过滤器。

通过把应用日志中的信息(比如 URL 路径等)和 Wireshark 里的 TCP 载荷的信息进行对比,可以帮助我们定位到跟这个日志相关的网络报文。

而在案例 2 里面,对“四次挥手”又有了新的认识。通过这个真实案例,希望能够了解到:

实际上 TCP 挥手可能不是表面上的四次报文,因为并包也就是 Piggybacking 的存在,它可能看起来是三次。

在某些特殊情况下,在 Wireshark 里看不到第一个 FIN。这个时候你不要真的把后面那个被 Wireshark 直接展示的 FIN 当作是第一个 FIN。你需要选中挥手阶段附近的报文,在 TCP 详情里面查看是否有报文携带了 FIN 标志位。这确实是个非常容易掉坑的地方,所以要提醒一下。

扩展知识:挥手的常见误区

案例也讲了两个了,相信你也对非正常挥手(RST)和正常挥手(FIN)有了更加深入的认识了。接下来,再介绍几个常见误区,希望起到“有则改之,无则加勉”的效果。

连接关闭由客户端发起?

其实不对,连接关闭可以是客户端,也可以是服务端发起。造成这个误解的原因,其实也跟这张图有关系:

你有没有发现,图中第一个 FIN 是从客户端发起的。但服务端就不会主动发起关闭 / 挥手吗?当然会,只是图中没有标明这种情况。挥手跟握手不同,握手一定是客户端发起的(所以才叫客户端),但挥手是双方都可以。

其实上节课也讲到过这张图,它出自 Richard Stevens 的《UNIX 网络编程:套接字联网 API》。那是不是 Stevens 自己就搞错了呢?我觉得,这个可能性比我中彩票的概率还要低好几个数量级。

Stevens 当然清楚双方都可以发起挥手,他只是为了突出重点,就没有把多种情况都画到同一张图里,因为这张图的重点是把 TCP 连接状态的变迁展示清楚,而不是要突出“谁可以发起挥手”这个细节。

挥手不能同时发起?

有的同学觉得挥手是客户端发起的,或者是服务端发起,反正就不能是双方同时发起。事实上,如果双方同时都主动发起了关闭,TCP 会怎么处理这种情况呢?我们看下图:

双方同时发起关闭后,也同时进入了 FIN_WAIT_1 状态;

然后也因为收到了对方的 FIN,也都进入了 CLOSING 状态;

当双方都收到对方的 ACK 后,最终都进入了 TIME_WAIT 状态。

这也意味着,两端都需要等待 2MSL 的时间,才能复用这个五元组 TCP 连接。这种情况是比较少见的,但是协议设计需要考虑各种边界条件下的实现,比普通的应用程序所要考虑的事情要多不少。所以也许有些 RFC 看似简单,但背后其实都十分不简单。

TCP 挥手时双方同时停止发送数据?

一方发送 FIN,表示这个连接开始关闭了,双方就都不会发送新的数据了?这也是很常见的误区。

实际上,一方发送 FIN 只是表示这一方不再发送新的数据,但对方仍可以发送数据。

还是在 Richard Stevens 的《TCP/IP 详解(第一卷)》中,明确提到 TCP 可以有“半关闭”的做法,也就是:

一端(A)发送 FIN,表示“我要关闭,不再发送新的数据了,但我可以接收新的数据”。

另一端(B)可以回复 ACK,表示“我知道你那头不会再发送了,我这头未必哦”。

B 可以继续发送新的数据给 A,A 也会回复 ACK 表示确认收到新数据。

在发送完这些新数据后,B 才启动了自己的关闭过程,也就是发送 FIN 给 A,表示“我的事情终于忙好了,我也要关闭,不会再发送新数据了”。

这时候才是真正的两端都关闭了连接。

还是搬运了 Stevens 的图过来参考,也再次致敬 Stevens 大师!

Nginx: recv() failed (104: connection reset by peer) 问题排查
诗渊的博客
07-07 2万+
recv() failed (104: connection reset by peer) 问题排查 最近项目中通过Nginx反向代理nodejs(采用nestjs框架)服务,压测过程中概率性出现502 Bad Gateway,出现概率较低,大概在0.005%左右,查看日志具体错信息为recv() failed (104: Connection reset by peer) while reading response header from upstream,通过查找资料,了解到该错的直接原因就是:
nginx 502 bad gateway:recv() failed (104: Connection reset by peer) 问题处理
klayer_cong的博客
01-21 2589
** nginx 502 bad gateway:recv() failed (104: Connection reset by peer) 问题处理 ** 服务器环境 nginx+tomcat8+docker+mysql。 *注:nginx没有启用php-fpm模块,也没有fastcgi模块。网上一大把说php模块问题,copy的真是服了,一点卵用都没有。 问题现象 web访问后端接口的时候不定时性出现502 bad gateway错误。查看nginx错误日志,出现“recv() failed (104:
Nginx常见错误
aijian3049的博客
08-31 587
错误信息 错误说明 "upstreamprematurely(过早的)closedconnection" 请求uri的时候出现的异常,是由于upstream还未返回应答给用户时用户断掉连接造成的,对系统没有影响,可以忽略 "recv()failed(104:Connectionresetbypeer...
NGINX 反向代理错误:104: Connection reset by peer
yf.z的专栏
10-25 1万+
转: https://my.oschina.net/u/1024107/blog/1838968 此问题在于 nginx的buffer太小,timeout太小。 http模块添加以下参数配置: client_body_buffer_size 20m; fastcgi_buffer_size 128k; fastcgi_buffers 4 128k; fastcgi_busy_buffe...
Connection reset by peer错解决
最新发布
m0_65307735的博客
08-16 920
Connection reset by peer错解决
Nginx: Connection reset by peer 错误定位
热门推荐
zzhongcy的专栏
04-08 10万+
最近Nginx反向代理遇到了“104: Connection reset by peer”错误,google了一下,这里记录一下。 1 错误原因:检查链接是否已经close。 upstream发送了RST,将连接重置。 errno = 104错误表明你在对一个对端socket已经关闭的的连接调用write或send方法,在这种情况下,调用write或se...
Nginx: 104: Connection reset by peer 错误
JineD的博客
05-10 2万+
1 错误原因:检查链接是否已经close。 upstream发送了RST,将连接重置。 errno = 104错误表明你在对一个对端socket已经关闭的的连接调用write或send方法,在这种情况下,调用write或send方法后,对端socket便会向本端socket发送一个RESET信号,在此之后如果继续执行write或send操作,就会得到errno为104,错误描述为connection reset by peer。 如果对方socket已经执行了c...
nginx容器 错:connection reset by peer错:connection refuse
weixin_44009659的博客
11-08 2289
connection reset by peer 原因是没有启动nginx,需要进入容器启动nginx,也就是进入之后,输入nginx就好。有connection refuse问题,应该是ping不通的,可能的原因是因为docker的网关 docker0 没有ipv4地址,添加就好,下面有说明。
nginx HTTPS无法访问,提示ERR CONNECTION RESET
u010227042的博客
07-25 3457
服务器使用nginx,SSL证书暂时使用的阿里云一年免费版。 使用HTTPS无法访问,提示ERR_CONNECTION_RESET 连接已重置 情况如下: 一、通过HTTP+域名方式访问正常 即访问http+yy.youyou.work能访问,说明域名已解析成功 二、通过HTTPS+IP方式访问正常 即访问https+IP能访问,说明防火墙对443端口已经放开三、通过HTTPS+域名方式访问异常 即访问https+yy.youyou.work访问,提示ERR_CONNECTION_RESET wget ..
docker 搭建nginx访问宿主机端口错curl: (56) Recv failure: Connection reset by peer
qq_40299519的博客
06-18 803
实验环境:openstack云主机CentOS72 1511,docker-ce 20.10.3在正常安装docker-ce环境后尝试运行nginx测试docker环境是否正常,结果发现docker容器映射到宿主机的端口无法正常访问,错:但容器内通过端口访问服务,返回结果正常。如图解决方法很简单,重建docker0网桥,但出现错误原因不清楚重建docker0网桥步骤。
nginx php fastcgi Connection reset by peer的原因及解决办法
redsen的专栏
05-06 8132
今天服务器反应慢,查看错误日志,提示 Connection reset by peer。找了下原因,感觉这个分析比较实用。留存。 Connection reset by peer 这个错误是在nginx的错误日志中发现的,为了更全面的掌握nginx运行的异常,强烈建议在nginx的全局配置中增加 error_log   logs/error.log notice; 这样,就
nginx 104: Connection reset by peer
04-11
关于你提到的"104: Connection reset by peer"错误,这是一个常见的网络错误代码,表示与服务器建立的连接被对方重置。这种情况通常发生在客户端和服务器之间的网络连接出现问题时,可能是由于网络中断、服务器负载...
Nginx-Mysql 502 recv() failed 104: Connection reset by peer
qq_40139152的博客
06-01 686
概述 文章出处:文章源地址 昨日项目基本完工,然后走测试服测试项目时,接口返回,连接异常关闭,排错发现,我的Mysql服务502了, 排错 Nginx & php-fpm 然后又继续排查Nginx日志, 发现Nginx 和 php-fpm通讯出现了问题 recv() failed (104: Connection reset by peer) while reading response header from upstream 出现这种错误,通常都是php-fpm没能正常响应Nginx
解决后台服务connector.ClientAbortException: java.io.IOException: Connection reset by peer
xiashenbao的博客
12-04 1938
一、运行环境 spirng cloud作为后台 二、发现问题 在测试环境运行一段时间后,某一只接口在进行服务接口调用时网关层后台必现这个错误 org.apache.catalina.connector.ClientAbortException: java.io.IOException: Connection reset by peer 但是,同一只接口数据量少的结果返回没有问题。 三、原因分析 同一只接口,当返回数据少的时候可以,返回数据多时错,因此定位为后端返回数据过大,而nginx 的 配置文件中,默
关于nginx连接被重置引发的网络协议工作解析
Hi,我是sharkchili,CSDN Java领域博客专家,开源项目JavaGuide维护人员,我想写一些有意思的东西,希望对你有帮助。
03-25 3256
这时笔者再次使用浏览器访问ip:3000后,发现问题所在,如下图所示,在服务器抓包记录中,我们看到浏览器请求的包,却没有看到服务器对客户端发的ack确认包,说明我们的请求包可能被服务器丢掉了。随查看nginx的access.log,发现并没有本机浏览器访问服务器的记录。一再确认上述问题无误后,使用curl命令在服务器尝试访问对应网站地址。笔者在配置nginx对某个网站的代理时,在浏览器访问对应地址连接被重置的错误。由于笔者访问的是服务器3000端口,于是就开一个tcpdump抓3000端口的包。
[乐意黎] Nginx 出现 "The connection was reset" 以及 "file not found" 等问题的解决方法
打杂人
04-25 3605
一、抛 The connection was reset 错误 get an error that says "The connection was reset" immediately when I upload a file over a certain size, I think it's over around 4MB. My web server is running on ngi...
一个接口获取所有省市区java.io.IOException: 连接被对方重设 Connection reset
fengbin2005的专栏
03-07 614
连接被对方重设 发送在浏览器和web接口之间. 也就是说请求中间nginx中断了请求导致错.因为请求体比较大,nginx写到缓存文件中,但是读取的时候是nobody,导致没权限.通过直接走后端8001服务,数据正常,走80端口就不正常.所以问题在nginx.表现:输出的json数据输出一部分,截断掉了.所以前端错.nginx 13: Permission denied。
Connection reset by peer (秒懂)
架构师尼恩
11-18 3万+
疯狂创客圈 经典图书 : 《Netty Zookeeper Redis 高并发实战》 面试必备 + 面试必备 + 面试必备 【博客园总入口 】 疯狂创客圈 经典图书 : 《SpringCloud、Nginx高并发核心编程》 大厂必备 + 大厂必备 + 大厂必备 【博客园总入口 】 入大厂+涨工资必备: 高并发【 亿级流量IM实战】 实战系列 【 SpringCloud Nginx秒杀】 实战系列 【博客园总入口 】 知识铺垫 疯狂创客圈 《SpringClo..
客户端连接超时,connection reset by peer问题解决方案
qq_44062110的博客
03-12 2961
原生ES无keepalive参数,依赖的是系统自带的tcp_keepalive,检查宿主机和pod内核参数,net.ipv4.tcp_keepalive_time=7200,即系统默认tcp探活时间 为7200,大于ipvs的900S,链接被IPVS重置回收。修改宿主机node系统参数net.ipv4.tcp_keepalive_time < 900S 如果ES client是容器部署的,也可以考虑容器使用initContainer单独配置容器的net.ipv4.tcp_keepalive_time。
写文章

热门文章

  • 05|音频降噪如何对症下药? 12271
  • 14|音效三剑客:变调、均衡器、混响 9161
  • 15|AI变声:音频AI技术的集大成者 7465
  • 22|再探HuggingFace:一键部署自己的大模型 7159
  • 11|网络差怎么办?音频网络传输与抗弱网策略 6235

分类专栏

  • Android 开发 19篇
  • C++实战笔记 26篇
  • 大规模数据处理 42篇
  • 技术管理2 37篇
  • 元宇宙 13篇
  • 前端 54篇
  • python自动化 36篇
  • Java 核心技术 41篇
  • 技术管理 37篇
  • Kafka 核心技术 45篇
  • 编译原理之美 41篇
  • 数学基础 56篇
  • 编程入门 35篇
  • 性能工程实战 33篇
  • 面试现场 39篇
  • 程序员工作法 55篇
  • 软件设计之美 36篇
  • 性能测试 33篇
  • 全栈工程师修炼 45篇
  • 搭建直播视频平台
  • AI大模型 30篇
  • 分布式协议与算法 28篇
  • 即时消息技术 23篇
  • Redis核心技术 49篇
  • 秒杀系统设计 9篇
  • 云计算 18篇
  • 程序员进阶攻略 65篇
  • 消息队列 39篇
  • 计算机组成原理 56篇
  • 代码精进之路 46篇
  • 动态规划面试 20篇
  • 系统性能调优 41篇
  • 架构实战案例 21篇
  • Go语言 50篇
  • eBPF 核心技术
  • 架构2 80篇
  • C++数据结构与算法
  • 微服务 39篇
  • 推荐系统 36篇
  • OAuth 2.0 15篇
  • 后端存储 27篇
  • python核心技术 32篇
  • 深入剖析 Kubernetes 52篇
  • 分布式技术原理与算法 35篇
  • RPC 实战与核心原理 27篇
  • Linux內核技术 21篇
  • 后端技术面试 44篇
  • 大数据 41篇
  • 游戏开发 37篇
  • HTTP 40篇
  • 机器学习 43篇
  • 架构 51篇
  • 网络编程 36篇
  • 视频技术 6篇
  • 密码学 20篇
  • Linux性能优化 58篇
  • 操作系统实战 5篇
  • c++ 34篇
  • 敏捷 10篇
  • 高并发系统设计 38篇
  • 音视频入门
  • Linux操作系统 66篇
  • C语言 31篇
  • 网络排查案例 5篇
  • 音频技术 16篇
  • 网络协议 46篇

最新评论

  • 10 分析篇 | 内存泄漏时,我们该如何一步步找到根因?

    hlzwty: 太猛了

  • 07 | Raft算法(一):如何选举领导者?

    wfh2015: 有些地方写的有偏差。 1. Follower等待Leader超时时间,是一个在固定区间的随机数,论文写的参考值是150-300ms 2. 很多时候不用Paxos主要原因有三个:a) Paxos算法是非常难以理解的,论文作者也提到,即使是斯坦福博士专门研究这东西,也花了1年左右的时间 b) Paxos算法有一些细节部分并没有公开 c) Raft是一个完整的算法,在Raft作者博士论文提到过的,除了基本部分意外,还增加了成员变更(可以理解为变更副本数提供了理论基础)/客户端交互/日志压缩,这意味着并不是空中楼阁

  • 06 | 定位防火墙(二):网络层的精确打击

    allen-smith: 请问虚拟机1和2的IP地址是多少,谢谢.

  • 05|码流结构:原来你是这样的H264

    YangShine01: 写的太好了!!!

  • 11 | I/O优化(下):如何监控线上I/O操作?

    CSDN-Ada助手: Python入门 技能树或许可以帮到你:https://edu.csdn.net/skill/python?utm_source=AI_act_python

最新文章

  • 16 | 网络优化(中):复杂多变的移动网络该如何优化?
  • 15 | 网络优化(上):移动开发工程师必备的网络优化知识
  • 14 | 存储优化(下):数据库SQLite的使用和优化
2024
08月 12篇
07月 49篇
06月 81篇
05月 158篇
04月 193篇
03月 255篇
02月 286篇
01月 218篇
2023年931篇
2022年46篇

目录

目录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值

玻璃钢生产厂家四川室内商场美陈研发公司烟台承接玻璃钢座椅雕塑玻璃钢仿真鸭梨雕塑丹东玻璃钢雕塑制作厂家玻璃钢浮雕不锈钢园林雕塑小品丽水玻璃钢人物雕塑价格铜仁玻璃钢花盆昆明玻璃钢雕塑厂电话河南玻璃钢人物雕塑厂家订制都匀玻璃钢雕塑订做浙江商场美陈哪里有九江多彩玻璃钢雕塑广东美陈玻璃钢卡通雕塑费用圣诞玻璃钢雕塑有哪些莱阳玻璃钢马雕塑名图玻璃钢雕塑款式多成都商场美陈研发公司商场美陈玩偶主题租赁玻璃钢螃蟹雕塑价格山西步行街玻璃钢雕塑优势玻璃钢胡萝卜雕塑1广州玻璃钢卡通雕塑厂家玻璃钢海绵宝宝雕塑上海主题商场美陈厂家直销玻璃钢雕塑供应价格怀化玻璃钢雕塑批发玻璃钢雕塑浮雕生产厂天津玻璃钢雕塑作品云南创意玻璃钢雕塑优势特色玻璃钢雕塑价位香港通过《维护国家安全条例》两大学生合买彩票中奖一人不认账让美丽中国“从细节出发”19岁小伙救下5人后溺亡 多方发声单亲妈妈陷入热恋 14岁儿子报警汪小菲曝离婚始末遭遇山火的松茸之乡雅江山火三名扑火人员牺牲系谣言何赛飞追着代拍打萧美琴窜访捷克 外交部回应卫健委通报少年有偿捐血浆16次猝死手机成瘾是影响睡眠质量重要因素高校汽车撞人致3死16伤 司机系学生315晚会后胖东来又人满为患了小米汽车超级工厂正式揭幕中国拥有亿元资产的家庭达13.3万户周杰伦一审败诉网易男孩8年未见母亲被告知被遗忘许家印被限制高消费饲养员用铁锨驱打大熊猫被辞退男子被猫抓伤后确诊“猫抓病”特朗普无法缴纳4.54亿美元罚金倪萍分享减重40斤方法联合利华开始重组张家界的山上“长”满了韩国人?张立群任西安交通大学校长杨倩无缘巴黎奥运“重生之我在北大当嫡校长”黑马情侣提车了专访95后高颜值猪保姆考生莫言也上北大硕士复试名单了网友洛杉矶偶遇贾玲专家建议不必谈骨泥色变沉迷短剧的人就像掉进了杀猪盘奥巴马现身唐宁街 黑色着装引猜测七年后宇文玥被薅头发捞上岸事业单位女子向同事水杯投不明物质凯特王妃现身!外出购物视频曝光河南驻马店通报西平中学跳楼事件王树国卸任西安交大校长 师生送别恒大被罚41.75亿到底怎么缴男子被流浪猫绊倒 投喂者赔24万房客欠租失踪 房东直发愁西双版纳热带植物园回应蜉蝣大爆发钱人豪晒法院裁定实锤抄袭外国人感慨凌晨的中国很安全胖东来员工每周单休无小长假白宫:哈马斯三号人物被杀测试车高速逃费 小米:已补缴老人退休金被冒领16年 金额超20万

玻璃钢生产厂家 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化