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

这篇具有很好参考价值的文章主要介绍了Golang实现之TCP长连接-------服务端和客户端。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

帧头

帧长度(头至尾)

帧类型

帧数据

帧尾

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端 实现代码文章来源地址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模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 服务端和客户端通信-TCP(含完整源代码)

    目录 简单TCP通信实验 分析 1、套接字类型 2、socket编程步骤 3、socket编程实现具体思路 实验结果截图 程序代码 实验设备:     目标系统:windows 软件工具:vs2022/VC6/dev 实验要求: 完成TCP服务端和客户端的程序编写; 实现简单字符串的收发功能。 需附上代码及运行结果截图

    2024年02月07日
    浏览(76)
  • SpringBoot搭建Netty+Socket+Tcp服务端和客户端

    yml配置:    完成,启动项目即可自动监听对应端口 这里为了测试,写了Main方法,可以参考服务端,配置启动类 ,实现跟随项目启动   ......想写就参考服务端...... 有测试的,,,但是忘记截图了................

    2024年02月15日
    浏览(54)
  • C++实现WebSocket通信(服务端和客户端)

    天行健,君子以自强不息;地势坤,君子以厚德载物。 每个人都有惰性,但不断学习是好好生活的根本,共勉! 文章均为学习整理笔记,分享记录为主,如有错误请指正,共同学习进步。 这里单纯是个人总结,如需更官方更准确的websocket介绍可百度 websocket是一种即时通讯协

    2024年02月09日
    浏览(48)
  • Socket实例,实现多个客户端连接同一个服务端代码&TCP网络编程 ServerSocket和Socket实现多客户端聊天

    Java socket(套接字)通常也称作\\\"套接字\\\",用于描述ip地址和端口,是一个通信链的句柄。应用程序通常通过\\\"套接字\\\"向网络发出请求或者应答网络请求。 使用socket实现多个客户端和同一客户端通讯;首先客户端连接服务端发送一条消息,服务端接收到消息后进行处理,完成后再

    2024年02月12日
    浏览(76)
  • (二) 用QWebSocket 实现服务端和客户端(详细代码直接使用)

    目录 前言 一、服务器的代码: 1、服务器的思路 2、具体服务器的代码示例 二、客户端的代码: 1、客户端的思路(和服务器类似) 2、具体客户端的代码示例 前言         要是想了解QWebSocket的详细知识,还得移步到上一篇文章: WebSocket 详解,以及用QWebSocket 实现服务端

    2024年01月20日
    浏览(44)
  • 【高并发网络通信架构】2.引入多线程实现多客户端连接的tcp服务端

    目录 一,往期文章 二,代码实现 关键代码 完整代码 运行效果 【高并发网络通信架构】1.Linux下实现单客户连接的tcp服务端 因为accept是阻塞等待客户端连接,当客户端连接成功后才会执行accept后面的代码,所以为实现多个客户端连接,第一步是将accept放在master循环里。 rec

    2024年02月13日
    浏览(47)
  • WebSocket 详解,以及用QWebSocket 实现服务端和客户端(含代码例子)

    目录 前言: 1、WebSocket 诞生背景 2、WebSocket的特点: 3、 WebSocket 简介 4、WebSocket 优点 5、QWebSocket通讯—客户端: 6、QWebSocket通讯—服务端: 前言:         要是对WebSocket 的基本知识都了解了,可以直接移步,实际如何使用这个类 (二) 用QWebSocket 实现服务端和客户端(

    2024年02月16日
    浏览(49)
  • 基于STM32F103,利用W5500芯片实现TCP客户端连接TCP服务器的实践

    尊敬的读者,您好!在这篇文章中,我们将一起深入了解如何使用STM32F103和W5500芯片,实现TCP客户端连接到TCP服务器的过程。在详细的步骤中,我们不仅会给出相关的理论介绍,同时也会提供实战代码以供大家参考和学习。希望大家在阅读完这篇文章后,能够有所收获。 实战

    2024年02月11日
    浏览(38)
  • (一)WebSocket 详解,以及用QWebSocket 实现服务端和客户端(含代码例子)

    目录 前言: 1、WebSocket 诞生背景 2、WebSocket的特点: 3、 WebSocket 简介 4、WebSocket 优点 5、QWebSocket通讯—客户端: 6、QWebSocket通讯—服务端: 前言:         要是对WebSocket 的基本知识都了解了,可以直接移步,实际如何使用这个类 (二) 用QWebSocket 实现服务端和客户端(

    2024年02月03日
    浏览(53)
  • SpringBoot+CAS整合服务端和客户端实现SSO单点登录与登出快速入门上手

    教学讲解视频地址:视频地址 因为CAS支持HTTP请求访问,而我们是快速入门上手视频,所以这期教程就不教大家如何配置HTTPS了,如果需要使用HTTPS,可以参考其他博客去云服务器申请证书或者使用JDK自行生成一个证书。 下载CAS Server(直接下载压缩包就可以) 这里我们用的是

    2024年02月02日
    浏览(68)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包