转载地址:https://mp.weixin.qq.com/s/MGMWkOON3DBkSjBKxGr7Kw

先来看一个 demo:

     1 package main2 3 import (4  "fmt"5  "net"6  "os"7  "runtime"8 )9 10 var rawFileList []*os.File11 12 func main() {13  l, err := net.Listen("tcp", ":12345")14  if err != nil {15   fmt.Println(err)16   return17  }18 19  var connList []net.Conn20  for {21   conn, err := l.Accept()22   connList = append(connList, conn)23   if err != nil {24    fmt.Println(err)25    return26   }27 28   go func() {29    f, err := conn.(*net.TCPConn).File()30    if err != nil {31     fmt.Println(err)32     return33    }34 35    rawFile := os.NewFile(f.Fd(), "")36    rawFileList = append(rawFileList, rawFile)37    _ = rawFile38    for {39     var buf = make([]byte, 1024)40     conn.Read(buf)41     conn.Write([]byte(`HTTP/1.1 200 OK42 Connection: Keep-Alive43 Content-Length: 044 Content-Type: text/html45 Server: Apache46 47 `))48     runtime.GC()49    }50   }()51  }52 }

可以认为是一个简单 read request,write response 的 http server,用 wrk 压的话,也能正常运行:

~ ❯❯❯ wrk http://localhost:12345
Running 10s test @ http://localhost:123452 threads and 10 connectionsThread Stats   Avg      Stdev     Max   +/- StdevLatency   589.84us    0.86ms  27.30ms   98.98%Req/Sec     9.19k     1.00k   10.91k    68.50%183093 requests in 10.02s, 16.94MB read
Requests/sec:  18278.93
Transfer/sec:      1.69MB

进程也没有什么错误日志,把上面的代码注释掉第 36 行再用 wrk 压测,这回结果就不一样了:

file tcp [::1]:12345->[::1]:58949: fcntl: bad file descriptor

这个结果还是有点令人意外的,我们又没有主动关闭连接,为什么会出现 bad file descriptor?

在代码中,我们使用连接的 rawFile 的 fd 新建了一个文件:

    29    f, err := conn.(*net.TCPConn).File()30    if err != nil {31     fmt.Println(err)32     return33    }34 35    rawFile := os.NewFile(f.Fd(), "") // 这里36    rawFileList = append(rawFileList, rawFile)37    _ = rawFile

注释掉 36 和没注释有什么区别呢?是谁把我们的连接给关了?

答案比较简单,rawFileList 是在堆上分配的全局对象,我们把 rawFile 追加进该数组后,GC 时便不会回收 rawFile。在 Go 语言中,文件类型在 GC 回收时会执行其 close 动作,这是通过 newFile 时的 SetFinalizer 完成的:

func newFile(fd uintptr, name string, kind newFileKind) *File {... 省略runtime.SetFinalizer(f.file, (*file).close)return f
}

也就是说所有文件类型都会在 GC 时被 close,在本文开头的 demo 中,这个被 close 的文件是我们用 raw fd 创建出来的,而 raw fd 本身是 uintptr 类型。我们知道,带 GC 的语言,对象之间主要是通过指针引用的,当我们用 uintptr 来创建新文件时,其实已经把这个引用关系破坏掉了:

右边的 NewFile 如果被 GC 先回收了,那么左边还在用这个文件就会报 bad file descriptor:

这时候可能有读者会觉得奇怪了,按说 net.Conn 是有 File 方法的,为什么我们直接用 File 这个方法生成出来的文件就没有问题?

那是因为 File 的实现中,将原有的 fd 复制了一份:

func (c *conn) File() (f *os.File, err error) {f, err = c.fd.dup() // 复制 fdif err != nil {err = &OpError{Op: "file", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err}}return
}

dup 操作会在 fd 上增加一个引用计数,当引用计数减为 0 时,才会执行 finalizer。

综上,看起来是个很简单的问题,生产环境查起来还是要费一些时间。因为类似的问题并不常见,祝你好运。

