Golang: 從 io-Reader 中讀數據

Go 的標準庫提供了多個從 io.Reader 中讀取數據的方法 (ioioutil),本文通過從 net.Conn中讀取數據爲例,演示各種方法已經應用場景。

使用 TCP 連接訪問某個網站,採用HTTP 1.0的協議,讓 TCP 連接保持短連接,讀取完 response 之後連接會關閉,這樣就模擬了io.EOF的錯誤:

conn, err := net.Dial("tcp", "rpcx.site:80")

if err != nil {

	fmt.Println("dial error:", err)

return

}

defer conn.Close()

io.Reader.Read

io.Reader接口定義了Read(p []byte) (n int, err error)方法,我們可以使用它從 Reader 中讀取一批數據。

常常使用這個方法從輸入流中批量讀取數據,直到輸入流讀取到頭,但是需要注意的時候,我們應該總是先處理讀取到的 n 個字節,然後再處理 error。

如果你要實現io.Reader接口,在實現Read方法時,不建議你返回0,nil, 除非輸入的len(p)==0, 因爲這容易造成困惑,一般用0,io.EOF 來表示輸入流已經讀取到頭。

下面是演示這個方法的例子:

package main

import (

"fmt"

"io"

"net"

"strings"

)

func main() {

	conn, err := net.Dial("tcp", "rpcx.site:80")

if err != nil {

		fmt.Println("dial error:", err)

return

	}

defer conn.Close()

	fmt.Fprintf(conn, "GET / HTTP/1.0\r\n\r\n")

var sb strings.Builder

	buf := make([]byte, 256)

for {

		n, err := conn.Read(buf)

if err != nil {

if err != io.EOF {

				fmt.Println("read error:", err)

			}

break

		}

		sb.Write(buf[:n])

	}

	fmt.Println("response:", sb.String())

	fmt.Println("total response size:", sb.Len())

}

io.Copy

func Copy(dst Writer, src Reader) (written int64, err error)可以從 src 複製數據到 dst(Go 實現的方法一般把目的地址參數放在源參數的前面),直到:

它返回讀取的字節數以及遇到的第一個錯誤

注意成功的讀取完輸入流後 err 並不是io.EOF, 而是nil
它內部是調用CopyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error)實現的,
CopyBuffer會有可能使用一個指定的buf來複制數據, 如果 buf 的長度爲 0, 它會 panic。

在兩種特例的情況下 buf 並不會被使用, 而是使用接口的方法進行復制:

  1. src 實現了WriterTo接口
  2. dst 實現了ReadFrom接口
package main

import (

"fmt"

"io"

"net"

"strings"

)

func main() {

	conn, err := net.Dial("tcp", "rpcx.site:80")

if err != nil {

		fmt.Println("dial error:", err)

return

	}

defer conn.Close()

	fmt.Fprintf(conn, "GET / HTTP/1.0\r\n\r\n")

var sb strings.Builder

	_, err = io.Copy(&sb, conn)

if err != nil {

		fmt.Println("read error:", err)

	}

	fmt.Println("response:", sb.String())

	fmt.Println("total response size:", sb.Len())

}

ioutil.ReadAll

ReadAll(r io.Reader) ([]byte, error)提供了一個從輸入流中讀取全部數據的方法,它讀取輸入流直到出現錯誤(error)或者讀到頭(EOF)。

成功的讀取到頭不會返回 io.EOF, 而是返回數據和 nil。

我們經常用它來讀取可信的 http.Response.Body。

package main

import (

"fmt"

"io"

"io/ioutil"

"net"

)

func main() {

	conn, err := net.Dial("tcp", "rpcx.site:80")

if err != nil {

		fmt.Println("dial error:", err)

return

	}

defer conn.Close()

	fmt.Fprintf(conn, "GET / HTTP/1.0\r\n\r\n")

	data, err := ioutil.ReadAll(conn)

if err != nil {

if err != io.EOF {

			fmt.Println("read error:", err)

		}

panic(err)

	}

	fmt.Println("response:", string(data)) 

	fmt.Println("total response size:", len(data))

}

io.ReadFull

ReadFull(r Reader, buf []byte) (n int, err error)從輸入流中讀取正好 len(buf) 字節的數據。

package main

import (

"fmt"

"io"

"net"

"strings"

)

func main() {

	conn, err := net.Dial("tcp", "rpcx.site:80")

if err != nil {

		fmt.Println("dial error:", err)

return

	}

defer conn.Close()

	fmt.Fprintf(conn, "GET / HTTP/1.0\r\n\r\n")

var sb strings.Builder

	buf := make([]byte, 256)

for {

		n, err := io.ReadFull(conn, buf)

if err != nil {

if err != io.EOF && err != io.ErrUnexpectedEOF {

				fmt.Println("read error:", err)

			}

break

		}

		sb.Write(buf[:n])

	}

	fmt.Println("response:", sb.String())

	fmt.Println("total response size:", sb.Len())

}

io.ReadAtLeast

ReadAtLeast(r Reader, buf []byte, min int) (n int, err error)從輸入流中讀取至少 min 個字節到 buf 中,直到把 buf 讀取滿或者遇到 error,包括 EOF。

package main

import (

"fmt"

"io"

"net"

"strings"

)

func main() {

	conn, err := net.Dial("tcp", "rpcx.site:80")

if err != nil {

		fmt.Println("dial error:", err)

return

	}

defer conn.Close()

	fmt.Fprintf(conn, "GET / HTTP/1.0\r\n\r\n")

var sb strings.Builder

	buf := make([]byte, 256)

for {

		n, err := io.ReadAtLeast(conn, buf, 256)

if err != nil {

if err != io.EOF && err != io.ErrUnexpectedEOF {

				fmt.Println("read error:", err)

			}

break

		}

		sb.Write(buf[:n])

	}

	fmt.Println("response:", sb.String())

	fmt.Println("total response size:", sb.Len())

}

io.LimitReader

LimitReader(r Reader, n int64) Reader返回一個內容長度受限的 Reader,當從這個 Reader 中讀取了 n 個字節一會就會遇到 EOF。

設想一下如果沒有這個保護措施,別讓告訴你發送一個圖片,結果發送給你一個 3G 的葫蘆娃的視頻,有可能會使你的內存飆升。

它就是提供了一個保護的功能,其它和普通的 Reader 沒有兩樣。

package main

import (

"fmt"

"io"

"net"

"strings"

)

func main() {

	conn, err := net.Dial("tcp", "rpcx.site:80")

if err != nil {

		fmt.Println("dial error:", err)

return

	}

defer conn.Close()

	fmt.Fprintf(conn, "GET / HTTP/1.0\r\n\r\n")

var sb strings.Builder

	buf := make([]byte, 256)

	rr := io.LimitReader(conn, 102400)

for {

		n, err := io.ReadAtLeast(rr, buf, 256)

if err != nil {

if err != io.EOF && err != io.ErrUnexpectedEOF {

				fmt.Println("read error:", err)

			}

break

		}

		sb.Write(buf[:n])

	}

	fmt.Println("response:", sb.String())

	fmt.Println("total response size:", sb.Len())

}

參考資料

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://colobu.com/2019/02/18/read-data-from-net-Conn/