【轉】解決golang開發socket服務時粘包半包bug

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

編程過程中遇到了粘包問題,看到這篇很詳盡的就mark下來了,雖然看代碼很簡單,也沒能解決我的粘包問題,但是對於自己瞭解粘包還是有用的麼,是吧。
在使用golang做socket服務時,我想大多數人都會碰見粘包的問題。 以前用python做socket服務時就想寫一篇關於tcp粘包的問題,後來因為單純的tcp伺服器開發功能實在煩雜,索性直接用http tornado進行通訊了。
下面的資料有些是來自我個人的印象筆記,相關的參考引用連結早就找不到了。

該文章寫的有些亂,歡迎來噴 ! 另外文章後續不斷更新中,請到原文地址查看更新。
http://xiaorui.cc/?p=2888

什麼是半包 ?

接受方沒有接受到完整的包,只接受了一部分。 由於發送方看到內容太大切分資料包進行發送,這樣切包能提高傳輸效率,如果一個包太大,接受方並不能一次接受完。(在長串連和短串連中都會出現)。
注: 半包、粘包都可以用後面的方法解決.

什麼是分包?
既然tcp的包產生了粘包,那麼需要分開處理吧。 對,這就是分包 ! 分包的前提是用戶端和服務端都提前定義一組結構,可以讓你準確拆分粘包的結構。

什麼時候需要考慮粘包的問題?

1: 類似 http的請求就不用考慮粘包的問題,因為服務端收到報文後, 就將緩衝區資料接收, 然後關閉串連,這樣粘包問題不用考慮到,因為大家都知道是發送一段字元。

2:如果發送資料無結構,如檔案傳輸,這樣發送方只管發送,接收方只管接收儲存就ok,也不用考慮粘包

3:如果雙方建立串連,需要在串連後一段時間內發送不同結構資料,如串連後,有好幾種結構: 1)”save it” 2)”delete it “ 這時候很不巧,發送方連續發送這個兩個包出去,接收方一次接收可能會是”saveit delete it” 這樣接收方就傻了,到底是要幹嘛? 不知道,因為協議沒有規定這麼詭異的字串,所以要處理把它分包,怎麼分也需要雙方組織一個比較好的包結構,所以一般可能會在頭加一個資料長度之類的包,以確保接收。 接著我們用虛擬碼來實現下tcp粘包的情境.
粘包問題就是TCP在傳輸資料時, 為了提高傳輸速度和效率, 把發送緩衝區中的資料拼為一個資料包發送到目的地 比如:
發送方:send(s, “abce”);send(s, “decfg”);
接收方:recv(s, buf); //buf = “abcedecfg”;
再廢話下,用一段話來描述什麼是tcp粘包:
出現粘包現象的原因既可能由發送方造成,也可能由接收方造成。
1 發送端需要等緩衝區滿才發送出去,造成粘包
2 接收方沒能及時地接收緩衝區的包,造成多個包接收

解決辦法:
為了避免粘包現象,可採取以下幾種措施。

  1. 對於發送方引起的粘包現象,使用者可通過編程設定來避免,TCP提供了強制資料立即傳送的操作指令push,TCP軟體收到該操作指令後,就立即將本段資料發送出去,而不必等待發送緩衝區滿;
    缺點: 第一種編程設定方法雖然可以避免發送方引起的粘包,但它關閉了最佳化演算法,降低了網路發送效率,影響應用程式的效能,一般不建議使用。
  2. 對於接收方引起的粘包,則可通過最佳化程式設計、精簡接收進程工作量、提高接收進程優先順序等措施,使其及時接收資料,從而盡量避免出現粘包現象;
    缺點: 第二種方法只能減少出現粘包的可能性,但並不能完全避免粘包,當發送頻率較高時,或由於網路突發可能使某個時間段資料包到達接收方較快,接收方還是有可能來不及接收,從而導致粘包。

最後解決tcp粘包的方法:
用戶端會定義一個標示,比如資料的前4位是資料的長度,後面才是資料。那麼用戶端只需發送 ( 資料長度+資料 ) 的格式資料就可以了,接收方根據包頭資訊裡的資料長度讀取buffer.

下面直接說golang socket下解決粘包的執行個體代碼.
用戶端:

//用戶端發送封包package main import (    "fmt"    "math/rand"    "net"    "os"    "strconv"    "strings"    "time") func main() {     server := "127.0.0.1:5000"    tcpAddr, err := net.ResolveTCPAddr("tcp4", server)    if err != nil {        fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())        os.Exit(1)    }     conn, err := net.DialTCP("tcp", nil, tcpAddr)    if err != nil {        fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())        os.Exit(1)    }     defer conn.Close()     for i := 0; i < 50; i++ {        //msg := strconv.Itoa(i)        msg := RandString(i)        msgLen := fmt.Sprintf("%03s", strconv.Itoa(len(msg)))        //fmt.Println(msg, msgLen)        words := "aaaa" + msgLen + msg        //words := append([]byte("aaaa"), []byte(msgLen), []byte(msg))        fmt.Println(len(words), words)        conn.Write([]byte(words))    }} /***產生隨機字元**/func RandString(length int) string {    rand.Seed(time.Now().UnixNano())    rs := make([]string, length)    for start := 0; start < length; start++ {        t := rand.Intn(3)        if t == 0 {            rs = append(rs, strconv.Itoa(rand.Intn(10)))        } else if t == 1 {            rs = append(rs, string(rand.Intn(26)+65))        } else {            rs = append(rs, string(rand.Intn(26)+97))        }    }    return strings.Join(rs, "")}

服務端:

package main import (    "fmt"    "io"    "net"    "os"    "strconv") func main() {    netListen, err := net.Listen("tcp", ":5000")    CheckError(err)     defer netListen.Close()     for {        conn, err := netListen.Accept()        if err != nil {            continue        }         go handleConnection(conn)    }} func handleConnection(conn net.Conn) {    allbuf := make([]byte, 0)    buffer := make([]byte, 1024)    for {        readLen, err := conn.Read(buffer)        //fmt.Println("readLen: ", readLen, len(allbuf))        if err == io.EOF {            break        }        if err != nil {            fmt.Println("read error")            return        }         if len(allbuf) != 0 {            allbuf = append(allbuf, buffer...)        } else {            allbuf = buffer[:]        }        var readP int = 0        for {            //fmt.Println("allbuf content:", string(allbuf))             //buffer長度小於7            if readLen-readP < 7 {                allbuf = buffer[readP:]                break            }             msgLen, _ := strconv.Atoi(string(allbuf[readP+4 : readP+7]))            logLen := 7 + msgLen            //fmt.Println(readP, readP+logLen)            //buffer剩餘長度>將處理的資料長度            if len(allbuf[readP:]) >= logLen {                //fmt.Println(string(allbuf[4:7]))                fmt.Println(string(allbuf[readP : readP+logLen]))                readP += logLen                //fmt.Println(readP, readLen)                if readP == readLen {                    allbuf = nil                    break                }            } else {                allbuf = buffer[readP:]                break            }        }    }} func CheckError(err error) {    if err != nil {        fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())        os.Exit(1)    }}

代碼測試可以直接用

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.