我煞筆的被 bufio.Reader 小坑

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

最近在用 Go 做一個小型的 gateway 服務。PHP 請求 Go 的 tcp server,然後 Go 根據命令參數開啟多個 goroutine 去調度 php-fpm 執行不同的指令碼並組合結果返回。 想來只是利用 goroutine 的便利並發執行邏輯,如此簡單直接。

不過在測試的時候 PHP 發送 socket 的 json 資料發生了明確的截斷,後來發現是我煞筆的被 bufio.Reader 坑了,真是無言以對。

問題重現

因為是長串連,PHP 每次發一段 json ,都會加上分行符號\n分割。我就很理所當然的用起了*bufio.Reader.ReadLine()。就像下面的代碼:

func handleConn(conn net.Conn) {    reader := bufio.NewReader(conn)    for {        // 讀取一行資料,交給幕後處理        line,_,err := reader.ReadLine()        if len(line) > 0{            fmt.Printf("ReadData|%d \n",len(line))            executeBytes(line)        }        if err != nil{            break        }    }    conn.Close()}

可是當 PHPer 測試發送大json資料的時候,發現了明確的截斷:

ReadData|4096|{"ename.com":........,"yunduo.com":{"check":[1// 又一次ReadData|4096|{"ename.com":........,"yunduo.com":{"getWhois"

想讓 json 還沒讀完就被截斷。我記得預設的 bufio.Reader 的大小是 4096,所以 bufio.Reader 的行為是讀滿了buffer就return出來啊。。我勒個去。我總不能寫個很大的size吧。

    reader := bufio.NewReaderSize(conn,409600)

PHP 發送的測試資料最大可能得到100k左右,平均只有<10k。我服務端每次都開一個100k+的bufio.Reader太浪費啦。最後變成,開小了截斷,開大了浪費,我煞筆了。

解決方式

既然bufio.Reader.ReadLine() 玩不下去了,我就只能回到最原生的用法,例如改造後的代碼:

func handleConn(conn net.Conn) {    buf := make([]byte, 4096)    var jsonBuf bytes.Buffer    for {        n, err := conn.Read(buf)        if n > 0 {            if buf[n-1] == 10 { // 10就是\n的ASCII                jsonBuf.Write(buf[:n-1]) // 去掉最後的分行符號                executeBytes(jsonBuf.Bytes())                jsonBuf.Reset() // 重設後用於下一次解析            } else {                jsonBuf.Write(buf[:n])            }        }        if err != nil {            break        }    }    conn.Close()}

這樣就是每次讀出4096的位元組,讀到\n就截斷,和之前放入buffer的資料取出來一起交給後續運算。這樣不用在意傳來的資料大小,只要\n分隔字元正確就沒有問題。

經過測試也沒有發現什麼業務問題,pprof也沒有看出明顯的效能瓶頸。那就這麼用著,我鬆一口氣交差。

又煞筆啦

bufio.Reader.ReadLine()應該沒有那麼傻吧。如果buffer滿了就return,我怎麼知道只是滿了還是讀到\n的返回,萬一內容剛好buffer長度呢!下班回家翻源碼,我又煞筆了:

// ReadLine is a low-level line-reading primitive. Most callers should use// ReadBytes('\n') or ReadString('\n') instead or use a Scanner.//// ReadLine tries to return a single line, not including the end-of-line bytes.// If the line was too long for the buffer then isPrefix is set and the// beginning of the line is returned. The rest of the line will be returned// from future calls. isPrefix will be false when returning the last fragment// of the line. The returned buffer is only valid until the next call to// ReadLine. ReadLine either returns a non-nil line or it returns an error,// never both.//// ......func (b *Reader) ReadLine() (line []byte, isPrefix bool, err error) {    line, err = b.ReadSlice('\n')    if err == ErrBufferFull {        // Handle the case where "\r\n" straddles the buffer.        if len(line) > 0 && line[len(line)-1] == '\r' {            // Put the '\r' back on buf and drop it from line.            // Let the next call to ReadLine check for "\r\n".            if b.r == 0 {                // should be unreachable                panic("bufio: tried to rewind past start of buffer")            }            b.r--            line = line[:len(line)-1]        }        return line, true, nil    }    ......}

我擦。如果讀出的滿了buffer,當不是\n結尾,isPrefix是true。我從來沒注意過中間這個傳回值的意思。真是被自己坑了。其實代碼可以這麼寫:

func handleConn(conn net.Conn) {    reader := bufio.NewReader(conn)    var jsonBuf bytes.Buffer    for {        // 讀取一行資料,交給幕後處理        line,isPrefix,err := reader.ReadLine()        if len(line) > 0{            jsonBuf.Write(line)            if !isPrefix{                executeBytes(jsonBuf.Bytes())                jsonBuf.Reset()            }        }        if err != nil{            break        }    }    conn.Close()}

RTFD

結論:Read The Fucking Documentation

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.