EchoServer设计到实现(四)

前言

这篇文章主要解决以下问题:
1.文件中存在硬编码问题  
2.不支持长链接断开和心跳包

正文

文件硬编码,是所有编程者头疼的问题,硬编码使程序难扩展,易导致bug。在C++中经常用宏来解决这些东西,但是golang不支持。不过我们可以通过配置器来解决,思路:把信息写入文本中,文本按一定格式进行编写,哈哈,这就通过读取文件化解这一危机。

我找使用Toml包进行文件解析,github地址:https://github.com/BurntSushi/toml,这个里面有说明使用方式和安装流程,这里就画蛇添足了。
设计文件读取,我编写两个文件读取类一个应用于动态配置,一个应用于静态配置。主要考虑到文件IO读取是操作一大难题和系统阻塞原因。在编写过程中,使动态配置支持热重载—–在程序启动时,可以通过修改文件更改系统操作。同样使用单例模式解决,支持配置随时读取。因动态配置和静态配置代码差异较小,这里只给出动态代码
动态配置文件

type = 11

动态配置读取

package config
import (
    "EchoTCP/common"
    "errors"
    "sync"
    "time"

    "github.com/BurntSushi/toml"
)
var once1 sync.Once
var dynConf *DynConfig

var strCon = "../config/dyn_config.toml"
//DynConf  对外调用对象
var DynConf = newDynInstance()
type dynConfig struct {
    Type int
}
//DynConfig   动态配置器
type DynConfig struct {
    Config   dynConfig
    ChangeID chan int
}
//NewDynInstance 返单例静态配置
func newDynInstance() *DynConfig {
    once1.Do(func() {
        dynConf = newDyn()
        common.Logger.Info("Create dyn Config")
        dynConf.readConfig()
    })
    return dynConf
}
//NewTomlConfig 同步调用接口 第一次读取配置 并返回配置对象
func newDyn() *DynConfig {
    var tlConfig dynConfig
    if err := configMsg(&tlConfig); err != nil {
        //打印日志

        common.Logger.Error(err.Error())
        return nil
    }
    changeID := make(chan int, 1000)
    return &DynConfig{Config: tlConfig, ChangeID: changeID}
}
//ConfigMsg  读取配置文件 @配置文件信息 @返回是否发生错误
func configMsg(tlconfig *dynConfig) (err error) {

    //读取失败后重试三次
    for i := 0; i < 3; i++ {
        if _, err = toml.DecodeFile(strCon, tlconfig); err != nil {
            //打印日志
            common.Logger.Error(err.Error())
            time.Sleep(1 * time.Second)
            continue
        } else {
            return nil
        }
    }
    return errors.New("read file three error ,please quit")
}
//readConfig 管理配置线程
func (dyn *DynConfig) readConfig() {
    var tlConfig dynConfig
    go func() {
        for {
            if err := configMsg(&tlConfig); err != nil {
                //打印日志
                common.Logger.Error(err.Error())
                time.Sleep(100 * time.Second)
                continue
            }
            if dyn.Config.Type != tlConfig.Type {

                common.Logger.Info(string("Opt:" + string(dyn.Config.Type)))
                common.Logger.Info(string("Opt:" + string(tlConfig.Type)))
                dyn.Config.Type = tlConfig.Type
                dyn.ChangeID <- 1
            }

            time.Sleep(10 * time.Second)
        }
    }()
}
心跳包解决方式,我使用的方法就是比较粗暴了,系统提供一下几个函数:
等待超时
func (*IPConn) SetDeadline
func (c *IPConn) SetDeadline(t time.Time) error
读超时
func (*IPConn) SetReadDeadline
func (c *IPConn) SetReadDeadline(t time.Time) error
写超时
func (*IPConn) SetWriteDeadline
func (c *IPConn) SetWriteDeadline(t time.Time) error
通过设置阻塞时间,当底层定时器计时返回时,及默认为这个链接为不活跃链接,就断开链接。心跳包的表现形式表现,有数据交互。针对服务器设计,表现受到客户端请求数据包。

