go语言 tcp封包LTV格式

本文深入探讨了如何在Go语言中实现TCP封包的Length-Type-Value(LTV)格式,详细解释了长度字段、类型字段和值字段的设计与处理,包括封包的发送和接收过程,以及在实际网络通信中的应用和优化技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

package main

import (
	"encoding/binary"
	"encoding/json"
	"fmt"
	"io"
	"net"
)

type REQ struct {
	Code    int64  //
	Content string // 发送的内容
}

type RESP struct {
	Content string // 发送的内容
	Code    int64  //
}

func RecvPck(conn net.Conn) (*REQ, error) {
	var sizeBuffer = make([]byte, 2)
	_, err := io.ReadFull(conn, sizeBuffer)
	if err != nil {
		fmt.Println("read head err")
		return nil, err
	}
	// 用大端格式读取Size
	size := binary.BigEndian.Uint16(sizeBuffer) //L
	fmt.Printf("L=%d\n", size)

	// 分配包体大小
	body := make([]byte, size)

	// 读取包体数据
	_, err = io.ReadFull(conn, body) //body = T+V
	if err != nil {
		fmt.Println("read body err")
		return nil, err
	}

	T := binary.LittleEndian.Uint16(body[:2])
	fmt.Printf("T=%d\n", T)

	req := &REQ{}
	err = json.Unmarshal(body[2:], req)
	if err != nil {
		return nil, err
	}
	fmt.Printf("V=%+v\n", req)
	return req, nil
}

func SendPck(conn net.Conn, T uint16, req string) error {
	resp := &RESP{
		Code:    int64(T),
		Content: req,
	}

	json_content, _ := json.Marshal(resp)

	L := len(string(json_content)) + 2
	fmt.Printf("send L=%d\n", L)
	//T := 1
	buffer := make([]byte, L+2)

	// 将包体长度写入缓冲
	binary.BigEndian.PutUint16(buffer, uint16(L))

	tmp := make([]byte, 2)
	binary.LittleEndian.PutUint16(tmp, T)

	copy(buffer[2:4], tmp)
	// 将包体数据写入缓冲
	copy(buffer[2+2:], json_content)

	// 将数据写入Socket
	if _, err := conn.Write(buffer); err != nil {
		return err
	}
	return nil
}

//LTV格式收发包
func process(conn net.Conn) {
	defer conn.Close() // 关闭连接
	for {
		req, err := RecvPck(conn)
		if err != nil && err == io.EOF {
			fmt.Printf("client close %s %d\n", conn.RemoteAddr().(*net.TCPAddr).IP,
				conn.RemoteAddr().(*net.TCPAddr).Port)
			break
		}
		err = SendPck(conn, 2, req.Content)
		//conn.Write([]byte(recvStr)) // 发送数据
	}
}

func main() {

	listen, err := net.Listen("tcp", "127.0.0.1:20000")
	if err != nil {
		fmt.Println("listen failed, err:", err)
		return
	}
	fmt.Println("listen on 127.0.0.1:20000")
	for {
		conn, err := listen.Accept() // 建立连接
		if err != nil {
			fmt.Println("accept failed, err:", err)
			continue
		}
		fmt.Printf("new client %s %d\n", conn.RemoteAddr().(*net.TCPAddr).IP,
			conn.RemoteAddr().(*net.TCPAddr).Port)

		go process(conn) // 启动一个goroutine处理连接
	}
}

package main

import (
	"bufio"
	"encoding/binary"
	"encoding/json"
	"fmt"
	"io"
	"net"
	"os"
	"strings"
)

type REQ struct {
	Code    int64  //
	Content string // 发送的内容
}

type RESP struct {
	Content string // 发送的内容
	Code    int64  //
}

