侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

剖析Go编写的Socket服务器模块解耦及基础模块的设计

Go  /  管理员 发布于 5年前   216

Server的解耦―通过Router+Controller实现逻辑分发

在实际的系统项目工程中中,我们在写代码的时候要尽量避免不必要的耦合,否则你以后在更新和维护代码的时候会发现如同深陷泥潭,随便改点东西整个系统都要变动的酸爽会让你深切后悔自己当初为什么非要把东西都写到一块去(我不会说我刚实习的时候就是这么干的。。。)
所以这一篇主要说说如何设计Sever的内部逻辑,将Server处理Client发送信息的这部分逻辑与Sevrer处理Socket连接的逻辑进行解耦~
这一块的实现灵感主要是在读一个HTTP开源框架: Beego  的源代码的时候产生的,Beego的整个架构就是高度解耦的,这里引用一下作者的介绍:
beego 是基于八大独立的模块构建的,是一个高度解耦的框架。当初设计 beego 的时候就是考虑功能模块化,用户即使不使用 beego 的 HTTP 逻辑,也依旧可以使用这些独立模块,例如:你可以使用 cache 模块来做你的缓存逻辑;使用日志模块来记录你的操作信息;使用 config 模块来解析你各种格式的文件。所以 beego 不仅可以用于 HTTP 类的应用开发,在你的 socket 游戏开发中也是很有用的模块,这也是 beego 为什么受欢迎的一个原因。大家如果玩过乐高的话,应该知道很多高级的东西都是一块一块的积木搭建出来的,而设计 beego 的时候,这些模块就是积木,高级机器人就是 beego。 
这里上一张Beego的架构图:

  这是一个典型的MVC框架,可以看到,当用户发送请求到beego后,Beego内部在通过路由进行参数的过滤,然后路由根据用户发来的参数判断调用哪个Controller执行相关的逻辑,并在controller里调用相关的模块实现功能。通过这种方式,Beego成功的将所有模块都独立出来,也就是astaxie所说的“乐高积木化”。
       在这里,我们可以仿照Beego的架构,在Server内部加入一层Router,通过Router对通过Socket发来的信息进通过我们设定的规则行的判断后,调用相关的Controller进行任务的分发处理。在这个过程中不仅Controller彼此独立,匹配规则和Controller之间也是相互独立的。
       下面给出Router的实现代码,其中Msg的结构对应的是Json字符串,当然考虑到实习公司现在也在用这个,修改了一部分,不过核心思路是一样的哦:
复制代码 代码如下:
import ( 
    "utils" 
    "fmt" 
    "encoding/json" 
) 
 
type Msg struct { 
    Conditions   map[string]interface{} `json:"meta"` 
    Content interface{}            `json:"content"` 
} 
 
type Controller interface { 
    Excute(message Msg) []byte 
} 
 
var routers [][2]interface{} 
 
func Route(judge interface{} ,controller Controller) { 
    switch judge.(type) { 
    case func(entry Msg)bool:{ 
        var arr [2]interface{} 
        arr[0] = judge 
        arr[1] = controller 
        routers = append(routers,arr) 
    } 
    case map[string]interface{}:{ 
        defaultJudge:= func(entry Msg)bool{ 
            for keyjudge , valjudge := range judge.(map[string]interface{}){ 
                val, ok := entry.Meta[keyjudge] 
                if !ok { 
                    return false 
                } 
                if val != valjudge { 
                    return false 
                } 
            } 
            return true 
        } 
        var arr [2]interface{} 
        arr[0] = defaultjudge 
        arr[1] = controller 
        routers = append(routers,arr) 
        fmt.Println(routers) 
        } 
    default: 
        fmt.Println("Something is wrong in Router") 
    } 
} 

      通过自定义接口Router,我们将匹配规则judge和对应的controller封装了进去,然后在Server端负责接收socket发送信息的函数handleConnection那里再实现Router内部的遍历即可:
复制代码 代码如下:
for _ ,v := range routers{ 
        pred := v[0] 
        act := v[1] 
        var message Msg 
        err := json.Unmarshal(postdata,&message) 
        if err != nil { 
            Log(err) 
        } 
        if pred.(func(entry Msg)bool)(message) { 
            result := act.(Controller).Excute(message) 
            conn.Write(result) 
            return 
        } 
    } 

       这样Client每次发来信息,我们就可以让Router自动跟现有的规则进行匹配,最后调用对应的Controller进行逻辑的实现啦,下面给出一个controller的编写实例,这个Controll的作用是发来的json类型是mirror的时候,将Client发来的信息原样返回:
复制代码 代码如下:
type MirrorController struct  { 
 
} 
 
func (this *MirrorController) Excute(message Msg)[]byte { 
    mirrormsg,err :=json.Marshal(message) 
    CheckError(err) 
    return mirrormsg 
} 
 
 
func init() { 
    var mirror  
    routers = make([][2]interface{} ,0 , 20) 
    Route(func(entry Msg)bool{ 
        if entry.Meta["msgtype"]=="mirror"{ 
        return true} 
        return  false 
    },&mirror) 
}

