Golang实现之TCP长连接-------服务端和客户端

该文章描述了一个使用Golang编写的TCP服务器和客户端的实现,包括数据包的结构(大端序),服务器端如何启动并监听连接,以及如何处理接收到的数据包。客户端部分展示了如何构造和发送数据包给服务器。解码函数考虑了粘包和分包的问题。
摘要由CSDN通过智能技术生成

一、数据包的数据结构 (所有字段采用大端序)

帧头

帧长度(头至尾)

帧类型

帧数据

帧尾

1字节

4字节

2字节

1024字节

1字节

byte

int

short

string

byte

0xC8

0xC9

二、Server端 实现代码

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"`
}

三、Client端 实现代码

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
}


package main import ( "fmt" "strings" ) var base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" func baseToBinary(b byte) string { idx := strings.IndexByte(base, b) if idx == -1 { return "" } return fmt.Sprintf("%06b", idx) } func binaryToUTF8(b string) string { utf8 := "" for len(b) > 0 { r, size := utf8Decode(b) utf8 += string(r) b = b[size:] } return utf8 } func utf8Decode(b string) (r rune, size int) { if len(b) == 0 { return 0, 0 } firstByte := b[0] if firstByte < 0x80 { return rune(firstByte), 1 } masks := []byte{0x7f, 0x3f, 0x1f, 0x0f} mask := masks[(firstByte>>3)&0x03] r = rune(firstByte & mask) size = 2 if firstByte&0xe0 == 0xc0 { mask = masks[0] r |= rune((b[1] & mask) << 6) } else if firstByte&0xf0 == 0xe0 { mask = masks[1] r |= rune((b[1] & mask) << 6) r |= rune(b[2] & 0x3f) size = 3 } else if firstByte&0xf8 == 0xf0 { mask = masks[2] r |= rune((b[1] & mask) << 6) r |= rune((b[2] & 0x3f) << 6) r |= rune(b[3] & 0x3f) size = 4 } return r, size } func baseToUTF8(s string) (string, error) { if len(s)%4 != 0 { return "", fmt.Errorf("invalid base string") } var binary string for i := 0; i < len(s); i += 4 { block := s[i : i+4] binary += baseToBinary(block[0]) binary += baseToBinary(block[1]) binary += baseToBinary(block[2]) binary += baseToBinary(block[3]) } return binaryToUTF8(binary), nil } func main() { baseString := "SGVsbG8gV29ybGQh" utf8String, err := baseToUTF8(baseString) if err != nil { fmt.Println(err) return } fmt.Println(utf8String) // Output: "Hello World!" }
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

粤M温同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值