欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

一个用golang编写的TCP服务端、客户端使用例子

程序员文章站 2022-06-06 12:27:51
...

使用golang进行socket编程,还是挺方便,我参考了一些网上文章和阅读了一些开源代码,也跟着学习如何进行golang的socket编程,这里只编写简单的服务端、客户端,抛砖引玉。

首先定义通讯协议,我使用protobuf,如何安装protobuf?可以参考我之前的文章:

https://mp.csdn.net/postedit/81983072

  • 准备工作

定义协议,首先需要编写protobuf的协议文件:

syntax = "proto3";

package protocol;

message EchoMsg {
    uint32 cmd = 1;
    uint64 UID = 2;
    string msg = 3;
}

然后使用protoc命令编译为go代码:

protoc ./* --go_out=./

另外,在发送字节流的时候,我在EchoMsg之前,加上了2个字节的长度字段,表示整个数据包的长度,这里还进行了大小端的处理,后面代码会体现。

  • 服务端编码

服务端开启侦听后,则进入for循环来等待客户端连接,如有客户端连接,则启动一个goroutine来处理该连接的业务逻辑。在从socket对象读取数据时,首先读取2个字节的长度字段,然后再根据长度读取剩余的字节,把数据读出来后,使用protobuf解码。完整代码如下:

package main

import (
	"alwaysgo/sockettest/server/protocol"
	"encoding/binary"
	"encoding/json"
	"io"
	"net"

	"github.com/golang/protobuf/proto"

	"github.com/astaxie/beego/logs"
)

// beego 日志配置结构体
type LoggerConfig struct {
	FileName            string `json:"filename"`
	Level               int    `json:"level"`
	Maxlines            int    `json:"maxlines"`
	Maxsize             int    `json:"maxsize"`
	Daily               bool   `json:"daily"`
	Maxdays             int    `json:"maxdays"`
	Rotate              bool   `json:"rotate"`
	Perm                string `json:"perm"`
	RotatePerm          string `json:"rotateperm"`
	EnableFuncCallDepth bool   `json:"-"` // 输出文件名和行号
	LogFuncCallDepth    int    `json:"-"` // 函数调用层级
}

func main() {
	var logCfg = LoggerConfig{
		FileName:            "./daily.log",
		Level:               7,
		EnableFuncCallDepth: true,
		LogFuncCallDepth:    3,
		RotatePerm:          "777",
		Perm:                "777",
	}

	// 设置beego log库的配置
	b, _ := json.Marshal(&logCfg)
	logs.SetLogger(logs.AdapterFile, string(b))
	logs.EnableFuncCallDepth(logCfg.EnableFuncCallDepth)
	logs.SetLogFuncCallDepth(logCfg.LogFuncCallDepth)

	logs.Info("start server...")

	server, err := net.Listen("tcp", "0.0.0.0:10000")
	if err != nil {
		logs.Error(err)
		panic(err)
	}

	serverLoop(server)
}

func serverLoop(s net.Listener) {
	for {
		conn, err := s.Accept()
		if err != nil {
			logs.Error(err)
		}
		addr := conn.RemoteAddr()
		logs.Info("client connect-->" + addr.String())
		// 开启goroutine处理该连接业务
		go clientSessionLoop(conn)
	}
}

func clientSessionLoop(conn net.Conn) {
	for {
		// 读取包长度
		var b [2]byte
		_, err := conn.Read(b[:])
		if err != nil {
			logs.Error(conn.RemoteAddr().String()+":connection error ", err)
			// 出错,退出
			return
		}

		// 大端转换
		length := binary.BigEndian.Uint16(b[:])
		buf := make([]byte, length-2) // 长度字段已读取,这里需减去
		if _, err = io.ReadFull(conn, buf); err != nil {
			logs.Error(conn.RemoteAddr().String() + ":connection error")
			// 出错,退出
			return
		}

		var msg protocol.EchoMsg
		// 解码,打印
		if err = proto.Unmarshal(buf, &msg); err != nil {
			logs.Error("data error!!-->" + conn.RemoteAddr().String() + ", " + err.Error())
		}
		logs.Info("Received message from " + conn.RemoteAddr().String() + ", " + msg.String())
	}
}
  • 客户端编码

客户端先生成一个随机数,表示客户端的身份,然后生成EchoMsg对象,赋值后使用protobuf编码、发送,完整的代码如下:

package main

import (
	"alwaysgo/sockettest/client/protocol"
	"encoding/binary"
	"encoding/json"
	"fmt"
	"math/rand"
	"net"
	"time"

	"github.com/golang/protobuf/proto"

	"github.com/astaxie/beego/logs"
)

// beego 日志配置结构体
type LoggerConfig struct {
	FileName            string `json:"filename"`
	Level               int    `json:"level"`    // 日志保存的时候的级别,默认是 Trace 级别
	Maxlines            int    `json:"maxlines"` // 每个文件保存的最大行数,默认值 1000000
	Maxsize             int    `json:"maxsize"`  // 每个文件保存的最大尺寸,默认值是 1 << 28, //256 MB
	Daily               bool   `json:"daily"`    // 是否按照每天 logrotate,默认是 true
	Maxdays             int    `json:"maxdays"`  // 文件最多保存多少天,默认保存 7 天
	Rotate              bool   `json:"rotate"`   // 是否开启 logrotate,默认是 true
	Perm                string `json:"perm"`     // 日志文件权限
	RotatePerm          string `json:"rotateperm"`
	EnableFuncCallDepth bool   `json:"-"` // 输出文件名和行号
	LogFuncCallDepth    int    `json:"-"` // 函数调用层级
}

func main() {
	var logCfg = LoggerConfig{
		FileName:            "./daily.log",
		Level:               7,
		EnableFuncCallDepth: true,
		LogFuncCallDepth:    3,
		RotatePerm:          "777",
		Perm:                "777",
	}

	// 设置beego log库的配置
	b, _ := json.Marshal(&logCfg)
	logs.SetLogger(logs.AdapterFile, string(b))
	logs.EnableFuncCallDepth(logCfg.EnableFuncCallDepth)
	logs.SetLogFuncCallDepth(logCfg.LogFuncCallDepth)

	logs.Info("start client...")

	rand.Seed(time.Now().UnixNano())
	uid := rand.Uint64()

	conn, err := net.Dial("tcp", "0.0.0.0:10000")
	if err != nil {
		logs.Error(err)
		panic(err)
	}

	for {
		msg := protocol.EchoMsg{
			Cmd: 20000,
			UID: uid,
			Msg: fmt.Sprintf("hello, this is %d", uid),
		}

		b, err = proto.Marshal(&msg)
		if err != nil {
			logs.Error(err)
			panic(err)
		}

		buf := make([]byte, 2+len(b))
		binary.BigEndian.PutUint16(buf[:2], uint16(2+len(b)))
		copy(buf[2:], b[:])

		n, err := conn.Write(buf)
		if err != nil {
			logs.Error(err)
		}
		logs.Info(uid, " send ", n, " data...")
		time.Sleep(1 * time.Second)
	}
}

至此,一个简单的TCP服务端、客户端编码完成。