`

golang中tcp socket粘包问题和处理

 
阅读更多

http://www.01happy.com/golang-tcp-socket-adhere/

在用golang开发人工客服系统的时候碰到了粘包问题,那么什么是粘包呢?例如我们和客户端约定数据交互格式是一个json格式的字符串:

{"Id":1,"Name":"golang","Message":"message"}

当客户端发送数据给服务端的时候,如果服务端没有及时接收,客户端又发送了一条数据上来,这时候服务端才进行接收的话就会收到两个连续的字符串,形如:

{"Id":1,"Name":"golang","Message":"message"}{"Id":1,"Name":"golang","Message":"message"}

如果接收缓冲区满了的话,那么也有可能接收到半截的json字符串,酱紫的话还怎么用json解码呢?真是头疼。以下用golang模拟了下这个粘包的产生。

备注:下面贴的代码均可以运行于golang 1.3.1,如果发现有问题可以联系我。

粘包示例

server.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
//粘包问题演示服务端
package main
 
import (
    "fmt"
    "net"
    "os"
)
 
func main() {
    netListen, err := net.Listen("tcp", ":9988")
    CheckError(err)
 
    defer netListen.Close()
 
    Log("Waiting for clients")
    for {
        conn, err := netListen.Accept()
        if err != nil {
            continue
        }
 
        Log(conn.RemoteAddr().String(), " tcp connect success")
        go handleConnection(conn)
    }
}
 
func handleConnection(conn net.Conn) {
    buffer := make([]byte, 1024)
    for {
        n, err := conn.Read(buffer)
        if err != nil {
            Log(conn.RemoteAddr().String(), " connection error: ", err)
            return
        }
        Log(conn.RemoteAddr().String(), "receive data length:", n)
        Log(conn.RemoteAddr().String(), "receive data:", buffer[:n])
        Log(conn.RemoteAddr().String(), "receive data string:", string(buffer[:n]))
    }
}
 
func Log(v ...interface{}) {
    fmt.Println(v...)
}
 
func CheckError(err error) {
    if err != nil {
        fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
        os.Exit(1)
    }
}

client.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//粘包问题演示客户端
package main
 
import (
    "fmt"
    "net"
    "os"
    "time"
)
 
func sender(conn net.Conn) {
    for i := 0; i < 100; i++ {
        words := "{\"Id\":1,\"Name\":\"golang\",\"Message\":\"message\"}"
        conn.Write([]byte(words))
    }
}
 
func main() {
    server := "127.0.0.1:9988"
    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()
 
    fmt.Println("connect success")
 
    go sender(conn)
 
    for {
        time.Sleep(1 * 1e9)
    }
}

运行后查看服务端输出:

golang粘包问题演示

golang粘包问题演示

可以看到json格式的字符串都粘到一起了,有种淡淡的忧伤了——头疼的事情又来了。

粘包产生原因

关于粘包的产生原因网上有很多相关的说明,主要原因就是tcp数据传递模式是流模式,在保持长连接的时候可以进行多次的收和发。如果要深入了解可以看看tcp协议方面的内容。这里推荐下鸟哥的私房菜,讲的非常通俗易懂。

粘包解决办法

主要有两种方法:

1、客户端发送一次就断开连接,需要发送数据的时候再次连接,典型如http。下面用golang演示一下这个过程,确实不会出现粘包问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//客户端代码,演示了发送一次数据就断开连接的
package main
 
import (
    "fmt"
    "net"
    "os"
    "time"
)
 
func main() {
    server := "127.0.0.1:9988"
 
    for i := 0; i < 10000; i++ {
        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)
        }
 
        words := "{\"Id\":1,\"Name\":\"golang\",\"Message\":\"message\"}"
        conn.Write([]byte(words))
 
        conn.Close()
    }
 
    for {
        time.Sleep(1 * 1e9)
    }
}

服务端代码参考上面演示粘包产生过程的服务端代码

2、包头+数据的格式,根据包头信息读取到需要分析的数据。形式如下图:

golang粘包问题包头定义

golang粘包问题包头定义

从数据流中读取数据的时候,只要根据包头和数据长度就能取到需要的数据。这个其实就是平时说的协议(protocol),只是这个数据传输协议非常简单,不像tcp、ip等协议有较多的定义。在实际的过程中通常会定义协议类或者协议文件来封装封包和解包的过程。下面代码演示了封包和解包的过程:

protocol.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
//通讯协议处理,主要处理封包和解包的过程
package protocol
 
import (
    "bytes"
    "encoding/binary"
)
 
const (
    ConstHeader         = "www.01happy.com"
    ConstHeaderLength   = 15
    ConstSaveDataLength = 4
)
 
//封包
func Packet(message []byte) []byte {
    return append(append([]byte(ConstHeader), IntToBytes(len(message))...), message...)
}
 
//解包
func Unpack(buffer []byte, readerChannel chan []byte) []byte {
    length := len(buffer)
 
    var i int
    for i = 0; i < length; i = i + 1 {
        if length < i+ConstHeaderLength+ConstSaveDataLength {
            break
        }
        if string(buffer[i:i+ConstHeaderLength]) == ConstHeader {
            messageLength := BytesToInt(buffer[i+ConstHeaderLength : i+ConstHeaderLength+ConstSaveDataLength])
            if length < i+ConstHeaderLength+ConstSaveDataLength+messageLength {
                break
            }
            data := buffer[i+ConstHeaderLength+ConstSaveDataLength : i+ConstHeaderLength+ConstSaveDataLength+messageLength]
            readerChannel <- data
 
            i += ConstHeaderLength + ConstSaveDataLength + messageLength - 1
        }
    }
 
    if i == length {
        return make([]byte, 0)
    }
    return buffer[i:]
}
 
//整形转换成字节
func IntToBytes(n int) []byte {
    x := int32(n)
 
    bytesBuffer := bytes.NewBuffer([]byte{})
    binary.Write(bytesBuffer, binary.BigEndian, x)
    return bytesBuffer.Bytes()
}
 
//字节转换成整形
func BytesToInt(b []byte) int {
    bytesBuffer := bytes.NewBuffer(b)
 
    var x int32
    binary.Read(bytesBuffer, binary.BigEndian, &x)
 
    return int(x)
}

tips:解包的过程中要注意数组越界的问题;另外包头要注意唯一性。

server.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
//服务端解包过程
package main
 
import (
    "./protocol"
    "fmt"
    "net"
    "os"
)
 
func main() {
    netListen, err := net.Listen("tcp", ":9988")
    CheckError(err)
 
    defer netListen.Close()
 
    Log("Waiting for clients")
    for {
        conn, err := netListen.Accept()
        if err != nil {
            continue
        }
 
        Log(conn.RemoteAddr().String(), " tcp connect success")
        go handleConnection(conn)
    }
}
 
func handleConnection(conn net.Conn) {
    //声明一个临时缓冲区,用来存储被截断的数据
    tmpBuffer := make([]byte, 0)
 
    //声明一个管道用于接收解包的数据
    readerChannel := make(chan []byte, 16)
    go reader(readerChannel)
 
    buffer := make([]byte, 1024)
    for {
        n, err := conn.Read(buffer)
        if err != nil {
            Log(conn.RemoteAddr().String(), " connection error: ", err)
            return
        }
 
        tmpBuffer = protocol.Unpack(append(tmpBuffer, buffer[:n]...), readerChannel)
    }
}
 
func reader(readerChannel chan []byte) {
    for {
        select {
        case data := <-readerChannel:
            Log(string(data))
        }
    }
}
 
func Log(v ...interface{}) {
    fmt.Println(v...)
}
 
func CheckError(err error) {
    if err != nil {
        fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
        os.Exit(1)
    }
}

client.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//客户端发送封包
package main
 
import (
    "./protocol"
    "fmt"
    "net"
    "os"
    "time"
)
 
func sender(conn net.Conn) {
    for i := 0; i < 1000; i++ {
        words := "{\"Id\":1,\"Name\":\"golang\",\"Message\":\"message\"}"
        conn.Write(protocol.Packet([]byte(words)))
    }
    fmt.Println("send over")
}
 
func main() {
    server := "127.0.0.1:9988"
    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()
    fmt.Println("connect success")
    go sender(conn)
    for {
        time.Sleep(1 * 1e9)
    }
}

运行这个程序可以看到服务端很好的获取到期望的json格式数据。完整代码演示下载:golang粘包问题解决示例

最后

上面演示的两种方法适用于不同的场景。第一种方法比较适合被动型的场景,例如打开网页,用户有请求才处理交互。第二种方法适合于主动推送的类型,例如即时聊天系统,因为要即时给用户推送消息,保持长连接是不可避免的,这时候就要用这种方法。

<!-- 336x280 --> <iframe id="aswift_1" style="left: 0px; top: 0px; position: absolute;" name="aswift_1" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" width="336" height="280"></iframe>

转载请注明:快乐编程 » golang中tcp socket粘包问题和处理

分享到:
评论

相关推荐

    GOLANG语言实现SOCKET通讯粘包问题解决示例

    在TCP/IP通信中,"粘包...总之,解决GOLANG中的SOCKET通信粘包问题,关键在于定义清晰的协议格式,并在客户端和服务器端正确地打包和解包数据。通过这种方式,即使在TCP的流式传输中,也能确保数据的完整性和准确性。

    Golang TCP粘包拆包问题的解决方法

    ### Golang TCP粘包拆包问题的解决方法 #### 一、引言 在使用Go语言进行网络编程时,特别是TCP编程过程中,经常会遇到所谓的“粘包”与“拆包”问题。这些问题的发生通常会影响到数据的正确性以及系统的稳定性。...

    GoLang 实现的TCP/Socket:(一) 客户端

    GoLang 实现的TCP Socket 客户端 ,模拟与服务器通信的例子

    golang网络socket粘包问题的解决方法

    在Golang中,处理TCP Socket粘包问题通常涉及以下几个方面: 1. **消息边界**:为了区分不同的消息,我们需要在数据中加入消息边界。例如,可以在每个消息前加上一个固定长度的消息头,头里面包含消息的总长度。...

    6行代码快速解决golang TCP粘包问题

    在用golang开发人工客服系统的...下面这篇文章主要给大家介绍了关于如何通过6行代码快速解决golang TCP粘包问题的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考借鉴下面随着小编来一起学习学习吧。

    一个简单的golang socket服务框架,使用简单的通信协议解决粘包问题,通过心跳计时的方式能-socket.zip

    总结来说,Golang的Socket服务框架结合了Golang的并发特性,解决了网络通信中的粘包问题,并通过心跳机制确保了连接的稳定性。这样的框架是构建高性能、高可用网络服务的基础,也是Golang在服务器开发领域的一大优势...

    golang tcp http搭建与通信

    在本文中,我们将深入探讨如何使用Golang搭建TCP和HTTP服务器以及实现它们之间的通信。Golang,也称为Go语言,是Google开发的一种静态类型的、编译型的、并发型的、垃圾回收的、C风格的编程语言。它以其简洁的语法、...

    modbustcp:golang ModbusTCP库

    在Golang中,有多种库支持实现ModbusTCP功能,其中之一便是`modbustcp`库。这个库提供了方便的API,使得开发人员能够轻松地在Go语言程序中集成ModbusTCP通信。 首先,我们要理解什么是Modbus。Modbus是一种串行通信...

    Golang socket server

    golang语言实现socket服务端,解决粘包拆包问题,心跳超时检测. //启动socket func (server *SocketServer) Start() { listener, err := net.Listen(server.Network, server.Address) if err != nil { server.On...

    Go-socket.io在golang中的一个实现一个实时应用程序框架

    go-socket.io是socket.io在golang中的一个实现,一个实时应用程序框架

    golang编写测试socket读写缓冲

    在TCP/IP协议栈中,socket有一个内置的缓冲区,用于临时存储发送和接收的数据。了解这些缓冲区的工作原理对于优化网络应用的性能至关重要。 在描述中提到,代码的目标是“只建立连接,不进行读写数据”。这通常是...

    tcp socket代理服务器

    在IT行业中,TCP Socket代理服务器是一种常见的网络通信技术,它扮演着连接客户端和目标服务器的桥梁角色。在本文中,我们将深入探讨TCP Socket基础编程、代理服务器模型以及相关的源码和示例。 首先,让我们了解...

    Go-smux是一个采用Golang开发的socket多路复用器

    Go-smux是基于Golang构建的一个高效的socket多路复用器,它的设计目的是为了提高网络通信的效率和灵活性。在深入探讨Go-smux之前,我们首先需要理解什么是socket多路复用以及它为何重要。 Socket多路复用,通常被...

    golang之tcp自动重连实现方法

    本案例中,我们面临的问题是设计并实现一个Golang TCP客户端程序,该程序能够定期从服务器获取数据,并在遇到网络异常或连接断开时自动重新建立连接。 #### 三、技术栈 - **操作系统**:CentOS 6.9_x64 - **Go语言...

    Go-Tao一个用golang编写的异步TCP框架

    在TCP框架中,Go-Tao利用了Golang的goroutine和channel特性来实现高效的并发处理,使得网络服务器能够同时处理大量连接。Goroutine可以看作是轻量级线程,它们的创建和销毁成本极低,而channel则充当了goroutine间的...

    基于golang实现的Socket网络通信系统,适合于Go语.zip

    基于golang实现的Socket网络通信系统,适合于Go语

    golang中文手册_golang中文手册_goapiCHM_golang中文手册.chm_

    Goroutines允许同时执行多个任务,而channels则提供了它们之间的通信机制,避免了竞态条件和死锁问题。 6. **内存管理**:Go语言的垃圾回收机制自动管理内存,减少程序员手动管理内存的负担,同时也提高了程序的...

    Golang实现简单tcp服务器内含源码以及说明书可以自己运行复现.zip

    服务器粘包处理.md"会涉及到TCP协议的一个特性——粘包问题。由于TCP是基于字节流的,如果不做特殊处理,连续发送的数据包可能会被合并或分割,导致接收方难以解析。解决这个问题通常需要在数据包的头部添加长度信息...

Global site tag (gtag.js) - Google Analytics