大家好,我是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 我是彬哥,下节见。 |