您的位置:首页 > 理论基础 > 计算机网络

leaf开源服务器第四节-分析源码实现模拟TCP客户端

2017-11-16 11:42 639 查看
leaf开源游戏服务器源码
leaf开源服务器第一节-分析项目结构
leaf开源服务器第二节-分析之配置文件说明及服务器运行
leaf开源服务器第三节-分析TCP消息通信之增加Glog日志(1)
      大家好,我是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

我是彬哥,下节见。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