跳至主要內容

Socket

LiCheng大约 6 分钟

Socket

介绍

  • 2022/10/25

自定义协议

  • 2023/2/16

// 定义一个自定义协议的结构体,包含消息的长度、类型和内容
type MyProtocol struct {
	Length int    // 消息的长度,用4个字节表示
	Type   int    // 消息的类型,用4个字节表示,比如1表示文本,2表示图片,3表示音频等
	Data   []byte // 消息的内容,用字节切片表示,长度由Length决定
}

// 定义一个编码函数,将自定义协议的结构体转换为字节切片,用于发送数据
func Encode(mp *MyProtocol) []byte {
	// 创建一个缓冲区,用于存储编码后的数据
	buf := bytes.NewBuffer([]byte{})
	// 使用encoding/binary包中的Write函数,按照大端字节序,将结构体中的字段写入缓冲区
	binary.Write(buf, binary.BigEndian, mp.Length)
	binary.Write(buf, binary.BigEndian, mp.Type)
	binary.Write(buf, binary.BigEndian, mp.Data)
	// 返回缓冲区中的字节切片
	return buf.Bytes()
}

// 定义一个解码函数,将字节切片转换为自定义协议的结构体,用于接收数据
func Decode(data []byte) *MyProtocol {
	// 创建一个缓冲区,用于存储解码后的数据
	buf := bytes.NewBuffer(data)
	// 创建一个自定义协议的结构体,用于存储解码后的字段
	mp := &MyProtocol{}
	// 使用encoding/binary包中的Read函数,按照大端字节序,从缓冲区中读取字段到结构体中
	binary.Read(buf, binary.BigEndian, &mp.Length)
	binary.Read(buf, binary.BigEndian, &mp.Type)
	// 根据Length的值,创建一个字节切片,用于存储Data字段
	mp.Data = make([]byte, mp.Length)
	binary.Read(buf, binary.BigEndian, &mp.Data)
	// 返回解码后的结构体
	return mp
}

使用示例

/ 服务端的示例代码
package main

import (
    "fmt"
    "log"
    "net"
)

// 定义一个处理连接的函数,接收一个net.TCPConn类型的参数
func handleConn(conn *net.TCPConn) {
    // 延迟关闭连接
    defer conn.Close()
    // 创建一个缓冲区,用于存储接收到的数据
    buf := make([]byte, 1024)
    // 循环读取数据
    for {
        // 从连接中读取数据,返回读取的字节数和错误
        n, err := conn.Read(buf)
        // 如果错误不为空,打印错误并退出循环
        if err != nil {
            log.Println(err)
            break
        }
        // 如果读取的字节数为0,说明连接已经关闭,退出循环
        if n == 0 {
            log.Println("connection closed")
            break
        }
        // 打印接收到的数据
        fmt.Println("received data:", string(buf[:n]))
        // 调用解码函数,将字节切片转换为自定义协议的结构体
        mp := Decode(buf[:n])
        // 打印解码后的结构体
        fmt.Printf("decoded data: %+v\n", mp)
        // 根据消息的类型,做不同的处理
        switch mp.Type {
        case 1: // 文本消息
            // 回复一个文本消息,内容为"Hello"
            reply := &MyProtocol{
                Length: 5,
                Type:   1,
                Data:   []byte("Hello"),
            }
            // 调用编码函数,将自定义协议的结构体转换为字节切片
            data := Encode(reply)
            // 将字节切片写入连接,返回写入的字节数和错误
            n, err = conn.Write(data)
            // 如果错误不为空,打印错误并退出循环
            if err != nil {
                log.Println(err)
                break
            }
            // 打印写入的字节数
            fmt.Println("sent bytes:", n)
        case 2: // 图片消息
            // 回复一个图片消息,内容为一张图片的字节切片
            reply := &MyProtocol{
                Length: len(imageBytes),
                Type:   2,
                Data:   imageBytes,
            }
            // 调用编码函数,将自定义协议的结构体转换为字节切片
            data := Encode(reply)
            // 将字节切片写入连接,返回写入的字节数和错误
            n, err = conn.Write(data)
            // 如果错误不为空,打印错误并退出循环
            if err != nil {
                log.Println(err)
                break
            }
            // 打印写入的字节数
            fmt.Println("sent bytes:", n)
        case 3: // 音频消息
            // 回复一个音频消息,内容为一段音频的字节切片
            reply := &MyProtocol{
                Length: len(audioBytes),
                Type:   3,
                Data:   audioBytes,
            }
            // 调用编码函数,将自定义协议的结构体转换为字节切片
            data := Encode(reply)
            // 将字节切片写入连接,返回写入的字节数和错误
            n, err = conn.Write(data)
            // 如果错误不为空,打印错误并退出循环
            if err != nil {
                log.Println(err)
                break
            }
            // 打印写入的字节数
            fmt.Println("sent bytes:", n)
        default: // 未知消息
            // 回复一个文本消息,内容为"Unknown"
            reply := &MyProtocol{
                Length: 7,
                Type:   1
                Data:   []byte("Unknown"),
            }
            // 调用编码函数,将自定义协议的结构体转换为字节切片
            data := Encode(reply)
            // 将字节切片写入连接,返回写入的字节数和错误
            n, err = conn.Write(data)
            // 如果错误不为空,打印错误并退出循环
            if err != nil {
                log.Println(err)
                break
            }
            // 打印写入的字节数
            fmt.Println("sent bytes:", n)
        }
    }
}

