TCP long connection implemented by Golang-------server and client

1. The data structure of the data packet (all fields adopt big endian order)

frame header

Frame length (head to tail)

frame type

frame data

end of frame

1 byte

4 bytes

2 bytes

1024 bytes

1 byte

byte

int

short

string

byte

0xC8

0xC9

2. Server-side implementation code

1、main.go

func main() {

	logconfig.InitLogger()//初始化日志库,日志库实现可以查阅: 
   https://blog.csdn.net/banzhuantuqiang/article/details/131403454


	//开启tcp server
	logconfig.SugarLogger.Info("server端启动中...")
	ip := "0.0.0.0"
	port := 10302
	

	go func() {
		
		server.Start(ip, port)
	}()


	logconfig.SugarLogger.Info("server端启动完成")

	for {
		logconfig.SugarLogger.Info("server端主进程活跃")
		time.Sleep(5 * time.Second)
	}

}

2、server.go

//启动TCP服务端
func Start(ip string, port int) (bool, error) {
var ClinetConn net.Conn = nil
	//1:启用监听
	listener, err := net.Listen("tcp", ip+":"+strconv.Itoa(port))
	//连接失败处理
	if err != nil {
		logconfig.SugarLogger.Infof("启动服务失败,err:%v", err)
		return false, err
	}

	//程序退出时释放端口
	defer func() {
		listener.Close()
		logconfig.SugarLogger.Error("服务的退出")
	}()


	for {
		coon, err := listener.Accept() //2.建立连接
		//判断是代理连接还是ssh连接
		reader := bufio.NewReader(coon)
	
		
			if ClinetConn != nil {
				ClinetConn.Close()
			}
			ClinetConn = coon
			if err != nil {
				logconfig.SugarLogger.Errorf("接收客户连接失败,err:%v", err)
				continue
			}

			logconfig.SugarLogger.Infof("接收客户连接成功,%s", coon.RemoteAddr().String())
			
			//启动一个goroutine处理客户端连接
			go process(coon, reader)
	}
}

func process(coon net.Conn, reader *bufio.Reader) {
	defer func() {
		coon.Close()
		logconfig.SugarLogger.Infof("客户连接断开,%s", coon.RemoteAddr().String())
	}()
	for {
		msg, err := protocol.Decode(reader, coon)
		if err != nil {
			logconfig.SugarLogger.Infof("decode失败,err:%v", err)
			if msg.Type == 1 {
				continue
			} else {
				logconfig.SugarLogger.Errorf("客户端连接处理异常......")
				return
			}
		}
		logconfig.SugarLogger.Infof("收到客户端%s的消息:%v", coon.RemoteAddr().String(), msg)
		//TODO 解析数据和回应消息,参照下面response.go
        //这里另起一个线程去解析数据,一般是json数据
	}
}

3、protocol.go

const HEAD byte = 0xC8
const TAIL byte = 0xC9


func Encode(message Msg) ([]byte, error) {
	var pkg = new(bytes.Buffer)
	// 写入消息头
	err := binary.Write(pkg, binary.BigEndian, message.Head)
	if err != nil {
		return nil, err
	}
	// 写入长度
	err = binary.Write(pkg, binary.BigEndian, message.Length)
	if err != nil {
		return nil, err
	}
	// 写入类型
	err = binary.Write(pkg, binary.BigEndian, message.Type)
	if err != nil {
		return nil, err
	}
	err = binary.Write(pkg, binary.BigEndian, []byte(message.Data))
	if err != nil {
		return nil, err
	}
	err = binary.Write(pkg, binary.BigEndian, message.Tail)
	if err != nil {
		return nil, err
	}
	return pkg.Bytes(), nil
}