从 Go 语言一个文件描述符错误讲起相关推荐

  1. linux 关闭所有文件描述符,close - 关闭一个文件描述符

    SYNOPSIS 总览 #include int close(intfd); DESCRIPTION 描述 close 关闭 一个 文件 描述符 , 使它 不在 指向 任何 文件 和 可以 在 新的 ...

  2. 在c语言中文件的指针是什么,C语言中文件描述符和文件指针的本质区别

    1,首先了解进程运行时默认打开的文件指针以及打开的文件 /* Standard streams. / extern struct _IO_FILE stdin; / Standard input st ...

  3. dup、dup2实现文件描述符重定向(标准输入、标准输出、标准错误输出)

    目录 dup函数 dup2函数 重定向标准输入 重定向标准输出 重定向标准错误输出 重定向恢复 总结 在前文中,可以知道,文件描述符实际上是指向文件表项的指针数组索引,也就相当于每个文件描述符都对应一 ...

  4. 如何理解Linux shell中的“2>1”(将文件描述2(标准错误输出)的内容重定向到文件描述符1(标准输出))(尼玛>符号竟然不支持搜索,害我搜搜不到,只能搜)

    文章目录 前言 有何妙用 如何理解 总结 前言 有时候我们常看到类似这样的脚本调用: ./test.sh > log.txt 2>&1 这里的2>&1是什么意思?该如 ...

  5. Linux内核机制总结内存管理之用户页错误文件描述符(二十八)

    文章目录 1 用户页错误文件描述符 1.1 使用方法 1.2 技术原理 重要:本系列文章内容摘自<Linux内核深度解析>基于ARM64架构的Linux4.x内核一书,作者余华兵.系列文章 ...

  6. Linux文件,文件描述符以及dup()和dup2()

    一.Linux中文件 可以分为4种:普通文件.目录文件.链接文件和设备文件. 1.普通文件 是用户日常使用最多的文件,包括文本文件.shell脚本.二进制的可执行和各种类型的数据. ls -lh 来查 ...

  7. linux 反弹shell(一)文件描述符与重定向

    0X00 前言 由于在反弹shell的过程中有一些非常精简的语句,但是一直没有深入理解,只是作为一个伸手党/搬运工,于是下定决心要将其弄明白,而这里面最难的也就是文件描述符和重定向的部分,因此我特地写 ...

  8. linux存储--文件描述符fd与FILE结构体(二)

    文件描述符fd 对于linux而言,所有对设备(对于linux而言,一切皆文件)和文件的操作都使用文件描述符来进行的. 文件描述符是一个非负的整数,它是一个索引值,指向内核中每个进程打开文件的记录表. ...

  9. linux存储--文件描述符以及file结构体(一)

    一.什么是文件描述符 在Linux下一切皆文件,对于内核而言,所有打开的文件都通过文件描述符引用,文件描述符是一个非负整数,当打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符.当读. ...

最新文章

  1. 嘿嘿,JAVA里第一次运行单元测试成功,立存
  2. ubuntu 18.04下按照搜狗输入法
  3. x265与SVT-HEVC现已合二为一
  4. 全国计算机等级考试题库二级C操作题100套(第29套)
  5. 第五十二期:Java开发数据库设计的14个技巧,你知道几个?
  6. 解决Silverlight中DataGrid在显示数据时多一空白列
  7. 【干货】长文详解Attention的前世今生
  8. appassembler-maven-plugin插件打包本地依赖的jar
  9. VB.NET外部程序调用总结下
  10. 微信小程序分享和转发朋友圈功能
  11. PDP附着和PDP激活的区别
  12. Nodemailer 使用Gmail发送邮件
  13. sublimit 编辑器扩展_字幕编辑器(Srt Sub Master)
  14. C/C++ Linux 后台服务器开发高级架构师学习知识路线总结
  15. 使用R语言绘制心形图
  16. 解决chrome自动填充白色背景(input:-internal-autofill-previewed)问题
  17. c#实现循环输入商品编号和购买数量,结账时应付金额并找零
  18. 电路实验——实验三 叠加原理
  19. 衬线字体和无衬线字体区别_字体,字体和字体系列有什么区别?
  20. weblogic中间件WLS(bea_wls_internal)组件敏感信息泄露漏洞整改

热门文章

  1. MyBatis由浅入深学习总结之一:MyBatis入门案例
  2. netcore docker_深入浅出 ASP.NET Core 与 Docker 入门课程目标说明
  3. Python入门(05) -- 类
  4. java回调函数(全干货)
  5. 怎么用nuget程序包管理器安装jquery_Nuget服务器
  6. 好玩Python--分析你的微信好友签名
  7. 美国百亿亿次超算要比中国晚两三年,但真实性能更恐怖
  8. 探秘采云间:全链路数据处理工具直击传统DW/BI痛点
  9. 如何设置 jqplot 图表插件的轴和网格
  10. 论MS-Ajax导致的大对象堆碎片化问题