1、TCP粘包
服務(wù)端代碼如下:文章來源:http://www.zghlxwxcb.cn/news/detail-705236.html
// socket_stick/server/main.go
func process(conn net.Conn) {
defer conn.Close()
reader := bufio.NewReader(conn)
var buf [1024]byte
for {
n, err := reader.Read(buf[:])
if err == io.EOF {
break
}
if err != nil {
fmt.Println("read from client failed, err:", err)
break
}
recvStr := string(buf[:n])
fmt.Println("收到client發(fā)來的數(shù)據(jù):", recvStr)
}
}
func main() {
listen, err := net.Listen("tcp", "127.0.0.1:30000")
if err != nil {
fmt.Println("listen failed, err:", err)
return
}
defer listen.Close()
for {
conn, err := listen.Accept()
if err != nil {
fmt.Println("accept failed, err:", err)
continue
}
go process(conn)
}
}
客戶端代碼如下:
// socket_stick/server/main.go
func process(conn net.Conn) {
defer conn.Close()
reader := bufio.NewReader(conn)
var buf [1024]byte
for {
n, err := reader.Read(buf[:])
if err == io.EOF {
break
}
if err != nil {
fmt.Println("read from client failed, err:", err)
break
}
recvStr := string(buf[:n])
fmt.Println("收到client發(fā)來的數(shù)據(jù):", recvStr)
}
}
func main() {
listen, err := net.Listen("tcp", "127.0.0.1:30000")
if err != nil {
fmt.Println("listen failed, err:", err)
return
}
defer listen.Close()
for {
conn, err := listen.Accept()
if err != nil {
fmt.Println("accept failed, err:", err)
continue
}
go process(conn)
}
}
將上面的代碼保存后,分別編譯。先啟動(dòng)服務(wù)端再啟動(dòng)客戶端,可以看到服務(wù)端輸出結(jié)果如下:
收到client發(fā)來的數(shù)據(jù): Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?
收到client發(fā)來的數(shù)據(jù): Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?
收到client發(fā)來的數(shù)據(jù): Hello, Hello. How are you?Hello, Hello. How are you?
收到client發(fā)來的數(shù)據(jù): Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?
收到client發(fā)來的數(shù)據(jù): Hello, Hello. How are you?Hello, Hello. How are you?
客戶端分10次發(fā)送的數(shù)據(jù),在服務(wù)端并沒有成功的輸出10次,而是多條數(shù)據(jù)“粘”到了一起。
1.1.1 為什么出現(xiàn)粘包
主要原因就是tcp數(shù)據(jù)傳遞模式是流模式,在保持長(zhǎng)連接的時(shí)候可以進(jìn)行多次的收和發(fā)。
“粘包”可發(fā)生在發(fā)送端也可發(fā)生在接收端:
1.由Nagle算法造成的發(fā)送端的粘包:Nagle算法是一種改善網(wǎng)絡(luò)傳輸效率的算法。簡(jiǎn)單來說就是當(dāng)我們提交一段數(shù)據(jù)給TCP發(fā)送時(shí),TCP并不立刻發(fā)送此段數(shù)據(jù),而是等待一小段時(shí)間看看在等待期間是否還有要發(fā)送的數(shù)據(jù),若有則會(huì)一次把這兩段數(shù)據(jù)發(fā)送出去。
2.接收端接收不及時(shí)造成的接收端粘包:TCP會(huì)把接收到的數(shù)據(jù)存在自己的緩沖區(qū)中,然后通知應(yīng)用層取數(shù)據(jù)。當(dāng)應(yīng)用層由于某些原因不能及時(shí)的把TCP的數(shù)據(jù)取出來,就會(huì)造成TCP緩沖區(qū)中存放了幾段數(shù)據(jù)。
1.1.2 解決辦法
出現(xiàn)”粘包”的關(guān)鍵在于接收方不確定將要傳輸?shù)臄?shù)據(jù)包的大小,因此我們可以對(duì)數(shù)據(jù)包進(jìn)行封包和拆包的操作。
封包:封包就是給一段數(shù)據(jù)加上包頭,這樣一來數(shù)據(jù)包就分為包頭和包體兩部分內(nèi)容了(過濾非法包時(shí)封包會(huì)加入”包尾”內(nèi)容)。包頭部分的長(zhǎng)度是固定的,并且它存儲(chǔ)了包體的長(zhǎng)度,根據(jù)包頭長(zhǎng)度固定以及包頭中含有包體長(zhǎng)度的變量就能正確的拆分出一個(gè)完整的數(shù)據(jù)包。
我們可以自己定義一個(gè)協(xié)議,比如數(shù)據(jù)包的前4個(gè)字節(jié)為包頭,里面存儲(chǔ)的是發(fā)送的數(shù)據(jù)的長(zhǎng)度。
// socket_stick/proto/proto.go
package proto
import (
"bufio"
"bytes"
"encoding/binary"
)
// Encode 將消息編碼
func Encode(message string) ([]byte, error) {
// 讀取消息的長(zhǎng)度,轉(zhuǎn)換成int32類型(占4個(gè)字節(jié))
var length = int32(len(message))
var pkg = new(bytes.Buffer)
// 寫入消息頭
err := binary.Write(pkg, binary.LittleEndian, length)
if err != nil {
return nil, err
}
// 寫入消息實(shí)體
err = binary.Write(pkg, binary.LittleEndian, []byte(message))
if err != nil {
return nil, err
}
return pkg.Bytes(), nil
}
// Decode 解碼消息
func Decode(reader *bufio.Reader) (string, error) {
// 讀取消息的長(zhǎng)度
lengthByte, _ := reader.Peek(4) // 讀取前4個(gè)字節(jié)的數(shù)據(jù)
lengthBuff := bytes.NewBuffer(lengthByte)
var length int32
err := binary.Read(lengthBuff, binary.LittleEndian, &length)
if err != nil {
return "", err
}
// Buffered返回緩沖中現(xiàn)有的可讀取的字節(jié)數(shù)。
if int32(reader.Buffered()) < length+4 {
return "", err
}
// 讀取真正的消息數(shù)據(jù)
pack := make([]byte, int(4+length))
_, err = reader.Read(pack)
if err != nil {
return "", err
}
return string(pack[4:]), nil
}
接下來在服務(wù)端和客戶端分別使用上面定義的proto包的Decode和Encode函數(shù)處理數(shù)據(jù)。
服務(wù)端代碼如下:
// socket_stick/server2/main.go
func process(conn net.Conn) {
defer conn.Close()
reader := bufio.NewReader(conn)
for {
msg, err := proto.Decode(reader)
if err == io.EOF {
return
}
if err != nil {
fmt.Println("decode msg failed, err:", err)
return
}
fmt.Println("收到client發(fā)來的數(shù)據(jù):", msg)
}
}
func main() {
listen, err := net.Listen("tcp", "127.0.0.1:30000")
if err != nil {
fmt.Println("listen failed, err:", err)
return
}
defer listen.Close()
for {
conn, err := listen.Accept()
if err != nil {
fmt.Println("accept failed, err:", err)
continue
}
go process(conn)
}
}
客戶端代碼如下:文章來源地址http://www.zghlxwxcb.cn/news/detail-705236.html
// socket_stick/client2/main.go
func main() {
conn, err := net.Dial("tcp", "127.0.0.1:30000")
if err != nil {
fmt.Println("dial failed, err", err)
return
}
defer conn.Close()
for i := 0; i < 20; i++ {
msg := `Hello, Hello. How are you?`
data, err := proto.Encode(msg)
if err != nil {
fmt.Println("encode msg failed, err:", err)
return
}
conn.Write(data)
}
}
到了這里,關(guān)于Go語言網(wǎng)絡(luò)編程(socket編程)TCP粘包的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!