一个用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服务端、客户端编码完成。
上一篇: netty~构建一个基于netty的客户端和服务端
下一篇: 一只破瓢伸到我的面前