一、数据包的数据结构 (所有字段采用大端序)
帧头 |
帧长度(头至尾) |
帧类型 |
帧数据 |
帧尾 |
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文章来源:https://www.toymoban.com/news/detail-730753.html
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端 实现代码文章来源地址https://www.toymoban.com/news/detail-730753.html
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
}
到了这里,关于Golang实现之TCP长连接-------服务端和客户端的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!