leaf开源服务器第四节-分析源码实现模拟TCP客户端
来源:互联网 发布:詹姆斯生涯总数据 编辑:程序博客网 时间:2024/06/11 13:11
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
我是彬哥,下节见。