上文介绍了拆包粘包的概念和按照固定分隔符的例子。
本文继续介绍消息头和包体的方案。
废话不多少直接上代码:
server:
// server.go
package main
import (
"bufio"
"encoding/binary"
"fmt"
"net"
)
const headerSize = 4 // Assuming a 4-byte header for simplicity
func handleConnection(conn net.Conn) {
defer conn.Close()
reader := bufio.NewReader(conn)
for {
// Read the fixed-length header
headerBytes := make([]byte, headerSize)
_, err := reader.Read(headerBytes)
if err != nil {
fmt.Println("Error reading header:", err)
return
}
// Convert the header to an integer to get the message length
messageLength := int(binary.BigEndian.Uint32(headerBytes))
// Read the message content based on the length
messageBytes := make([]byte, messageLength)
_, err = reader.Read(messageBytes)
if err != nil {
fmt.Println("Error reading message content:", err)
return
}
message := string(messageBytes)
fmt.Printf("Received message: %s\n", message)
}
}
func main() {
listener, err := net.Listen("tcp", "127.0.0.1:12345")
if err != nil {
fmt.Println("Error listening:", err)
return
}
defer listener.Close()
fmt.Println("Server is listening on port 12345")
for {
conn, err := listener.Accept()
if err != nil {
fmt.Println("Error accepting connection:", err)
continue
}
go handleConnection(conn)
}
}
client:
// client.go
package main
import (
"bufio"
"encoding/binary"
"fmt"
"net"
"os"
)
const headerSize = 4 // Assuming a 4-byte header for simplicity
func main() {
conn, err := net.Dial("tcp", "127.0.0.1:12345")
if err != nil {
fmt.Println("Error connecting:", err)
return
}
defer conn.Close()
reader := bufio.NewReader(os.Stdin)
for {
fmt.Print("Enter message (or 'exit' to quit): ")
message, _ := reader.ReadString('\n')
if message == "exit\n" {
break
}
// Convert the length to a 4-byte header
length := len(message)
header := make([]byte, headerSize)
binary.BigEndian.PutUint32(header, uint32(length))
// Send the length-prefixed message to the server
conn.Write(append(header, []byte(message)...))
}
}
在这个例子中,服务器和客户端各定义了一个长度4字节的消息头。消息头存储消息的长度,服务器读取消息头以确定传入消息的长度。这种方法允许根据特定应用程序的需要在标头中包含其他信息。根据的需求调整消息头大小。
运行结果:
固定包长
顾名思义就是每次发送消息为固定的长度,接收完毕后也按照该长度进行分割。
代码如下:
// 服务器端代码
package main
import (
"fmt"
"net"
)
const (
packetLength = 4
)
func main() {
listener, err := net.Listen("tcp", "localhost:8080")
if err != nil {
fmt.Println("Error listening:", err.Error())
return
}
defer listener.Close()
fmt.Println("Server started. Listening on localhost:8080")
for {
conn, err := listener.Accept()
if err != nil {
fmt.Println("Error accepting connection:", err.Error())
return
}
go handleConnection(conn)
}
}
func handleConnection(conn net.Conn) {
defer conn.Close()
fmt.Println("New client connected:", conn.RemoteAddr())
buffer := make([]byte, 0)
for {
data := make([]byte, 1024)
n, err := conn.Read(data)
if err != nil {
fmt.Println("Error reading data:", err.Error())
return
}
buffer = append(buffer, data[:n]...)
for len(buffer) >= packetLength {
packet := buffer[:packetLength]
buffer = buffer[packetLength:]
processPacket(packet)
}
}
}
func processPacket(packet []byte) {
fmt.Println("Received packet:", packet)
}
// 客户端代码
package main
import (
"fmt"
"net"
"time"
)
const (
packetLength = 4
)
func main() {
conn, err := net.Dial("tcp", "localhost:8080")
if err != nil {
fmt.Println("Error connecting:", err.Error())
return
}
defer conn.Close()
fmt.Println("Connected to server.")
packet := []byte{0x01, 0x02}
sendPacket(conn, packet)
time.Sleep(2 * time.Second)
packet = []byte{0x05, 0x06, 0x07, 0x08}
sendPacket(conn, packet)
}
func sendPacket(conn net.Conn, packet []byte) {
// 如果消息长度不够4字节,补充0x00字节
if len(packet) < packetLength {
padding := make([]byte, packetLength-len(packet))
packet = append(packet, padding...)
}
_, err := conn.Write(packet)
if err != nil {
fmt.Println("Error sending packet:", err.Error())
return
}
fmt.Println("Sent packet:", packet)
}
如果客户端发送单条消息长度不够固定长度,则使用0x00补全,就可以保证服务器每次能正常的解析到整包。