大家好,我是Golang语言社区(WwW.Golang.Ltd)的站长,今天继续来给大家分析leaf游戏服务器源码,来实现模拟客户端;
这节我们主要是分析源码实现模拟客户端,因为leaf的作者已经把客户端的结构及收发函数已经实现,所以我们参照源码实现模拟
客户端是很简单的
原作者实现的客户端代码级逻辑处理函数
找到了原作者的客户端实现的逻辑处理及基本结构后,直接copy代码,运行就可以;模拟客户单代码如下
-
package main
-
-
import (
-
"glog-master" // 此包:Golang语言社区资源站下载,www.Golang.MoM
-
"log"
-
"math"
-
"net"
-
"sync"
-
"time"
-
)
-
-
func init() {
-
-
// 初始化 日志系统
-
flag.Set("alsologtostderr", "true") // 日志写入文件的同时,输出到stderr
-
flag.Set("log_dir", "./log") // 日志文件保存目录
-
flag.Set("v", "3") // 配置V输出的等级。
-
flag.Parse()
-
-
return
-
}
-
-
func main() {
-
// 调用函数
-
-
return
-
}
-
-
//-----------------------------------------------------------------------------
-
-
type ConnSet map[net.Conn]struct{}
-
-
type TCPConn struct {
-
sync.Mutex
-
conn net.Conn
-
writeChan chan []byte
-
closeFlag bool
-
msgParser *MsgParser
-
}
-
-
// --------------
-
// | len | data |
-
// --------------
-
// 数据结构信息
-
type MsgParser struct {
-
lenMsgLen int
-
minMsgLen uint32
-
maxMsgLen uint32
-
littleEndian bool
-
}
-
-
// 接口信息
-
type Agent interface {
-
Run()
-
OnClose()
-
}
-
-
// 客户端结构
-
type TCPClient struct {
-
sync.Mutex
-
Addr string
-
ConnNum int
-
ConnectInterval time.Duration
-
PendingWriteNum int
-
AutoReconnect bool
-
NewAgent func(*TCPConn) Agent
-
conns ConnSet
-
wg sync.WaitGroup
-
closeFlag bool
-
-
// msg parser
-
LenMsgLen int
-
MinMsgLen uint32
-
MaxMsgLen uint32
-
LittleEndian bool
-
msgParser *MsgParser
-
}
-
-
func NewMsgParser() *MsgParser {
-
p := new(MsgParser)
-
p.lenMsgLen = 2
-
p.minMsgLen = 1
-
p.maxMsgLen = 4096
-
p.littleEndian = false
-
-
return p
-
}
-
-
func (client *TCPClient) Start() {
-
client.init()
-
-
for i := 0; i < client.ConnNum; i++ {
-
client.wg.Add(1)
-
go client.connect()
-
}
-
}
-
-
func (client *TCPClient) init() {
-
client.Lock()
-
defer client.Unlock()
-
-
if client.ConnNum <= 0 {
-
client.ConnNum = 1
-
glog.Info("invalid ConnNum, reset to %v", client.ConnNum)
-
}
-
if client.ConnectInterval <= 0 {
-
client.ConnectInterval = 3 * time.Second
-
glog.Info("invalid ConnectInterval, reset to %v", client.ConnectInterval)
-
}
-
if client.PendingWriteNum <= 0 {
-
client.PendingWriteNum = 100
-
glog.Info("invalid PendingWriteNum, reset to %v", client.PendingWriteNum)
-
}
-
if client.NewAgent == nil {
-
log.Fatal("NewAgent must not be nil")
-
}
-
if client.conns != nil {
-
log.Fatal("client is running")
-
}
-
-
client.conns = make(ConnSet)
-
client.closeFlag = false
-
-
// msg parser
-
msgParser := NewMsgParser()
-
msgParser.SetMsgLen(client.LenMsgLen, client.MinMsgLen, client.MaxMsgLen)
-
msgParser.SetByteOrder(client.LittleEndian)
-
client.msgParser = msgParser
-
}
-
-
// It's dangerous to call the method on reading or writing
-
func (p *MsgParser) SetByteOrder(littleEndian bool) {
-
p.littleEndian = littleEndian
-
}
-
-
// It's dangerous to call the method on reading or writing
-
func (p *MsgParser) SetMsgLen(lenMsgLen int, minMsgLen uint32, maxMsgLen uint32) {
-
if lenMsgLen == 1 || lenMsgLen == 2 || lenMsgLen == 4 {
-
p.lenMsgLen = lenMsgLen
-
}
-
if minMsgLen != 0 {
-
p.minMsgLen = minMsgLen
-
}
-
if maxMsgLen != 0 {
-
p.maxMsgLen = maxMsgLen
-
}
-
-
var max uint32
-
switch p.lenMsgLen {
-
case 1:
-
max = math.MaxUint8
-
case 2:
-
max = math.MaxUint16
-
case 4:
-
max = math.MaxUint32
-
}
-
if p.minMsgLen > max {
-
p.minMsgLen = max
-
}
-
if p.maxMsgLen > max {
-
p.maxMsgLen = max
-
}
-
}
-
-
func (client *TCPClient) dial() net.Conn {
-
for {
-
conn, err := net.Dial("tcp", client.Addr)
-
if err == nil || client.closeFlag {
-
return conn
-
}
-
-
glog.Info("connect to %v error: %v", client.Addr, err)
-
time.Sleep(client.ConnectInterval)
-
continue
-
}
-
}
-
-
func newTCPConn(conn net.Conn, pendingWriteNum int, msgParser *MsgParser) *TCPConn {
-
tcpConn := new(TCPConn)
-
tcpConn.conn = conn
-
tcpConn.writeChan = make(chan []byte, pendingWriteNum)
-
tcpConn.msgParser = msgParser
-
-
go func() {
-
for b := range tcpConn.writeChan {
-
if b == nil {
-
break
-
}
-
-
_, err := conn.Write(b)
-
if err != nil {
-
break
-
}
-
}
-
-
conn.Close()
-
tcpConn.Lock()
-
tcpConn.closeFlag = true
-
tcpConn.Unlock()
-
}()
-
-
return tcpConn
-
}
-
-
func (client *TCPClient) connect() {
-
defer client.wg.Done()
-
-
reconnect:
-
conn := client.dial()
-
if conn == nil {
-
return
-
}
-
-
client.Lock()
-
if client.closeFlag {
-
client.Unlock()
-
conn.Close()
-
return
-
}
-
client.conns[conn] = struct{}{}
-
client.Unlock()
-
-
tcpConn := newTCPConn(conn, client.PendingWriteNum, client.msgParser)
-
agent := client.NewAgent(tcpConn)
-
agent.Run()
-
-
// cleanup
-
tcpConn.Close()
-
client.Lock()
-
delete(client.conns, conn)
-
client.Unlock()
-
agent.OnClose()
-
-
if client.AutoReconnect {
-
time.Sleep(client.ConnectInterval)
-
goto reconnect
-
}
-
}
-
-
func (tcpConn *TCPConn) Close() {
-
tcpConn.Lock()
-
defer tcpConn.Unlock()
-
if tcpConn.closeFlag {
-
return
-
}
-
-
tcpConn.doWrite(nil)
-
tcpConn.closeFlag = true
-
}
-
-
func (tcpConn *TCPConn) doWrite(b []byte) {
-
if len(tcpConn.writeChan) == cap(tcpConn.writeChan) {
-
glog.Info("close conn: channel full")
-
tcpConn.doDestroy()
-
return
-
}
-
-
tcpConn.writeChan <- b
-
}
-
-
func (tcpConn *TCPConn) doDestroy() {
-
tcpConn.conn.(*net.TCPConn).SetLinger(0)
-
tcpConn.conn.Close()
-
-
if !tcpConn.closeFlag {
-
close(tcpConn.writeChan)
-
tcpConn.closeFlag = true
-
}
-
}
-
-
func (client *TCPClient) Close() {
-
client.Lock()
-
client.closeFlag = true
-
for conn := range client.conns {
-
conn.Close()
-
}
-
client.conns = nil
-
client.Unlock()
-
-
client.wg.Wait()
-
}
-
复制代码
模拟客户端代码,简单的修改;增加了第三方日志glog库(ps:日志库可以去GITHUB或者去我们社区资源站www.Golang.MoM)
我们的模拟客户端就写好了,后面我们会在这个代码的基础上进行模拟消息的发送;
最后给大家总结下,
1 开源框架的目录结构我们首先要熟悉下,原作者肯定比我们使用者考虑的方面多;所以我们多数会找到;所以首先相信原作者
2 每个人开发都有自己的风格,不一定拘泥一个定式;所以大家可以按照自己的实现方式去实现逻辑;写了代码越多你才会越精炼;切忌纸上谈兵。
3 做项目尽量把简单的事情做”复杂“了,这个对后面有益无害。
公众账号:Golang语言社区
社区微博:Golang语言社区
社区网址:www.Golang.Ltd
社区资源:www.Golang.MoM
社区直播:www.huya.com/golang
社区教育:www.NewTon.TV
我是彬哥,下节见。
|