您的位置:首页 > 新闻 > 资讯 > 成都最正规的装修公司_今日军事_google关键词分析_seo顾问服务 品达优化

成都最正规的装修公司_今日军事_google关键词分析_seo顾问服务 品达优化

2025/1/6 16:44:09 来源:https://blog.csdn.net/qq_36940806/article/details/144833394  浏览:    关键词:成都最正规的装修公司_今日军事_google关键词分析_seo顾问服务 品达优化
成都最正规的装修公司_今日军事_google关键词分析_seo顾问服务 品达优化

1. 什么是 TCP 粘包与拆包?

  1. 粘包(Sticky Packet)
    粘包是指在发送多个小的数据包时,接收端会将这些数据包合并成一个数据包接收。由于 TCP 是面向流的协议,它并不会在每次数据发送时附加边界信息。所以当多个数据包按顺序发送时,接收端可能会一次性接收多个数据包的数据,造成数据被粘在一起。
    粘包一般发生在发送端每次写入的数据 < 接收端套接字(Socket)缓冲区的大小。

假设发送端发送了两个消息:消息1:“Hello”,消息2:“World”;由于 TCP 是流协议,接收端可能会接收到如下数据:“HelloWorld”。这种情况就是粘包,接收端就无法准确区分这两个消息。

  1. 拆包(Packet Fragmentation)
    拆包是指发送的数据包在传输过程中被分割成多个小包。尽管发送端可能发送了一个完整的消息,但由于 TCP 协议在网络传输时可能会对数据进行分段,接收端可能接收到的是多个小数据包。
    拆包一般发生在发送端每次写入的数据 > 接收端套接字(Socket)缓冲区的大小。

假设发送端发送了一个大的消息:“Hello, this is a long message.”;但是在传输过程中,网络层可能会将该消息拆分成多个小包,接收端可能先收到一部分数据:“Hello, this”,然后再收到另外一部分:“is a long message.”;这样接收端就会得到多个数据包,且它们并不代表单一的逻辑消息。

2. go 模拟TCP粘包

  1. server.go(接收端)
package mainimport ("bufio""fmt""io""net"
)func handleConnection(conn net.Conn) {defer conn.Close()// 创建缓冲读取器,读取客户端数据reader := bufio.NewReader(conn)var buffer [1024]bytefor {// 持续读取数据n, err := reader.Read(buffer[:])if err == io.EOF {break}if err != nil {fmt.Println("Error reading data:", err)break}recvStr := string(buffer[:n])// 打印接收到的数据fmt.Println("Received:", recvStr)}
}func main() {// 启动服务器,监听 8080 端口ln, err := net.Listen("tcp", ":8080")if err != nil {fmt.Println("Error starting server:", err)return}defer ln.Close()fmt.Println("Server started on port 8080...")for {// 等待客户端连接conn, err := ln.Accept()if err != nil {fmt.Println("Error accepting connection:", err)continue}// 处理连接go handleConnection(conn)}
}
  1. client.go(发送端)
package mainimport ("fmt""net""time"
)func main() {// 连接到服务器conn, err := net.Dial("tcp", "localhost:8080")if err != nil {fmt.Println("Error connecting to server:", err)return}defer conn.Close()// 模拟粘包和拆包for i := 0; i < 100; i++ {// 发送粘包情况:多个小消息一次发送message := fmt.Sprintf("Message %d\n", i+1)conn.Write([]byte(message))}// 等待服务器输出接收到的消息time.Sleep(2 * time.Second)
}
  1. 执行结果分析

在这里插入图片描述

可以看到接收端收到的消息并非都是一条,说明发生了粘包

3. go模拟TCP拆包

  1. server.go(接收端)
package mainimport ("bufio""fmt""io""net"
)func handleConnection(conn net.Conn) {defer conn.Close()// 创建缓冲读取器,读取客户端数据reader := bufio.NewReader(conn)var buffer [18]bytefor {// 持续读取数据n, err := reader.Read(buffer[:])if err == io.EOF {break}if err != nil {fmt.Println("Error reading data:", err)break}recvStr := string(buffer[:n])// 打印接收到的数据fmt.Println("Received message :", recvStr)}
}func main() {// 启动服务器,监听 8080 端口ln, err := net.Listen("tcp", ":8080")if err != nil {fmt.Println("Error starting server:", err)return}defer ln.Close()fmt.Println("Server started on port 8080...")for {// 等待客户端连接conn, err := ln.Accept()if err != nil {fmt.Println("Error accepting connection:", err)continue}// 处理连接go handleConnection(conn)}
}
  1. client.go(发送端)
