Nano

Nano

一个易于使用、快速、轻量的游戏服务器网络库

参考自: nano开源库学习 –by 这一切没有想象那么糟

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

github Nano

注: github的官方例子貌似跑不了 它导的包是github.com/lonnng/nano而不是github.com/lonng/nano, 有些方法也不存在了, 作者可能是改了一次名, 并对原有有包进行了更新, 但教程并未跟进, 使用上则没有影响

名词介绍

Component:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// Component 组件
type Component interface {
    // 初始化函数
	Init()
    // 初始化之后运行的函数
	AfterInit()
    // 销毁之前将被调用
	BeforeShutdown()
    // 销毁时被调用
	Shutdown()
}

nano 应用的功能就是由一些松散耦合的 Component 组成的, 它的接口用于控制它的生命周期 除此之外它应该实现某些具体的功能

Session:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// Session instance related to the client will be passed to Handler method as the first
type Session struct {
	sync.RWMutex                        // protect data
	id           int64                  // session global unique id
	uid          int64                  // binding user id
	lastTime     int64                  // last heartbeat time
	entity       NetworkEntity          // low-level network entity
	data         map[string]interface{} // session data store
	router       *Router
}

客户端连接服务器后建立Session, 它可以存储一些临时数据, 连接断开时释放所有数据

Group:

1
2
3
4
5
6
7
8
// Group represents a session group which used to manage a number of
// sessions, data send to the group will send to all session in it.
type Group struct {
	mu       sync.RWMutex
	status   int32                      // channel current status
	name     string                     // channel name
	sessions map[int64]*session.Session // session id map to session instance
}

Session组的封装, 对Group发信息会广播给所有其中的Session

四种消息类型:

  • Request(请求)Response(响应): 客户端发起Request到服务器端,服务器端处理后会给其返回响应Response
  • Notify: 客户端发给服务端的通知, 也就是不需要服务端给予回复的Request
  • Push: 服务端主动给客户端推送消息的类型

简单示例

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
package main

import (
	"fmt"
	"net/http"

	"github.com/lonng/nano"
	"github.com/lonng/nano/component"
	"github.com/lonng/nano/serialize/json"
	"github.com/lonng/nano/session"
)

const (
	// 测试房间 id
	testRoomID = 1
	// 测试房间 key
	roomIDKey = "ROOM_ID"
)

type Room struct {
	Name  string
	Id    int
	Group *nano.Group
}

type RoomManager struct {
	component.Base
	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)
	})
}

// 加入房间的业务逻辑处理
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", &struct {
		Members []int64 `json:"members"`
	}{Members: room.Group.Members()})
	// 广播房间内其它成员,有新人加入
	room.Group.Broadcast("onNewUser", &struct {
		Content string `json:"content"`
	}{Content: fmt.Sprintf("New user: %d", s.ID())})
	// 将 session 加入到房间 group 统一管理
	room.Group.Add(s)
	fmt.Println(s.Value(roomIDKey).(*Room))
	// 回应当前用户加入成功
	return s.Response(&struct {
		Code   int    `json:"code"`
		Result string `json:"result"`
	}{Result: "success"})
}

// 同步最新的消息给房间内所有成员
func (mgr *RoomManager) Message(s *session.Session, msg *struct {
	Name    string `json:"name"`
	Content string `json:"content"`
}) error {
	fmt.Printf("msg: %v\n", msg)
	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(
		// 组件实例
		&RoomManager{
			Rooms: map[int]*Room{},
		},
		// 重写组件名字
		component.WithName("room"),
	)
	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),           // 加载组件
	)
}

前端

updatedupdated2023-09-032023-09-03