// 解码(这里考虑了粘包和分包问题)
func Decode(reader *bufio.Reader, conn net.Conn) (Msg, error) {
	//|帧头    |帧长度(头至尾)   	|帧类型  	|帧数据    	|帧尾
	//|1字节   |4字节    			|2字节   	|1024字节  	|1字节
	//|byte   |int     			|short   	|string   	|byte
	//|0xC8   |        			|        	|         	|0xC9

	for {
		headbyte, headError := reader.ReadByte()
		if headError != nil {
			return Msg{}, headError
		} else if headbyte == HEAD {
			//已读取到正确头
			break
		} else {
			logconfig.SugarLogger.Infof("读到错误字节:%v 错误的连接地址:%s", headbyte, conn.RemoteAddr().String())
		}
	}
	// 读消息长度,不移动位置
	lengthByte, _ := reader.Peek(4)
	lengthBuff := bytes.NewBuffer(lengthByte)
	var length int32
	//将长度buff转换为 int32,大端序
	err := binary.Read(lengthBuff, binary.BigEndian, &length)
	if err != nil {
		return Msg{Type: 1}, err
	}
	//如果消息体超过 4096(默认长度)
	var pack []byte
	if length > 4096 {
		pack = make([]byte, 0, int(length-1))
		readableLength := length - 1
		for {
			if readableLength < 4096 {
				slice := make([]byte, readableLength)
				_, err = reader.Read(slice)
				pack = append(pack, slice...)
				break
			}
			slice := make([]byte, int32(reader.Buffered()))
			_, err = reader.Read(slice)
			pack = append(pack, slice...)
			//更新可读长度
			readableLength = readableLength - int32(len(slice))
		}
		// buffer返回缓冲中现有的可读的字节数,2+length+1表示帧类型+数据长度+帧尾
	} else if length < 4096 && int32(reader.Buffered()) < length-1 {
		//退回已读取的帧头
		reader.UnreadByte()
		return Msg{Type: 1}, errors.New("数据长度不足")
	} else {
		// 读取剩余帧内容
		pack = make([]byte, int(length-1))
		_, err = reader.Read(pack)
		if err != nil {
			return Msg{Type: 1}, err
		}
	}

	typeBuff := bytes.NewBuffer(pack[4:6])
	var msgType int16
	msgTypeErr := binary.Read(typeBuff, binary.BigEndian, &msgType)
	if msgTypeErr != nil {
		return Msg{Type: 1}, msgTypeErr
	}
	data := string(pack[6 : len(pack)-1])
	tail := pack[len(pack)-1]
	if tail != TAIL {
		reader.UnreadByte()
		return Msg{Type: 1}, errors.New("帧尾错误,丢弃已读取的字节")
	}
	msg := Msg{Head: HEAD, Length: length, Type: msgType, Data: data, Tail: TAIL}
	return msg, nil
}

type Msg struct {
	Head   byte
	Length int32
	Type   int16
	Data   string
	Tail   byte
}

4、response.go

func tcpReturn(coon net.Conn, result *dto.Result, msgType int16) {
	marshal, _ := json.Marshal(result)//这里根据定义的消息体解析成json字符串
	msg := protocol.Msg{protocol.HEAD, 8 + int32(len(marshal)), msgType, string(marshal), protocol.TAIL}
	encode, _ := protocol.Encode(msg)
	coon.Write(encode)
	logconfig.SugarLogger.Infof("结束消息处理,tpc连接:%s,回复结果:%s", coon.RemoteAddr().String(), string(encode))
}

5、result.go

type Result struct {
	DataType string `json:"data_type"`
	// 消息标识
	CallBackKey string `json:"call_back_key"`
	// 状态码
	Status string `json:"status"`
	// 返回描述
	Message string `json:"message"`
	// 消息体
	Data interface{} `json:"data"`
}

3. Client implementation code

package main

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"net"
	"time"
)
const HEAD byte = 0xC8
const TAIL byte = 0xC9
func main() {
	
		conn, err := net.Dial("tcp", "127.0.0.1:10301")
		if err != nil {
			fmt.Println("连接服务端失败,err:", err)
			return
		}
		conn.SetReadDeadline(time.Now().Add(100 * time.Second))

		

		strJosn:="66666"//这里写自己的json字符串
		//Type 0x01  0x02.....
		message := Msg{HEAD, 8 + int32(len(strJosn)), 0x10, string(strJosn), TAIL} // 板卡ROM重置状态查询

		b, err := Encode(message)
		if err != nil {
			fmt.Println("Encode失败,err:", err)
		}
		_, error := conn.Write(b)
		if error != nil {
			fmt.Println("发送失败,err:", error)
			return
		}
		fmt.Println("发送成功...,msg:", b)
		fmt.Println("发送成功...,msg:", message)
	    parseServerResponseMesage(conn)
	
}

//服务端返回消息
func parseServerResponseMesage(coon net.Conn) {
	for {
		dataByte := make([]byte, 4096)
		n, _ := coon.Read(dataByte)
		bytes := dataByte[0:n]
		fmt.Println("收到服务端消息:", string(bytes))
	}
}


type Msg struct {
	Head   byte
	Length int32
	Type   int16
	Data   string
	Tail   byte
}

func Encode(message Msg) ([]byte, error) {
	var pkg = new(bytes.Buffer)
	// 写入消息头
	err := binary.Write(pkg, binary.BigEndian, message.Head)
	if err != nil {
		return nil, err
	}
	// 写入长度
	err = binary.Write(pkg, binary.BigEndian, message.Length)
	if err != nil {
		return nil, err
	}
	// 写入类型
	err = binary.Write(pkg, binary.BigEndian, message.Type)
	if err != nil {
		return nil, err
	}
	err = binary.Write(pkg, binary.BigEndian, []byte(message.Data))
	if err != nil {
		return nil, err
	}
	err = binary.Write(pkg, binary.BigEndian, message.Tail)
	if err != nil {
		return nil, err
	}
	return pkg.Bytes(), nil
}


Guess you like

Origin blog.csdn.net/banzhuantuqiang/article/details/131402492