前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >探索 Golang 云原生游戏服务器开发,5 分钟上手 Nano 游戏服务器框架

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

作者头像
为少
发布2021-05-27 18:59:34
6.7K0
发布2021-05-27 18:59:34
举报
文章被收录于专栏:黑客下午茶黑客下午茶

介绍

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 中四种消息类型。

组件的生命周期

代码语言:javascript
复制
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

代码语言:javascript
复制
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

代码语言:javascript
复制
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

代码语言:javascript
复制
docker build -f Dockerfile.dev -t cloud-native-game-server:dev .

docker-compose.yaml

代码语言:javascript
复制
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)

代码语言:javascript
复制
# 如我要开发 1-nano-chat
DEMO=1-nano-chat docker-compose up demo

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

启动调式环境

代码语言:javascript
复制
# 如我要调试 1-nano-chat
DEMO=1-nano-chat docker-compose up demo-debug
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-10-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 黑客下午茶 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 介绍
    • Nano 是什么?
      • 示例仓库
      • 使用 Nano 快速搭建一个 Chat Room
        • 一句话描述 Nano 术语
          • 组件的生命周期
            • 一句话描述业务
              • 业务具体分析
              • Demo 源码解析
              • Docker 搭建开发调试环境
                • Dockerfile
                  • docker-compose.yaml
                    • 启动开发环境(支持 live reload)
                      • 启动调式环境
                      相关产品与服务
                      容器镜像服务
                      容器镜像服务(Tencent Container Registry,TCR)为您提供安全独享、高性能的容器镜像托管分发服务。您可同时在全球多个地域创建独享实例,以实现容器镜像的就近拉取,降低拉取时间,节约带宽成本。TCR 提供细颗粒度的权限管理及访问控制,保障您的数据安全。
                      领券
                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档