HTTP EOF/connection reset by peer详解 in golang
背景
使用net/http同时发起多个简单请求时,偶尔会出现EOF
或connect: connection reset by peer
的情况。明明就是一个很简单的例子,为何会出现这种情况呢?
举例
这里我为了省事,直接套用网上其他人的客户端的例子:
req, err := http.NewRequest(method, url, body)
if err != nil {
return nil, err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return b, nil
如此简单的几行代码,在我们套上大并发以后,各种异常情况接踵而至,最常见的就是下面的几个:
- EOF
- connection reset by peer
那具体又是什么原因导致出现上面几种情况呢?
探讨
HTTP也是针对TCP的一个封装,那我们先来简单回归一下?
上面的图向我们展示了TCP连接建立、数据通信以及连接断开的几个步骤,go得益于goroutine和channel,与其他语言的实现方式不太一样,它是直接起了两个协程,一个用于读(readLoop),一个用于写(writeLoop)。我们来看看官方描述:
from:io/io.go
// EOF is the error returned by Read when no more input is available.
// Functions should return EOF only to signal a graceful end of input.
// If the EOF occurs unexpectedly in a structured data stream,
// the appropriate error is either ErrUnexpectedEOF or some other error
// giving more detail.
var EOF = errors.New("EOF")
读写细节
conn.Read
-
socket无数据:read阻塞,直到有数据。
-
socket有部分数据:如果socket中有部分数据,且长度小于一次Read操作所期望读出的数据长度,那么Read将会成功读出这部分数据并返回,而不是等待所有期望数据全部读取后再返回。
-
socket有足够数据:如果socket中有数据,且长度大于等于一次Read操作所期望读出的数据长度,那么Read将会成功读出这部分数据并返回。这个情景是最符合我们对Read的期待的了:Read将用Socket中的数据将我们传入的slice填满后返回:n = 10, err = nil
-
有数据,socket关闭:第一次Read成功读出了所有的数据,当第二次Read时,由于client端 socket关闭,Read返回EOF error;
-
无数据,socket关闭:Read直接返回EOF error
conn.Write
-
成功写:Write调用返回的n与预期要写入的数据长度相等,且error = nil;
-
写阻塞:当发送方将对方的接收缓冲区以及自身的发送缓冲区写满后,Write就会阻塞;
-
写入部分数据:Write操作存在写入部分数据的情况,没有按照预期的写入所有数据,则需要循环写入。
开方子
通过上面的描述,我们大致已经明白了为何会出现EOF了,其实就是readLoop在进行读的时候,检测到socket被关闭了。在HTTP1.1,我们默认都是采用了Keep-Alive的,也就是会启动长连接,当server端断掉了该socket后,我们的EOF就出来了。所以尽量避免该情况发生的话,直接这样
req.Close = true
。
来源:https://dpjeep.com/2019/06/10/golangzhi-http-eofxiang-jie/