暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Go语言I/O框架

专业造轮子 2021-10-10
818

Go的I/O特点

Go的I/O分网络I/O和文件I/O:

  • 网络fd可以用epoll管理事件,实现异步I/O
  • 文件fd不能用epoll管理事件,只能同步I/O

简而言之,Go是网络I/O密集型程序,I/O操作是Go最核心内容之一

I/O接口

I/O就是读写,go用interface
来定义I/O,具体代码参考io/io.go
,最基本的就是Reader
Writer
接口,不涉及到具体的实现

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

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

I/O主要可以分为三类:

  1. 基础类型: Reader
    Writer
    Closer
    ReaderAt
    Seeker
    ByteReader
    ByteWriter
    RuneReader
    StringWriter
    等. 这是最基本的接口,自己可以实现这些接口,定义新的I/O相关的包,但是实现之前一定要参考相关注释
  2. 组合类型: ReaderCloser
    WriterCloser
    WriteSeeker
    等. 这些是把基本的接口组合起来
  3. 进阶类型: TeeReader
    LimitReader
    SectionReader
    MultiReader
    MultiWriter
    PipeReader
    PipeWriter
    等. 这种基于基础接口之上,加了一些有趣的实现

I/O还有一些通用的函数:

  • Copy
    : 把一个Reader
    读出来,写到Writer
    中去,直到其中一方出错为止(最常见是EOF)
  • CopyN
    : 和Copy
    类似,但是多了一个结束条件:数据拷贝不会超过N
  • CopyBuffer
    : 也是拷贝函数,但是能让用户指定使用多大的Buffer内存,使用恰当可以优化性能

I/O工具包

I/O工具包主要指的是io/ioutil
, 这个库位于src/io
目录之下,目录路径为src/io/ioutil
。他的核心是提供一些方便使用的函数,如

  • ReadFile
    : 给一个路径,把文件读到内存(open,close,read都统一封装进来)
  • WriteFile
    : 给一个路径,把内存写入文件
  • ReadDir
    : 给一个目录路径,把这个路径下的文件列表一起读取
  • ReadAll
    : 给一个Reader
    流,全都读到内存

这个工具包只是简便操作,但是并不关心客观条件是否满足,比如ReadAll
,只是读取所有内容,但是并不关心内存是否足够容纳

I/O拓扑

Go的I/O封装了其他的相关的包,比如文件I/O,网络I/O,bufio,bytes,strings都是实现基础类型的接口,所以I/O包丰富多彩,I/O的主体可以随意定制

bytes

bytes
库,主要把[]byte
转换成Reader
Writer
,从而把内存块作为读写的数据流。主要类型有bytes.Reader
(仅仅为Reader
)和bytes.Buffer
(Reader
Writer
).

buffer := make([]byte1024)
readerFromBytes := bytes.NewReader(buffer)
n, err := io.Copy(ioutil.Discard, readerFromBytes)
fmt.Printf("n=%v, err=%v\n", n, err)

strings

strings.Reader
可以把字符串转换成Reader
,直接把字符串作为读源

data := "hello world"

readerFromBytes := strings.NewReader(data)
n, err := io.Copy(ioutil.Discard, readerFromBytes)
fmt.Printf("n=%v, err=%v\n", n, err)

net

网络可以作为读写源,抽象成了Reader
Writer
形式,是net.Conn
封装出来的

func handleConn(conn net.Conn) {
    defer conn.Close()
    buf := make([]byte4096)
    conn.Read(buf)
    conn.Write([]byte("pong: "))
    conn.Write(buf)
}

func main() {
    // 创建套接字并监听
    server, err := net.Listen("tcp"":9999")
    if err != nil {
        log.Fatalf("err:%v", err)
    }
    for {
        // 接收连接,可以reader也可以writer
        c, err := server.Accept()
        if err != nil {
            log.Fatalf("err:%v", err)
        }
        go handleConn(c)
    }
}

func main() {
    // 创建连接,可以作为reader也可以作为writer
    conn, err := net.Dail("tcp"":9999")
    if err != nil {
        panic(err)
    }
    conn.Write([]byte("hello world\n"))
    io.Copy(os.Stdout, conn)
}

Go的net
封装了底层的系统调用:syscall.Socket
syscall.Connect
syscall.Listen
syscall.GetsockoptInt
.创建之后就会设置成nonblock
模式,从而使用I/O多路复用;然后把socket fd放到epoll池里,监听事件;最后封装好读写事件到来的函数回调

os

os
库通常指文件I/O。Go封装了一个File
结构体,对外实现了Read
Write
ReadAt
WriteAt
可以把其当作Reader
Writer
来使用

os.Stdin
os.Stdout
os.Stderr
分别是标准输入,标准输出和标准错误输出,这三个变量也是File
的变量,定义在src/os/file.go
文件里。这三个也是Linux的 0, 1, 2文件句柄

var (
    Stdin = NewFile(uintptr(syscall.Stdin), "/dev/stdin")
    Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout")
    Stderr = NewFile(uintptr(syscall.Stderr), "/dev/stderr")
)

bufio

c语言的缓冲I/O有三种模式:

  1. 全缓冲:数据填满buffer才会调用底层I/O
  2. 行缓冲:遇到换行符,就会调用底层I/O
  3. 不缓冲:每次都直接调用底层I/O

buffer I/O主要是为了减少底层I/O调用次数,但是会损失数据一致性,还可能出现脏读的情况

bufio
就是提供缓冲I/O的功能,对一个Reader
Writer
携带一个buffer,做了一层封装,达到聚合的目的。

bufio.NewWriter // 带buffer的writer,默认大小是4096
bufio.NewWriterSize // 手动指定size

总结

  1. Go的I/O核心库是src/io
    ,如果要实现其中的interface
    ,一定要先阅读注视
  2. 除了基础的I/O功能,还实现了通用函数,如Copy
  3. io/ioutil
    实现挺复杂,但真香
  4. 内存字节数组可以作为Reader
    Writer
    ,实现在bytes
    库中
  5. 字符串可以作为Reader
    ,实现在strings
    库中
  6. 网络句柄可以作为Reader
    Writer
    , 实现在net
    库中,net.Conn
  7. 文件句柄可以作为Reader
    Writer
    , 实现在os
    库中, os.File
  8. 标准输入,标准输出,标准错误可以作为Reader
    Writer
    实现在os
    库中, os.Stdio
    os.Stdout
    os.Stderr

参考资料

  • 浅析Go IO的知识框架


文章转载自专业造轮子,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论