分享
  1. 首页
  2. 文章

Go语言小贴士1 - io包

达达 · · 1732 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

Go语言作为定位服务端编程的语言,处理文件和网络通讯是它主要的应用场景,不论是处理文件还是处理网络通讯,它们都被称之为IO编程,即-对计算机的输入输出设备进行编程。 Go的运行时有一个名叫io的包,从命名可想而知它在Go语言的实际应用中有多么重要的地位,Go语言的所有IO编程都绕不过这一个包。

所以正确的理解这个包,在Go语言的工程实践中是非常重要的,不论你是准备用Go语言处理文件还是处理网络通讯,请务必先看这个包。

io包中大部分是接口定义,其中io.Readerio.Writer最为关键。在Go语言中,文件、套接字等一切输入设备抽象,都会实现io.Reader接口,而一切输出设备抽象,都会实现io.Writer接口。

io.Reader的定义如下:

type Reader interface {
 Read(p []byte) (n int, err error)
}

其中文档的说明非常重要,文档中详细描述了Read方法的各种返回可能性。

文档描述中有一个要点,就是n可能小于等于len(p),也就是说Go在读IO的时候,是不会保证一次读取预期的所有数据的。

如果我们要确保一次读取我们所需的所有数据,就需要在一个循环里调用Read,累加每次返回的n并小心设置下次Readp的偏移量,直到n的累加值达到我们的预期。

因为上述需求实在太常见了,所以Go在io包中提供了一个ReadFull函数来做到一次读取要求的所有数据,通过阅读ReadFull函数的代码,也可以反过来帮助大家理解io.Reader是怎么运作的。

我们稍微跳跃一下,看一下net包中的Conn接口,net.Conn接口的第一个方法就是Read,方法签名跟io.Reader接口要求的一样。

要做好Go语言的网络编程,在理解net.Conn之前,先得理解io.Reader,否则该用io.ReadFull的地方没有用,就可能出现奇怪的协议解析错误,并且很难排查。

这就是我说io.Reader很重要的原因。

接着我们再看io.Writer接口:

type Writer interface {
 Write(p []byte) (n int, err error)
}

文档中最关键的信息是:如果n小于len(p),err必须不为nil

也就是说io.Writer在每次写数据时,要保证数据的完整写入,这个特性跟io.Reader正好是相反的。

基于io.Writer的这一特性,我们可以推断,当我们往一个net.Conn写入数据时(是的,它当然也实现了这个接口),调用会阻塞直到数据完整写完或者发生IO错误,这甚至不需要我们阅读任何Go的底层代码就可以做出这个判断,如果你去阅读net包和runtime包的底层代码,可以进一步确认这个事实。

基于这个事实,我们在设计网络应用或其它IO应用的时候就要小心我们的应用场景中IO阻塞是否是可接受的,如果IO阻塞会影响到业务处理,那我们就需要想办法让IO变成异步行为。

Go语言中要让一个事情变成异步很简单,举个例子:

type Session struct {
 conn net.Conn
 sendChan chan []byte
}
func NewSession(conn net.Conn, sendChanSize int) *Session {
 s := &Session{
 conn: conn,
 sendChan: make(chan []byte, sendChanSize),
 }
 go s.sendLoop()
 return s
}
func (s *Session) sendLoop() {
 for {
 p := <-s.sendChan
 _, err := s.conn.Write(p)
 if err != nil {
 return
 }
 }
}
func (s *Session) Send(p []byte) error {
 select {
 case s.sendChan <- p:
 default:
 return errors.New("Send Chan Blocked!")
 }
 return nil
}

以上代码是利用Goroutine和chan来实现异步发送消息,Go的chan在缓冲区满之前是不会阻塞的,缓冲区满了再继续往里写就会阻塞。

为了防止极端情况下chan的缓冲区满发生阻塞影响到业务,我们利用select语法的特性来做到不阻塞并返回错误。

io包除了io.Readerio.Writer外,还有很多很有用的内容,比如io.Copy,通过阅读底层实现代码,你会发现io.Copy不仅仅是简单的从一个io.Reader读区数据写入io.Writer,当要通过net.Conn发送一个文件时,它其实会利用Linux的SendFile机制做到零拷贝。

这篇文章没办法一一例举和说明io包的所有内容,也无法代替包的文档,请大家务必要自己仔细阅读包的文档(其实文档第二段就非常关键,不要假定这些调用是线程安全的)。


有疑问加站长微信联系(非本文作者)

本文来自:达达的主页

感谢作者:达达

查看原文:Go语言小贴士1 - io包

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
1732 次点击
1 回复 | 直到 2025年05月15日 08:50:13
暂无回复
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