Golang之HTTP EOF/connection reset by peer详解

2019-06-10 12:18:48

背景

使用net/http同时发起多个简单请求时,偶尔会出现EOFconnect: 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的一个封装,那我们先来简单回归一下?
tcpzhuang-tai
上面的图向我们展示了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

关于服务目录框架的一些思考

陆陆续续使用Go来进行日常业务服务开发有段时间了,慢慢有了一些心得,也在往这些方面进行靠拢。 清晰的文件目录 我们在进行服务开发过程中,往往不是一个人在那战斗,而是一个团队。应该说,对于稍具规模的公司在这个方面都是相当看重的。因为清晰的目录结构、文件命名等体现一个团队的素养,一定程度上降低项目维护成本。比如在A同事接手B同事项目时能够做到尽量快的接手。 下面是我们现有服务的一个目录结构 展开查看 . ├── config │   └── dev.conf ├── constants │   ├── errors │   └── types ├── global │   ├── config.go │   └── config_watcher.go ├── main.go ├── modules │   └── doc.go ├── routes │   └── root.go └── vendor    └── ... 当前主要的目录结构就是以上的几个: config:顾名思义,就是存放的本地想相关配置文件,我们配置文件主要以json为主 constants:存放的是一些全局宏定义,比如定义的通用错误放errors,通用全局控制放types global:初始化配置文件的地方,从本地or远端配置服务拉取出来的配置均在此处进行初始化 modules:一些模块化定义的地方,

【转】深度解密Go语言之context

最近在公众号【码农桃花源】读到微信一篇关于context的文章,很不错,推荐给大家。原文地址:https://mp.weixin.qq.com/s/GpVy1eB5Cz_t-dhVC6BJNw Go 语言的 context 包短小精悍,非常适合新手学习。不论是它的源码还是实际使用,都值得投入时间去学习。 这篇文章依然想尝试全面、深入地去研究。文章相比往期而言,整体不长,希望你看完可以有所收获! 贴上文章的目录: 什么是context Go 1.7 标准库引入 context,中文译作“上下文”,准确说它是 goroutine 的上下文,包含 goroutine 的运行状态、环境、现场等信息。 context 主要用来在 goroutine 之间传递上下文信息,包括:取消信号、超时时间、