360云计算 360云计算
女主宣言
Go语言以其本身具有的高并发特性,在云计算开发中,得到了广泛的应用,也深受广大开发者的欢迎。但是大家对go语言真的理解了么?本文作者经过对go语言的多年实践应用,现对go语言中如何从io.Reader中读数据进行了详细介绍,相信对于go语言爱好者有很大的帮助。下来就跟随作者一起学习下吧。
PS:丰富的一线技术、多元化的表现形式,尽在“360云计算”,点关注哦!
1
概述
开发过程中,我们经常从io.Reader中读取数据。type Reader interface {
Read(p []byte) (n int, err error)
}
- 一次最多读取len(p)长度的数据。
- 当读取遭遇到error或EOF, 会返回已读取的数据的字节数和error或EOF。
- Read方法,不会修改len(p)的大小。
- 使用io.EOF 代表结束了。
package main
import (
"fmt"
"io"
"net"
)
func main() {
// 建立tcp连接
conn, err := net.Dial("tcp", "www.findme.wang:80")
if err != nil {
fmt.Println("dial error:", err)
return
}
defer conn.Close() // 关闭连接
// 构建http协议内容,发起http请求
httpReq := `GET / HTTP/1.0
Host: www.findme.wang
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36
Content-Type:application/x-www-form-urlencoded
Content-Length:0
`
_, err = fmt.Fprintf(conn, httpReq)
if err != nil {
fmt.Println("http request error:", err)
return
}
// read from conn
rsData := make([]byte, 0)
for {
// 每次最多读取512 个字节
var tmp = make([]byte, 512)
n, err := conn.Read(tmp)
if n >= 0 {
rsData = append(rsData, tmp[:n]...)
}
if err == io.EOF {
fmt.Println("数据读取完毕")
break
} else if err != nil {
fmt.Println("读取数据报错:", err)
break
}
}
fmt.Println("读取的数据长度:", len(rsData))
}
在案例中,我们利用for循环反复的读,有没有简洁的方式呢?2
利用io.copy读取
io.copy定义如下:func Copy(dst Writer, src Reader) (written int64, err error) {
return copyBuffer(dst, src, nil)
}
将reader中内容读取到dst中的数据,读取到dst中,所以我们需要一个writer 就行,来吧,封装一个如下:
package main
import (
"fmt"
"io"
"net"
)
type MyWriter struct {
data []byte
}
func (m *MyWriter) Write(p []byte) (n int, err error) {
if m.data == nil {
m.data = make([]byte, 0)
}
if p != nil && len(p) != 0 {
m.data = append(m.data, p...)
}
return len(p), nil
}
func main() {
// 建立tcp连接
conn, err := net.Dial("tcp", "www.findme.wang:80")
if err != nil {
fmt.Println("dial error:", err)
return
}
defer conn.Close() // 关闭连接
// 构建http协议内容,发起http请求
httpReq := `GET / HTTP/1.0
Host: www.findme.wang
User- Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36
Content-Type:application/x-www-form-urlencoded
Content-Length:0
`
_, err = fmt.Fprintf(conn, httpReq)
if err != nil {
fmt.Println("http request error:", err)
return
}
w := new(MyWriter)
n, err := io.Copy(w, conn) // 将 conn中的数据读取到 writer中
if err != nil {
fmt.Println("读取err ", err)
}
//fmt.Println(string(w.data))// 打印数据
fmt.Println("读取的数据长度:", n)
}
从io读取数据虽然是简单了,但是需要封装一个writer。那么,go里面是否有类似的writer呢?能够让我们很容易获取数据的writer呢?
于是,我们找到了strings.buffer ,如下:// A Builder is used to efficiently build a string using Write methods.
// It minimizes memory copying. The zero value is ready to use.
// Do not copy a non-zero Builder.
type Builder struct {
addr *Builder // of receiver, to detect copies by value
buf []byte
}
有了strings.buffer,代码又可精简一波。
package main
import (
"fmt"
"io"
"net"
"strings"
)
func main() {
// 建立tcp连接
conn, err := net.Dial("tcp", "www.findme.wang:80")
if err != nil {
fmt.Println("dial error:", err)
return
}
defer conn.Close() // 关闭连接
// 构建http协议内容,发起http请求
httpReq := `GET / HTTP/1.0
Host: www.findme.wang
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36
Content-Type:application/x-www-form-urlencoded
Content-Length:0
`
_, err = fmt.Fprintf(conn, httpReq)
if err != nil {
fmt.Println("http request error:", err)
return
}
var sb strings.Builder
n, err := io.Copy(&sb, conn) // 将 conn中的数据读取到 writer中
if err != nil {
fmt.Println("读取err ", err)
}
fmt.Println(sb.String()) // print res
fmt.Println("读取的数据长度:", n)
}
除了,使用strings.buffer,我们还可以使用bytes.Buffer。3
使用ioutil.ReadAll
ReadAll(r io.Reader) ([]byte, error) 是一次性从输入流(reader)中读取全量数据,直到发送错误或EOF。若读取失败,返回已读数据和err;若读取成功,则返回全量数据和nil。即改方法,不会返回EOF,案例如下:package main
import (
"fmt"
"io/ioutil"
"net"
)
func main() {
// 建立tcp连接
conn, err := net.Dial("tcp", "www.findme.wang:80")
if err != nil {
fmt.Println("dial error:", err)
return
}
defer conn.Close() // 关闭连接
// 构建http协议内容,发起http请求
httpReq := `GET / HTTP/1.0
Host: www.findme.wang
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36
Content-Type:application/x-www-form-urlencoded
Content-Length:0
`
_, err = fmt.Fprintf(conn, httpReq)
if err != nil {
fmt.Println("http request error:", err)
return
}
data, err := ioutil.ReadAll(conn)
if err != nil {
fmt.Println("读取err ", err)
}
fmt.Println(string(data)) // print res
fmt.Println("读取的数据长度:", len(data))
}
4
补充
此外,我们还可以使用io包提供的一些方法,比如:io.ReadAtLeast、io.ReadFull等.1、io.ReadAtLeast从输入流中至少min个字节,放到buf中,返回读取的字节数和err,结构如下:func ReadAtLeast(r Reader, buf []byte, min int) (n int, err error) {
if len(buf) < min {
return 0, ErrShortBuffer
}
for n < min && err == nil {
var nn int
nn, err = r.Read(buf[n:])
n += nn
}
if n >= min { //读取字节不小于min的时候,把err 设置nil
err = nil
} else if n > 0 && err == EOF {
err = ErrUnexpectedEOF
}
return
}
如果buf的长度小于 min,会触发ErrShortBuffer 。
- 如果读取的字节数小于min,这会触发ErrUnexpectedEOF 错误。
- 如果读取的字节数不小于min ,就算遇到了err,也会返回nil。
func ReadFull(r Reader, buf []byte) (n int, err error) {
return ReadAtLeast(r, buf, len(buf))
}
io.ReadFull本质上面调用了io.ReadAtLeast,在此不再赘述。