package mainimport ("fmt""net""strings""time"
)func main() {// 连接到服务器conn, err := net.Dial("tcp", "localhost:8080")if err != nil {fmt.Println("Error connecting to server:", err)return}defer conn.Close()// 构造一个超过默认 MTU 的大数据包(32 字节)message := strings.Repeat("A", 32)// 模拟发送大量数据for i := 0; i < 100; i++ {fmt.Printf("Sending message : %s\n", message)conn.Write([]byte(message))}// 等待服务器输出time.Sleep(2 * time.Second)
}
  1. 执行结果分析
    在这里插入图片描述

可以看到接收端对接收到的数据进行了拆分,说明发生了拆包

4. 如何解决 TCP 粘包与拆包问题?

4.1 自定义协议

发送端将请求的数据封装为两部分:消息头(发送数据大小)+消息体(发送具体数据);接收端根据消息头的值读取相应长度的消息体数据

  1. server.go(接收端)
    服务端接收到数据时,首先读取前4个字节来获取消息的长度,然后再根据该长度读取完整的消息体
package mainimport ("encoding/binary""fmt""io""log""net"
)// readMessage 函数根据长度字段读取消息
func readMessage(conn net.Conn) (string, error) {// 读取4个字节的长度字段lenBytes := make([]byte, 4)_, err := io.ReadFull(conn, lenBytes)if err != nil {return "", fmt.Errorf("failed to read length field: %v", err)}// 解析消息长度msgLength := binary.BigEndian.Uint32(lenBytes)// 读取消息体msgBytes := make([]byte, msgLength)_, err = io.ReadFull(conn, msgBytes)if err != nil {return "", fmt.Errorf("failed to read message body: %v", err)}return string(msgBytes), nil
}func handleConnection(conn net.Conn) {defer conn.Close()// 一直循环接收客户端发来的消息for {msg, err := readMessage(conn)if err != nil {log.Printf("Error reading message: %v", err)break}fmt.Println("Received message:", msg)}
}func main() {// 启动监听服务listener, err := net.Listen("tcp", ":8080")if err != nil {log.Fatalf("Error starting server: %v", err)}defer listener.Close()fmt.Println("Server is listening on port 8080...")// 接受客户端连接并处理for {conn, err := listener.Accept()if err != nil {log.Printf("Error accepting connection: %v", err)continue}// 启动新的 Goroutine 处理客户端请求go handleConnection(conn)}
}
  1. client.go(发送端)
    客户端将连接到服务端,并发送多个消息。每个消息的前4字节表示消息的长度,随后是消息体
package mainimport ("bytes""encoding/binary""log""net"
)// sendMessage 函数将消息和长度一起发送给服务端
func sendMessage(conn net.Conn, msg string) {// 计算消息的长度msgLen := uint32(len(msg))buf := new(bytes.Buffer)// 将消息长度转换为4字节的二进制数据binary.Write(buf, binary.BigEndian, msgLen)// 将消息体内容添加到缓冲区buf.Write([]byte(msg))// 发送缓冲区数据到服务端conn.Write(buf.Bytes())
}func main() {// 连接到服务端conn, err := net.Dial("tcp", "127.0.0.1:8080")if err != nil {log.Fatalf("Error connecting to server: %v", err)}defer conn.Close()// 发送多个消息sendMessage(conn, "Hello, Server!")sendMessage(conn, "This is a second message.")sendMessage(conn, "Goodbye!")
}
4.2 固定长度数据包

每个消息的长度是固定的(例如 1024 字节)。如果客户端发送的数据长度不足指定长度,则会使用空格填充,确保每个数据包的大小一致

  1. server.go(接收端)
    服务端接收到的数据是固定长度的。每次接收 1024 字节的数据,并将其打印出来。如果数据不足 1024 字节,服务端会读取并处理这些数据。