func main() {
    // 定义一个服务端的地址,包含IP和端口
    addr := &net.TCPAddr{
        IP:   net.ParseIP("127.0.0.1"),
        Port: 8080,
    }
    // 使用net包中的ListenTCP函数,监听服务端的地址,返回一个net.TCPListener类型的对象和错误
    listener, err := net.ListenTCP("tcp", addr)
    // 如果错误不为空,打印错误并退出程序
    if err != nil {
        log.Fatal(err)
    }
    // 延迟关闭监听器
    defer listener.Close()
    // 打印服务端的地址
    fmt.Println("server listening on:", addr)
    // 循环接收客户端的请求
    for {
        // 使用net.TCPListener的AcceptTCP方法,接收客户端的连接,返回一个net.TCPConn类型的对象和错误
        conn, err := listener.AcceptTCP()
        // 如果错误不为空,打印错误并继续循环
        if err != nil {
            log.Println(err)
            continue
        }
        // 打印客户端的地址
        fmt.Println("client connected from:", conn.RemoteAddr())
        // 创建一个goroutine,调用处理连接的函数,传入连接对象作为参数
        go handleConn(conn)
    }
}

简单协议

代码

// MyProtocol 定义一个自定义协议的结构体,包含消息的长度、类型和内容
type MyProtocol struct {
	Length int32  // 消息的长度,用4个字节表示
	Data   []byte // 消息的内容,用字节切片表示,长度由Length决定
}

func (p *MyProtocol) SetData(data []byte) {
	p.Length = int32(len(data))
	p.Data = data
}

// Encode 定义一个编码函数,将自定义协议的结构体转换为字节切片,用于发送数据
func Encode(mp *MyProtocol) []byte {
	// 创建一个缓冲区,用于存储编码后的数据
	buf := bytes.NewBuffer([]byte{})
	// 使用encoding/binary包中的Write函数,按照大端字节序,将结构体中的字段写入缓冲区
	_ = binary.Write(buf, binary.BigEndian, mp.Length)
	_ = binary.Write(buf, binary.BigEndian, mp.Data)
	// 返回缓冲区中的字节切片
	return buf.Bytes()
}

// Decode 定义一个解码函数,将字节切片转换为自定义协议的结构体,用于接收数据
func Decode(data []byte) *MyProtocol {
	// 创建一个缓冲区,用于存储解码后的数据
	buf := bytes.NewBuffer(data)
	// 创建一个自定义协议的结构体,用于存储解码后的字段
	mp := &MyProtocol{}
	// 使用encoding/binary包中的Read函数,按照大端字节序,从缓冲区中读取字段到结构体中
	_ = binary.Read(buf, binary.BigEndian, &mp.Length)
	// 根据Length的值,创建一个字节切片,用于存储Data字段
	mp.Data = make([]byte, mp.Length)
	_ = binary.Read(buf, binary.BigEndian, &mp.Data)
	// 返回解码后的结构体
	return mp
}


func TestMyProtocol(t *testing.T) {
    m := MyProtocol{}
    m.SetData([]byte("HelloWorld"))
    encode := Encode(&m)
    result := Decode(encode)
    fmt.Println(string(result.Data)) // HelloWorld
}