func RecvPck(conn net.Conn) (*RESP, error) {
	var sizeBuffer = make([]byte, 2)
	_, err := io.ReadFull(conn, sizeBuffer)
	if err != nil {
		fmt.Println("read head err")
		return nil, err
	}
	// 用大端格式读取Size
	size := binary.BigEndian.Uint16(sizeBuffer) //L
	fmt.Printf("L=%d\n", size)

	// 分配包体大小
	body := make([]byte, size)

	// 读取包体数据
	_, err = io.ReadFull(conn, body) //body = T+V
	if err != nil {
		fmt.Println("read body err")
		return nil, err
	}

	T := binary.LittleEndian.Uint16(body[:2])
	fmt.Printf("T=%d\n", T)

	req := &RESP{}
	err = json.Unmarshal(body[2:], req)
	if err != nil {
		return nil, err
	}
	fmt.Printf("V=%+v\n", req)
	return req, nil
}

func SendPck(conn net.Conn, T uint16, msg string) error {
	resq := &REQ{
		Code:    int64(T),
		Content: msg,
	}
	json_content, _ := json.Marshal(resq)

	L := len(string(json_content)) + 2
	fmt.Printf("send L=%d\n", L)
	//T := 1
	buffer := make([]byte, L+2)

	// 将包体长度写入缓冲
	binary.BigEndian.PutUint16(buffer, uint16(L))

	tmp := make([]byte, 2)
	binary.LittleEndian.PutUint16(tmp, T)

	copy(buffer[2:4], tmp)
	// 将包体数据写入缓冲
	copy(buffer[2+2:], json_content)

	// 将数据写入Socket
	if _, err := conn.Write(buffer); err != nil {
		return err
	}
	return nil
}

//LTV格式收发包
func process(conn net.Conn) {
	defer conn.Close() // 关闭连接
	for {
		req, err := RecvPck(conn)
		if err != nil && err == io.EOF {
			fmt.Printf("server close %s %d\n", conn.RemoteAddr().(*net.TCPAddr).IP,
				conn.RemoteAddr().(*net.TCPAddr).Port)
			break
		}
		fmt.Printf("%+v\n", req)
	}
}

func main() {
	conn, err := net.Dial("tcp", "127.0.0.1:20000")
	if err != nil {
		fmt.Println("conn failed, err:", err)
		return
	}
	fmt.Println("connect ok")

	go process(conn) // 启动一个goroutine处理连接

	for {
		// 从键盘读取数据,直到读到换行为止
		text, err := bufio.NewReader(os.Stdin).ReadString('\n')

		if err != nil {
			break
		}

		// 去掉空白字符
		text = strings.TrimSpace(text)
		err = SendPck(conn, 1, text)
	}
}

/*
gcc -o client client.c
*/

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>

int tcp_client(const char*ip, int port);

int main() 
{
    int fd = tcp_client("127.0.0.1", 20000);
    assert(fd);

    //LTV
    char json_str[20] = "{\"Content\":\"qwe\"}";
    unsigned short type = 1;
    unsigned short len = sizeof(type) + strlen(json_str);

    char *buf = (char*)malloc(len + 2);

    *(unsigned short*)buf = htons(len);//L是大端
    *(unsigned short*)(buf+2) = type;
    memcpy(buf+2+2, json_str, strlen(json_str));

    printf("send body len=%d\n", len);

    write(fd, buf, len + 2);

    memset(buf, 0, len + 2);
    char tmp[3];
    read(fd, tmp, 2);
    len = *(unsigned short*)tmp;
    len = ntohs(len);
    printf("recv body len=%u\n", len);
    memset(buf, 0, len + 2);
    read(fd, buf, len);
    buf[len] = '\0';
    type = *(unsigned short*)buf;
    printf("L=%d,T=%d,V=%s\n", len, type, buf+2);

    //free(buf);
    close(fd);
}

int tcp_client(const char*ip, int port)
{
    int sock;
	struct sockaddr_in addr;
	memset(&addr, 0, sizeof addr);
	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = inet_addr(ip);
	addr.sin_port = htons(port);

	sock = socket(AF_INET, SOCK_STREAM, 0);

    socklen_t len = sizeof addr;
	
	int r = connect(sock, (struct sockaddr*)&addr, len);
	if(r==-1)
    {
        return 0;
    }
    else
    {
        return sock;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值