基于Golang TCP 开发网络游戏 CLI四川麻将 - 2.Tcp通讯

本文介绍了使用Go语言的gob序列化技术定义的TransfeData结构,详细展示了数据传输结构和在Server端与Client端的TCP通信实现,包括编码、解码和心跳包发送过程。

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

项目地址

https://github.com/mangenotwork/CLI-Sichuan-Mahjong

数据传输结构定义

这里使用gob序列化数据

// entity.go

type TransfeData struct {
	Cmd enum.Command  // 指令
	Timestamp int64
	Token string // 识别客户端身份
	Data interface{} // 传输的数据
	Message string // 传输消息
	Code int // 传输code
}

func (t *TransfeData) Byte() []byte {
	var buf bytes.Buffer
	enc := gob.NewEncoder(&buf)
	err := enc.Encode(t)
	if err != nil {
		log.Fatal("encode error:", err)
	}
	return buf.Bytes()
}

func NewTransfeData(cmd enum.Command, token string, data interface{}) []byte {
	tra := &TransfeData{
		Cmd: cmd,
		Timestamp: time.Now().Unix(),
		Token: token,
		Data: data,
	}
	var buf bytes.Buffer
	enc := gob.NewEncoder(&buf)
	err := enc.Encode(tra)
	if err != nil {
		log.Fatal("encode error:", err)
	}
	return buf.Bytes()
}

func TransfeDataDecoder(data []byte) *TransfeData {
	buf := bytes.NewBuffer(data)
	dec := gob.NewDecoder(buf)
	tra := &TransfeData{}
	err := dec.Decode(&tra)
	if err != nil {
		log.Fatal("decode error:", err)
	}
	return tra
}

Server 端

// tcp
type TcpServer struct {
	Listener   *net.TCPListener
	HawkServer *net.TCPAddr
}

// 运行服务
func Run() {
	//类似于初始化套接字,绑定端口
	hawkServer, err := net.ResolveTCPAddr("tcp", "0.0.0.0:14444")
	if err != nil {
		panic(err)
	}

	//侦听
	listen, err := net.ListenTCP("tcp", hawkServer)
	utils.PanicErr(err)

	//关闭
	defer listen.Close()

	tcpServer := &TcpServer{
		Listener:   listen,
		HawkServer: hawkServer,
	}
	log.Println("start Master TCP server successful.")

	//接收请求
	for {

		//来自客户端的连接
		conn, err := tcpServer.Listener.Accept()
		if err != nil {
			log.Println("[连接失败]:", err.Error())
			continue
		}
		log.Println("[连接成功]: ", conn.RemoteAddr().String(), conn)

		go func(conn net.Conn){
			// 最大接收1024个字节
			recv := make([]byte, 1024)
			
			// 设置读取超时
			err = conn.SetReadDeadline(time.Now().Add(60*time.Second)) // timeout
			if err != nil {
				log.Println("setReadDeadline failed:", err)
			}
			for {
				// 接收数据
				n, err := conn.Read(recv)
				if err != nil{
					// 处理断开连接
					if err == io.EOF {
						log.Println(conn.RemoteAddr().String(), " 断开了连接!")
						conn.Close()
						return
					}
				}
				// 打印接收的数据
				if n > 0 && n < 1025 {
					data := entity.TransfeDataDecoder(recv[:n])
					log.Println(data)
				}
				
			}
		}(conn)
	}
}

Client端

// 重连chan
var RConn = make(chan bool)

type TcpClient struct {
	Connection *net.TCPConn
	HawkServer *net.TCPAddr
	StopChan   chan struct{} // 停止 chan
	CmdChan chan *entity.TransfeData // 来自Server数据传输chan
}

func (c *TcpClient) Send(b []byte) (int, error) {
	return c.Connection.Write(b)
}

func (c *TcpClient) Read(b []byte) (int, error) {
	return c.Connection.Read(b)
}

func (c *TcpClient) Addr() string {
	return c.Connection.RemoteAddr().String()
}

func (c *TcpClient) Close(){
	c.Connection.Close()
	RConn <- true
}

func Run(){

	//用于重连
Reconnection:
	host := "192.168.0.9:14444"
	hawkServer, err := net.ResolveTCPAddr("tcp", host)
	if err != nil {
		log.Printf("hawk server [%s] resolve error: [%s]", host, err.Error())
		time.Sleep(1 * time.Second)	//连接失败后1秒重连
		goto Reconnection
	}

	//连接服务器
	connection, err := net.DialTCP("tcp", nil, hawkServer)
	if err != nil {
		log.Printf("connect to hawk server error: [%s]", err.Error())
		time.Sleep(1 * time.Second)
		goto Reconnection
	}
	log.Println("[连接成功] 连接服务器成功")

	//创建客户端实例
	client := &TcpClient{
		Connection: connection,
		HawkServer: hawkServer,
		StopChan:   make(chan struct{}),
		CmdChan: make(chan *entity.TransfeData),
	}

	//启动接收
	go func(conn *models.TcpClient){
		for{
			recv := make([]byte, 1024)
			for {
				n, err := conn.Connection.Read(recv)
				if err != nil{
					if err == io.EOF {
						log.Println(conn.Addr(), " 断开了连接!")
						conn.Close()
						return
					}
				}
				if n > 0 && n < 1025 {
					conn.CmdChan <- entity.TransfeDataDecoder(recv[:n])
				}
			}
		}
	}(client)

	// 发送心跳
	go func(conn *models.TcpClient){
		i := 0
		heartBeatTick := time.Tick(10 * time.Second)
		for {
			select {
			case <-heartBeatTick:
				heartBeat := entity.NewTransfeData(enum.HeartPacket, "", i)
				if _, err := conn.Send(heartBeat); err != nil {
					models.RConn <- true
					return
				}
				i++
			case <-conn.StopChan:
				return
			}
		}
	}(client)
	
	// 接收来自Server端的数据
	go func(conn *TcpClient){
		for{
			select{
				case data := <- c.CmdChan:
					log.Println(data)
			}
		}
	}(conn)

	// 处理重连信号
	for {
		select {
		case a := <- models.RConn:
			log.Println("global.RConn = ", a)
			goto Reconnection
		}
	}

	//等待退出
	<-client.StopChan

}

上一篇 1.选型与结构定义

https://blog.youkuaiyun.com/Man_ge/article/details/120204207

下一篇 3. 使用grom进行Mysql存储数据

https://blog.youkuaiyun.com/Man_ge/article/details/120290472

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值