服务端代码

import (
    "EchoTCP/common"
    "EchoTCP/config"
    "EchoTCP/protocol"
    //  "github.com/uber-go/zap"
    //  "io"
    "net"
    //  "os"
    "time"
)

//EchoServer 服务sock结构
type EchoServer struct {
    Port string
    IP   string
    Pro  string

    Conns   []net.Conn
    RecMsgs []chan string
}

//New  创建新的EchoServer
func New() *EchoServer {
    //var conns = make([]net.Conn, 1000)
    //var recMsgs = make([]chan string, 1000)
    common.Logger.Info("Create EchoServer Sucesss")
    return &EchoServer{} //{Conns: conns, RecMsgs: recMsgs}
}

//SetPort   设置端口号
func (server *EchoServer) SetPort(port string) {
    server.Port = port
}

//SetAddr   设置IP地址
func (server *EchoServer) SetAddr(addr string) {
    server.IP = addr
}

//SetPor  设置连接协议
func (server *EchoServer) SetPor(pro string) {
    server.Pro = pro
}

//Listen 启动监听服务 返回监听对象 错误址
func (server *EchoServer) Listen() (listener net.Listener, err error) {
    if server.IP == "" {
        server.IP = config.StaticConf.Config.Sock.IP
    }
    if server.Port == "" {
        server.Port = string(config.StaticConf.Config.Sock.Port)
    }
    if "" == server.Pro {
        server.Pro = config.StaticConf.Config.Sock.Por
    }

    addr := server.IP + ":" + server.Port
    listener, err = net.Listen(server.Pro, addr)
    common.Logger.Info(addr)
    common.Logger.Info(server.Pro)

    if err != nil {
        common.Logger.Error(err.Error())
        return nil, err
    }
    return listener, nil
}

//Accept 接受连接
func (server *EchoServer) Accept(listener net.Listener) {

    for {
        conn, err := listener.Accept()
        if err != nil {
            common.Logger.Error(err.Error())
            continue
        }
        common.Logger.Info("There is Conner")

        recvMsg := make(chan string, 1024)
        server.RecMsgs = append(server.RecMsgs, recvMsg)
        server.Conns = append(server.Conns, conn)
        go server.RecvHandle(conn, recvMsg)
    }
}

//RecvHandle 接受客户端发送过的消息
func (server *EchoServer) RecvHandle(conn net.Conn, recMsg chan string) {
    buf := make([]byte, 1024)
    defer conn.Close()
    readerChannal := make(chan []byte, 1024)
    go func(read chan []byte) {
        for {
            select {
            case buf := <-read:
                n := len(buf)
                recMsg <- string(buf[:n])
                common.Logger.Info(string("recv  message:" + string(buf[:n])))
            }
        }
    }(readerChannal)

    tmpBuf := make([]byte, 0)
    for {
        conn.SetReadDeadline(time.Now().Add(time.Second * 1))
        n, err := conn.Read(buf)

        if err != nil {
            if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
                var key = 0
                for _, value := range server.Conns {
                    if value == conn {
                        break
                    }
                    key++
                }
                if key == len(server.Conns) {
                    common.Logger.Info("conner leave")
                    return
                }
                continue
            } else {
                common.Logger.Error(err.Error())
                return
            }
        }
        tmpBuf = protocol.Depack(append(tmpBuf, buf[:n]...), readerChannal)
    }
}

//SendHandle  批量读写消息
func (server *EchoServer) SendHandle(conns []net.Conn, sendMsg []string) {

    for key, value := range conns {
        _, err := server.SendSingleMsg(value, sendMsg[key])
        //value.Write([]byte(sendMsg[key]))
        if err != nil {
            common.Logger.Error(err.Error())
        }
        common.Logger.Info(string("send message:" + sendMsg[key]))
    }
}

//SendSingleMsg   单条发送
func (server *EchoServer) SendSingleMsg(conn net.Conn, sendMsg string) (n int, err error) {
    n, err = conn.Write([]byte(sendMsg))
    return
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值