前言
这篇文章主要解决以下问题:
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
}