探索 Golang 云原生游戏服务器开发,5 分钟上手 Nano 游戏服务器框架

简介: 探索 Golang 云原生游戏服务器开发,5 分钟上手 Nano 游戏服务器框架

介绍



Nano 是什么?


轻量级,方便,高性能 golang 的游戏服务器框架。

nano 是一个轻量级的服务器框架,它最适合的应用领域是网页游戏、社交游戏、移动游戏的服务端。当然还不仅仅是游戏,用 nano 开发高实时 web 应用也非常合适。

最重要的是可以通过这个入门 Golang 游戏服务器框架开发


示例仓库


[cloud-native-game-server](https://github.com/Hacker-Linner/cloud-native-game-server)


使用 Nano 快速搭建一个 Chat Room



一句话描述 Nano 术语


  • 组件(Component):nano 应用的功能就是由一些松散耦合的 Component 组成的,每个 Component 完成一些功能。
  • Handler:它定义在 Component 内的方法,用来处理具体的业务逻辑。
  • 路由(Route):用来标识一个具体服务 或者客户端接受服务端推送消息的位置
  • 会话(Session):客户端连接服务器后, 建立一个会话保存连接期间一些上下文信息。连接断开后释放。
  • 组(Group):Group 可以看作是一个 Session 的容器,主要用于需要广播推送消息的场景。
  • 请求(Request), 响应(Response), 通知(Notify), 推送(Push):Nano 中四种消息类型。


组件的生命周期


type DemoComponent struct{}
func (c *DemoComponent) Init()           {}
func (c *DemoComponent) AfterInit()      {}
func (c *DemoComponent) BeforeShutdown() {}
func (c *DemoComponent) Shutdown()       {}


  • Init:组件初始化时将被调用。
  • AfterInit:组件初始化完成后将被调用。
  • BeforeShutdown:组件销毁之前将被调用。
  • Shutdown:组件销毁时将被调用。

整个组件的生命周期看起来非常的清晰。


一句话描述业务

  • 用户可以加入具体房间
  • 用户可以看到房间内所有成员
  • 用户可以在当前房间发送消息


业务具体分析

  • 用户可以加入具体房间
  • 请求加入(Request) -> Request 对应 nano 一种消息类型
  • 需要响应(Response)是否允许加入 -> Response 对应 nano 一种消息类型
  • 用户可以看到房间内所有成员
  • 服务端主动推送(Push)房间内所有成员Members -> Push 对应 nano 一种消息类型
  • 服务端主动广播📢(Push)房间内其它成员,有新人加入New user
  • 用户可以在当前房间发送消息
  • 用户发送(Notify)消息到当前房间 -> Notify 对应 nano 一种消息类型,不需要服务器对他有所回应
  • 服务器将消息📢(Push)给房间其它成员

至此,我们了解了业务,然后通过业务我们又了解了 Nano 的四种消息类型应用。


Demo 源码解析


demo/1-nano-chat


type (
  // 房间的定义
  Room struct {
    // 管理房间内所有的会话
    group *nano.Group
  }
  // RoomManager 表示一个包含一堆房间的组件,他是 nano 组件,可在生命周期内 hook 逻辑
  RoomManager struct {
    // 继承 nano 组件,拥有完整的生命周期
    component.Base
    // 组件初始化完成后,做一些定时任务
    timer *scheduler.Timer
    // 多个房间,key-value 存储
    rooms map[int]*Room
  }
  // 表示一个用户发送的消息定义
  UserMessage struct {
    Name    string `json:"name"`
    Content string `json:"content"`
  }
  // 当新用户加入房间时将收到新用户消息(广播)
  NewUser struct {
    Content string `json:"content"`
  }
  // 包含所有成员的 UID 
  AllMembers struct {
    Members []int64 `json:"members"`
  }
  // 表示加入房间服务端的响应结果
  JoinResponse struct {
    Code   int    `json:"code"`
    Result string `json:"result"`
  }
  // 流量统计
  Stats struct {
    // 继承 nano 组件,拥有完整的生命周期
    component.Base
    // 组件初始化完成后,做一些定时任务
    timer         *scheduler.Timer
    // 出口流量统计
    outboundBytes int
    // 入口流量统计
    inboundBytes  int
  }
)
// 统计出口流量,会定义到 nano 的 pipeline
func (stats *Stats) outbound(s *session.Session, msg *pipeline.Message) error {
  stats.outboundBytes += len(msg.Data)
  return nil
}
// 统计入口流量,会定义到 nano 的 pipeline
func (stats *Stats) inbound(s *session.Session, msg *pipeline.Message) error {
  stats.inboundBytes += len(msg.Data)
  return nil
}
// 组件初始化完成后,会调用
// 每分钟会打印下出口与入口的流量
func (stats *Stats) AfterInit() {
  stats.timer = scheduler.NewTimer(time.Minute, func() {
    println("OutboundBytes", stats.outboundBytes)
    println("InboundBytes", stats.outboundBytes)
  })
}
func (st *Stats) Nil(s *session.Session, msg []byte) error {
  return nil
}
const (
  // 测试房间 id
  testRoomID = 1
  // 测试房间 key
  roomIDKey  = "ROOM_ID"
)
// 初始化 RoomManager
func NewRoomManager() *RoomManager {
  return &RoomManager{
    rooms: map[int]*Room{},
  }
}
// RoomManager 初始化完成后将被调用
func (mgr *RoomManager) AfterInit() {
  // 用户断开连接后将会被调用
  // 将它从房间中移除
  session.Lifetime.OnClosed(func(s *session.Session) {
    if !s.HasKey(roomIDKey) {
      return
    }
    room := s.Value(roomIDKey).(*Room)
    // 移除这个会话
    room.group.Leave(s)
  })
  // 一个定时任务,每分钟打印下房间的成员数量
  mgr.timer = scheduler.NewTimer(time.Minute, func() {
    for roomId, room := range mgr.rooms {
      println(fmt.Sprintf("UserCount: RoomID=%d, Time=%s, Count=%d",
        roomId, time.Now().String(), room.group.Count()))
    }
  })
}
// 加入房间的业务逻辑处理
func (mgr *RoomManager) Join(s *session.Session, msg []byte) error {
  // 注意:这里 demo 仅仅只是加入 testRoomID
  room, found := mgr.rooms[testRoomID]
  if !found {
    room = &Room{
      group: nano.NewGroup(fmt.Sprintf("room-%d", testRoomID)),
    }
    mgr.rooms[testRoomID] = room
  }
  fakeUID := s.ID() // 这里仅仅是用 sessionId 模拟下 uid
  s.Bind(fakeUID)   // 绑定 uid 到 session
  s.Set(roomIDKey, room) // 设置一下当前 session 关联到的房间
  // 推送房间所有成员到当前的 session
  s.Push("onMembers", &AllMembers{Members: room.group.Members()})
  // 广播房间内其它成员,有新人加入
  room.group.Broadcast("onNewUser", &NewUser{Content: fmt.Sprintf("New user: %d", s.ID())})
  // 将 session 加入到房间 group 统一管理
  room.group.Add(s)
  // 回应当前用户加入成功
  return s.Response(&JoinResponse{Result: "success"})
}
// 同步最新的消息给房间内所有成员
func (mgr *RoomManager) Message(s *session.Session, msg *UserMessage) error {
  if !s.HasKey(roomIDKey) {
    return fmt.Errorf("not join room yet")
  }
  room := s.Value(roomIDKey).(*Room)
  // 广播
  return room.group.Broadcast("onMessage", msg)
}
func main() {
  // 新建组件容器实例
  components := &component.Components{}
  // 注册组件
  components.Register(
    // 组件实例
    NewRoomManager(),
    // 重写组件名字
    component.WithName("room"),
    // 重写组件 handler 名字,这里是小写
    component.WithNameFunc(strings.ToLower),
  )
  // 流量统计
  pip := pipeline.New()
  var stats = &stats{}
  // 入队 Outbound pipeline 
  pip.Outbound().PushBack(stats.outbound)
  // 入队 Inbound pipeline
  pip.Inbound().PushBack(stats.inbound)
  // 注册下流量统计组件
  components.Register(stats, component.WithName("stats"))
  // 设置日志打印格式
  log.SetFlags(log.LstdFlags | log.Llongfile)
  // web 静态资源处理
  http.Handle("/web/", http.StripPrefix("/web/", http.FileServer(http.Dir("web"))))
  // 启动 nano 
  nano.Listen(":3250", // 端口号
    nano.WithIsWebsocket(true), // 是否使用 websocket
    nano.WithPipeline(pip), // 是否使用 pipeline
    nano.WithCheckOriginFunc(func(_ *http.Request) bool { return true }), // 允许跨域
    nano.WithWSPath("/nano"), // websocket 连接地址
    nano.WithDebugMode(),  // 开启 debug 模式
    nano.WithSerializer(json.NewSerializer()), // 使用 json 序列化器
    nano.WithComponents(components), // 加载组件
  )
}


前端代码非常简单,大家直接看 cloud-native-game-server


Docker 搭建开发调试环境



Dockerfile


Dockerfile.dev


FROM golang:1.14
WORKDIR /workspace
# 阿里云
RUN go env -w GO111MODULE=on
RUN go env -w GOPROXY=https://mirrors.aliyun.com/goproxy/,direct
# debug
RUN go get github.com/go-delve/delve/cmd/dlv
# live reload
RUN go get -u github.com/cosmtrek/air
# nano
RUN go mod init cloud-native-game-server
RUN go get github.com/lonng/nano@master


构建 Image


docker build -f Dockerfile.dev -t cloud-native-game-server:dev .


docker-compose.yaml


version: "3.4"
services:
  demo:
    image: cloud-native-game-server:dev
    command: >
      bash -c "cp ./go.mod ./go.sum app/
      && cd app/demo/${DEMO}
      && ls -la
      && air -c ../../.air.toml -d"
    volumes:
    - ./:/workspace/app
    ports:
      - 3250:3250
  demo-debug:
    image: cloud-native-game-server:dev
    command: >
      bash -c "cp ./go.mod ./go.sum app/
      && cd app/demo/${DEMO}
      && ls -la
      && dlv debug main.go --headless --log -l 0.0.0.0:2345 --api-version=2"
    volumes:
    - ./:/workspace/app
    ports:
      - 3250:3250
      - 2345:2345
    security_opt:
      - "seccomp:unconfined"


启动开发环境(支持 live reload)


# 如我要开发 1-nano-chat
DEMO=1-nano-chat docker-compose up demo


进入 localhost:3250/web/ 可以看到效果。


启动调式环境


# 如我要调试 1-nano-chat
DEMO=1-nano-chat docker-compose up demo-debug
相关文章
|
18天前
|
Python
Flask学习笔记(二):基于Flask框架上传图片到服务器端并原名保存
关于如何使用Flask框架上传图片到服务器端并以其原名保存的教程。
50 1
|
18天前
|
Python
Flask学习笔记(三):基于Flask框架上传特征值(相关数据)到服务器端并保存为txt文件
这篇博客文章是关于如何使用Flask框架上传特征值数据到服务器端,并将其保存为txt文件的教程。
25 0
Flask学习笔记(三):基于Flask框架上传特征值(相关数据)到服务器端并保存为txt文件
|
22天前
|
分布式计算 Hadoop
Hadoop-27 ZooKeeper集群 集群配置启动 3台云服务器 myid集群 zoo.cfg多节点配置 分布式协调框架 Leader Follower Observer
Hadoop-27 ZooKeeper集群 集群配置启动 3台云服务器 myid集群 zoo.cfg多节点配置 分布式协调框架 Leader Follower Observer
34 1
|
27天前
|
Web App开发 JavaScript 前端开发
使用Node.js和Express框架构建Web服务器
使用Node.js和Express框架构建Web服务器
|
22天前
|
存储 SQL 消息中间件
Hadoop-26 ZooKeeper集群 3台云服务器 基础概念简介与环境的配置使用 架构组成 分布式协调框架 Leader Follower Observer
Hadoop-26 ZooKeeper集群 3台云服务器 基础概念简介与环境的配置使用 架构组成 分布式协调框架 Leader Follower Observer
37 0
|
3月前
|
运维 Cloud Native 开发者
云原生技术演进:从微服务到无服务器的旅程
【8月更文挑战第20天】在数字化时代的浪潮中,云原生技术如同一艘航船,承载着企业转型的梦想与挑战。本文将深入探讨云原生技术的发展路径,从微服务的兴起到无服务器架构的革新,揭示这一技术演进背后的逻辑与动力。通过分析云原生技术的优势、面临的挑战以及未来的发展趋势,我们将描绘出一幅云原生技术演进的宏伟蓝图。
|
4月前
|
弹性计算 运维 云计算
云服务器 ECS产品使用问题之如何把本地的游戏存档上传到在线游戏服务器
云服务器ECS(Elastic Compute Service)是各大云服务商阿里云提供的一种基础云计算服务,它允许用户租用云端计算资源来部署和运行各种应用程序。以下是一个关于如何使用ECS产品的综合指南。
|
3月前
|
JSON API 数据格式
基于服务器响应的实时天气数据进行JSON解析的详细代码及其框架
【8月更文挑战第25天】这段资料介绍了一个使用Python从服务器获取实时天气数据并解析JSON格式数据的基本框架。主要分为三个部分:一是安装必要的`requests`库以发起HTTP请求获取数据,同时利用Python内置的`json`库处理JSON数据;二是提供了具体的代码实现,包括获取天气数据的`get_weather_data`函数和解析数据的`parse_weather_data`函数;三是对代码逻辑进行了详细说明,包括如何通过API获取数据以及如何解析这些数据来获取温度和天气描述等信息。用户需要根据实际使用的天气API调整代码中的API地址、参数和字段名称。
|
3月前
|
运维 Cloud Native 云计算
云原生架构的演进:从微服务到无服务器计算
在数字化转型的浪潮中,云原生技术以其灵活性、可扩展性和成本效益性,成为推动现代软件开发和运维的关键力量。本文将探讨云原生概念的演变,特别是从微服务架构到无服务器计算的转变,揭示这一进化如何影响应用程序的开发、部署和管理。通过分析实际案例,我们旨在提供对云原生技术未来趋势的洞察,同时指出企业在这一转变过程中可能面临的挑战和机遇。
47 2
|
3月前
|
缓存 监控 中间件
构建高效的Go语言Web服务器:基于Fiber框架的性能优化实践
在追求极致性能的Web开发领域,Go语言(Golang)凭借其高效的并发处理能力、垃圾回收机制及简洁的语法赢得了广泛的青睐。本文不同于传统的性能优化教程,将深入剖析如何在Go语言环境下,利用Fiber这一高性能Web框架,通过精细化配置、并发策略调整及代码层面的微优化,构建出既快速又稳定的Web服务器。通过实际案例与性能测试数据对比,揭示一系列非直觉但极为有效的优化技巧,助力开发者在快节奏的互联网环境中抢占先机。