日志模块的设计与定时任务模块模块
作为一个Server,日志(Log)功能是必不可少的,一个设计良好的日志模块,不论是开发Server时的调试,还是运行时候的维护,都是非常有帮助的。
因为这里写的是一个比较简化的Server框架,因此我选择对Golang本身的log库进行扩充,从而实现一个简单的Log模块。
在这里,我将日志的等级大致分为Debug,Operating,Error 3个等级,Debug主要用于存放调试阶段的日志信息,Operateing用于保存Server日常运行时产生的信息,Error则是保存报错信息。
模块代码如下:
复制代码 代码如下:
func LogErr(v ...interface{}) { 
 
    logfile := os.Stdout 
    log.Println(v...) 
    logger := log.New(logfile,"\r\n",log.Llongfile|log.Ldate|log.Ltime); 
    logger.SetPrefix("[Error]") 
    logger.Println(v...) 
    defer logfile.Close(); 
} 
 
func Log(v ...interface{}) { 
 
    logfile := os.Stdout 
    log.Println(v...) 
    logger := log.New(logfile,"\r\n",log.Ldate|log.Ltime); 
    logger.SetPrefix("[Info]") 
    logger.Println(v...) 
    defer logfile.Close(); 
} 
 
func LogDebug(v ...interface{}) { 
    logfile := os.Stdout 
    log.Println(v...) 
    logger := log.New(logfile,"\r\n",log.Ldate|log.Ltime); 
    logger.SetPrefix("[Debug]") 
    logger.Println(v...) 
    defer logfile.Close(); 
} 
 
func CheckError(err error) { 
    if err != nil { 
        LogErr(os.Stderr, "Fatal error: %s", err.Error()) 
    } 
} 

注意这里log的输出我使用的是stdout,因为这样在Server运行的时候可以直接将log重定向到指定的位置,方便整个Server的部署。不过在日常开发的时候,为了方便调试代码,我推荐将log输出到指定文件位置下,这样在调试的时候会方便很多(主要是因为golang的调试实在太麻烦,很多时候都要依靠打log的时候进行步进。便于调试的Log模块代码示意:
复制代码 代码如下:
func Log(v ...interface{}) { 
 
    logfile := os.OpenFile("server.log",os.O_RDWR|os.O_APPEND|os.O_CREATE,0); 
    if err != nil { 
        fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error()) 
        return    } 
    log.Println(v...) 
    logger := log.New(logfile,"\r\n",log.Ldate|log.Ltime); 
    logger.SetPrefix("[Info]") 
    logger.Println(v...) 
    defer logfile.Close(); 
} 

然后就是计时循环模块啦,日常运行中,Server经常要执行一些定时任务,比如隔一定时间刷新后台,隔一段时间自动刷新爬虫等等,在这里我设计了一个Task接口,通过类似于TaskList的的方式将所有定时任务注册后统一执行,代码如下:
复制代码 代码如下:
type DoTask interface { 
    Excute() 
} 
 
var tasklist []interface{} 
 
func AddTask(controller DoTask) { 
    var arr interface{} 
    arr = controller 
    tasklist = append(tasklist,arr) 
    fmt.Println(tasklist) 
    } 

在这里以一个定时报时任务作为例子:
复制代码 代码如下:
type Task1 struct {} 
 
func (this * Task1)Excute() { 
    timer := time.NewTicker(2 * time.Second) 
    for { 
        select { 
        case <-timer.C: 
            go func() { 
                Log(time.Now()) 
            }() 
        } 
    } 
} 
 
func init() { 
    var task1 Task1 
    tasklist = make([]interface{} ,0 , 20) 
    AddTask(&task1) 
        for _, v := range tasklist { 
            v.(DoTask).Excute() 
        } 
 
} 

注意这里的定时任务要做成非阻塞的,否则整个Server都会卡在tasklist的第一个task的。。。

 


  • 上一条:
    举例讲解Go语言中函数的闭包使用
    下一条:
    服务器端Go程序对长短链接的处理及运行参数的保存
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • 在go中实现一个常用的先进先出的缓存淘汰算法示例代码(0个评论)
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • 近期文章
    • 智能合约Solidity学习CryptoZombie第三课:组建僵尸军队(高级Solidity理论)(0个评论)
    • 智能合约Solidity学习CryptoZombie第二课:让你的僵尸猎食(0个评论)
    • 智能合约Solidity学习CryptoZombie第一课:生成一只你的僵尸(0个评论)
    • 在go中实现一个常用的先进先出的缓存淘汰算法示例代码(0个评论)
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-10
    • 2017-09
    • 2020-03
    • 2020-05
    • 2020-06
    • 2020-07
    • 2020-12
    • 2021-01
    • 2021-05
    • 2021-06
    • 2021-07
    • 2021-08
    • 2021-10
    • 2021-11
    • 2021-12
    • 2022-01
    • 2022-02
    • 2022-03
    • 2022-04
    • 2022-05
    • 2022-06
    • 2022-07
    • 2022-08
    • 2022-09
    • 2022-10
    • 2022-11
    • 2022-12
    • 2023-01
    • 2023-02
    • 2023-03
    • 2023-04
    • 2023-05
    • 2023-06
    • 2023-07
    • 2023-08
    • 2023-09
    • 2023-10
    • 2023-11
    • 2023-12
    • 2024-01
    • 2024-02
    • 2024-03
    • 2024-04
    • 2024-05
    • 2024-06
    • 2024-07
    • 2024-08
    • 2024-11
    • 2025-02
    • 2025-04
    • 2025-05
    • 2025-06
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客