leaf开源服务器第四节-分析源码实现模拟TCP客户端
2017-11-16 11:42
639 查看
这节我们主要是分析源码实现模拟客户端,因为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 我是彬哥,下节见。 |
相关文章推荐
- 基于TCP网络通信的自动升级程序源码分析-客户端连接服务器
- 基于TCP网络通信的自动升级程序源码分析-客户端请求服务器上的升级信息
- leaf开源服务器第三节-分析TCP消息通信之增加Glog日志(1)
- 基于TCP网络通信的自动升级程序源码分析-客户端连接服务器
- 基于TCP网络通信的自动升级程序源码分析-客户端连接服务器
- 基于TCP网络通信的自动升级程序源码分析-客户端请求服务器上的升级信息
- Select I/O模型来实现一个并发处理多个客户端的TCP服务器 <转>
- WINDOWS (服务器) 和 DOS(客户端) 网络互连 基于TCP/IP的编程实现
- 修改例子,TCP服务器和客户端,加入线程的概念,实现单方多次发送信息
- PHP与Memcached服务器交互的分布式实现源码分析
- Mangos源码分析(7):服务器公共组件实现之游戏主循环
- java 通过 socket 实现 服务器和客户端的通信 TCP
- Mangos源码分析(8):服务器公共组件实现之消息队列
- Mangos源码分析(9):服务器公共组件实现之环形缓冲区
- 网络编程资料总结(二)----Tcp多线程服务器和客户端的实现
- Glusterfs之nfs模块源码分析(中)之Glusterfs实现NFS服务器
- PHP与Memcached服务器交互的分布式实现源码分析
- WINDOWS (服务器) 和 DOS(客户端) 网络互连 基于TCP/IP的编程实现
- php与memcached服务器交互的分布式实现源码分析[memcache版]
- Mangos源码分析(3):服务器结构探讨之简单的世界服实现