关于http请求的报错:connect reset by peer,我相信大家应该都有所见过。今天我来剖析一下这个报错情况以及整理一下相关技术知识。
案例
报错原因:服务端断开连接,但是客户端还是复用之前的连接进行请求,则会报此错误。如果是http1协议和http1.1协议中出现此类报错,则是开启:Keep-Alive,http1.1则是默认开启Keep-Alive的,如果是用的http2协议的话,则报错是因为复用连接。
复现方式:由于这个报错基于tcp协议来实现比较方便,我这里就采用go来实现server端,客户端,并且复现报错。
1 实现server端代码
package main
import (
"log"
"net"
"os"
)
func server() {
listener, err := net.Listen("tcp", ":8090")
if err != nil {
log.Fatal(err)
}
defer listener.Close()
conn, err := listener.Accept()
if err != nil {
log.Fatal("server", err)
os.Exit(1)
}
log.Printf("server close start")
conn.Close()
log.Printf("server close end")
}
func main() {
server()
}
2 实现客户端代码
package main
import (
"errors"
"log"
"net"
"syscall"
"time"
)
func client() {
conn, err := net.Dial("tcp", "124.221.97.119:8090")
if err != nil {
log.Fatal("client", err)
}
if _, err := conn.Write([]byte("ab")); err != nil {
log.Printf("client: %v", err)
}
time.Sleep(1 * time.Second) // wait for close on the server side
data := make([]byte, 1)
if _, err := conn.Read(data); err != nil {
log.Printf("client: %v", err)
if errors.Is(err, syscall.ECONNRESET) {
log.Print("This is connection reset by peer error")
}
}
}
func main() {
client()
}
3 启动server端
server端在124.221.97.119(这个我买的临时的机器,大家不要对我这个进行进行攻击哦)这个机器上。
4 执行客户端
客户端在另外一个机器上,然后server端会断开,客户端则会报如下的错误:
2022/03/27 19:59:20 client: read tcp 10.23.165.46:24972->124.221.97.119:8090: read: connection reset by peer
2022/03/27 19:59:20 This is connection reset by peer error
5 tcpdump抓包结果
20:11:23.580281 IP xx.xx.xx.xx.24976 > 10.0.16.6.8090: Flags [S], seq 3946141306, win 64952, options [mss 1412,sackOK,TS val 3205380659 ecr 0,nop,wscale 7], length 0
20:11:23.580293 IP 10.0.16.6.8090 > xx.xx.xx.xx.24976: Flags [S.], seq 298221640, ack 3946141307, win 28960, options [mss 1460,sackOK,TS val 2496773398 ecr 3205380659,nop,wscale 7], length 0
20:11:23.588677 IP xx.xx.xx.xx.24976 > 10.0.16.6.8090: Flags [.], ack 1, win 508, options [nop,nop,TS val 3205380668 ecr 2496773398], length 0
20:11:23.588698 IP xx.xx.xx.xx.24976 > 10.0.16.6.8090: Flags [P.], seq 1:3, ack 1, win 508, options [nop,nop,TS val 3205380668 ecr 2496773398], length 2
20:11:23.588703 IP 10.0.16.6.8090 > xx.xx.xx.xx.24976: Flags [.], ack 3, win 227, options [nop,nop,TS val 2496773406 ecr 3205380668], length 0
20:11:23.589014 IP 10.0.16.6.8090 > xx.xx.xx.xx.24976: Flags [R.], seq 1, ack 3, win 227, options [nop,nop,TS val 2496773407 ecr 3205380668], length 0
截图如下:
好了,上面的报错我们已经给大家复现出来了,下面我们来看一下另外一种情况,假如我们直接执行上面的client代码,会得到如下的报错:
2022/03/27 20:20:06 clientdial tcp 124.221.97.119:8090: connect: connection refused
exit status 1
这个时候的关于tcpdump抓包则会出现如下信息:
20:23:52.448494 IP xx.xx.xx.xx.25010 > 10.0.16.6.8090: Flags [S], seq 2442872680, win 64952, options [mss 1412,sackOK,TS val 3206129524 ecr 0,nop,wscale 7], length 0
20:23:52.448524 IP 10.0.16.6.8090 > xx.xx.xx.xx.25010: Flags [R.], seq 0, ack 2442872681, win 0, length 0
关于connection refused的报错,tcp协议直接会返回RST包给客户端,我们看下下面的图。
关于connect reset by peer,tcp协议响应了RST包,造成客户端报错。具体如图:
关于上面的出现的问题,我们需要学习如下知识,才能让我们更好的分析问题,复现问题,解决好问题。
1 理解tcpdump的输出
20:23:52.448494 IP xx.xx.xx.xx.25010 > 10.0.16.6.8090: Flags [S], seq 2442872680, win 64952, options [mss 1412,sackOK,TS val 3206129524 ecr 0,nop,wscale 7], length 0
内容结构
第一列:时分秒毫秒 20:23:52.448494
第二列:网络协议 IP
第三列:发送方的ip地址+端口号,其中xx.xx.xx.xx是 ip,而25010是端口号
第四列:箭头 >, 表示数据流向
第五列:接收方的ip地址+端口号,其中 10.0.16.6是 ip,而8090是端口号
第六列:冒号
第七列:数据包内容,包括Flags 标识符,seq 号,ack 号,win 窗口,数据长度 length,其中 [P.] 表示 PUSH 标志位为 1,更多看Flag标识符讲解。
Flags 标识符
[S] : SYN(开始连接)
[P] : PSH(推送数据)
[F] : FIN (结束连接)
[R] : RST(重置连接)
[.] : 没有 Flag,由于除了 SYN 包外所有的数据包都有ACK,所以一般这个标志也可表示 ACK
2 OSI 七层模型与TCP/IP五层模型
关于分层的知识,如下。
3 HTTP协议
HTTP协议基于TCP/IP的传输流图解:
一个HTTPS请求的流程图解
tcp建立图解
总结
1:要学会从问题出发,学习相关问题的技术知识,深入剖析知识,这样更容易成长。
2:connect reset by peer的报错是因为出现RST重置连接引发的报错。
参考文献
https://gosamples.dev/connection-reset-by-peer/#:~:text=Typically%2C%20you%20can%20see%20the,connection%20to%20be%20forcibly%20closed.