package mainimport ("fmt""io""log""net""strings"
)// handleConnection 函数处理每个客户端的连接
func handleConnection(conn net.Conn) {defer conn.Close()// 设定每个消息的固定长度const messageLength = 1024buf := make([]byte, messageLength)for {// 每次读取固定长度的消息_, err := io.ReadFull(conn, buf)if err != nil {if err.Error() == "EOF" {// 客户端关闭连接break}log.Printf("Error reading message: %v", err)break}// 将读取的字节转换为字符串并打印msg := string(buf)// 去除空格填充fmt.Println("Received message:", strings.TrimSpace(msg))}
}func main() {// 启动 TCP 监听listener, err := net.Listen("tcp", ":8080")if err != nil {log.Fatalf("Error starting server: %v", err)}defer listener.Close()fmt.Println("Server is listening on port 8080...")// 等待客户端连接for {conn, err := listener.Accept()if err != nil {log.Printf("Error accepting connection: %v", err)continue}// 启动新的 Goroutine 处理每个客户端的连接go handleConnection(conn)}
}
  1. client.go(发送端)
    客户端会向服务器发送固定长度的消息,如果消息长度不足 1024 字节,则会填充空格
package mainimport ("log""net""strings"
)// sendFixedLengthMessage 函数向服务端发送固定长度的消息
func sendFixedLengthMessage(conn net.Conn, msg string) {// 确保消息长度为 1024 字节,不足部分用空格填充if len(msg) < 1024 {msg = msg + strings.Repeat(" ", 1024-len(msg))}// 发送消息到服务端_, err := conn.Write([]byte(msg))if err != nil {log.Fatalf("Error sending message: %v", err)}
}func main() {// 连接到服务端conn, err := net.Dial("tcp", "127.0.0.1:8080")if err != nil {log.Fatalf("Error connecting to server: %v", err)}defer conn.Close()// 发送固定长度的消息sendFixedLengthMessage(conn, "Hello, Server!")sendFixedLengthMessage(conn, "This is a second message.")sendFixedLengthMessage(conn, "Goodbye!")
}
4.3 特殊字符来标识消息边界

通过在发送端每条消息的末尾加上 \n,然后接收端使用 ReadLine() 方法按行读取数据来区分每个数据包的边界

  1. server.go(接收端)
    服务端会监听端口,并按行读取客户端发送的消息。每个消息的末尾会有一个 \n 来标识消息的结束
package mainimport ("bufio""fmt""log""net""strings"
)func handleConnection(conn net.Conn) {defer conn.Close()// 创建一个带缓冲的读取器reader := bufio.NewReader(conn)for {// 读取客户端发送的一行数据,直到遇到 '\n' 为止line, err := reader.ReadString('\n')if err != nil {log.Printf("Error reading from client: %v", err)break}// 去掉结尾的换行符line = strings.TrimSpace(line)fmt.Printf("Received message: %s\n", line)}
}func main() {// 启动 TCP 监听listener, err := net.Listen("tcp", ":8080")if err != nil {log.Fatalf("Error starting server: %v", err)}defer listener.Close()fmt.Println("Server is listening on port 8080...")// 等待客户端连接for {conn, err := listener.Accept()if err != nil {log.Printf("Error accepting connection: %v", err)continue}// 启动新的 Goroutine 处理每个客户端的连接go handleConnection(conn)}
}
  1. client.go(发送端)
    客户端向服务端发送消息,每条消息末尾都会加上一个 \n,然后发送到服务器
package mainimport ("log""net"
)func sendMessage(conn net.Conn, message string) {// 将消息添加换行符并发送message = message + "\n"_, err := conn.Write([]byte(message))if err != nil {log.Fatalf("Error sending message: %v", err)}
}func main() {// 连接到服务端conn, err := net.Dial("tcp", "127.0.0.1:8080")if err != nil {log.Fatalf("Error connecting to server: %v", err)}defer conn.Close()// 发送几条消息sendMessage(conn, "Hello, Server!")sendMessage(conn, "How are you?")sendMessage(conn, "Goodbye!")
}

5. 三种方式的优缺点对比

特性固定长度方式特殊字符分隔方式自定义协议方式
实现简单
带宽效率低(需要填充)高(仅传输有效数据)高(仅传输有效数据,且灵活处理)
灵活性
易于调试高(每包大小固定)中(需解析换行符等)低(需要解析协议头和体)
性能开销中等(需要额外解析消息头)
适用场景长度固定的消息消息大小可变但有清晰的分隔符复杂协议、支持多类型消息的场景

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com