golang之http-eof和connection-reset-by-peer错误详解

多花一點心思去說溫柔的話,那些話可能改變他人一生

背景

使用net/http同时发起多个简单请求时,偶尔会出现EOF或connect: connection reset by peer的情况。明明就是一个很简单的例子,为何会出现这种情况呢?

举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
eq, 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的一个封装,go得益于goroutine和channel,与其他语言的实现方式不太一样,它是直接起了两个协程,一个用于读(readLoop),一个用于写(writeLoop)。

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/golangzhi-http-eofxiang-jie/