introduce
What is the Nano?
Golang’s lightweight, convenient, high-performance game server framework.
The Nano is a lightweight server framework that is best suited for web games, social games, and mobile games. It’s not just games, of course. The Nano is also ideal for developing high real-time Web applications.
Most importantly, you can develop the Golang game server framework through this entry
The sample warehouse
cloud-native-game-server
Use Nano to quickly build a Chat Room
One sentence describes the Nano term
- Components (
Component
) :nano
The functionality of the application is loosely coupledComponent
Of eachComponent
Accomplish some functionality. Handler
: It is defined inComponent
To handle the concrete business logic.- Routing (
Route
) : Identifies aThe specific service
Or the client receives a push message from the serverlocation
. - Session (
Session
) : After the client connects to the server, a session is set up to hold some context information during the connection. Release after the connection is disconnected. - Group (
Group
) :Group
You can view it as aSession
For scenarios where push messages need to be broadcast. - Request (
Request
Response (),Response
), notify (Notify
), push (Push
) :Nano
Four message types in the.
The lifecycle of the component
type DemoComponent struct{}
func (c *DemoComponent) Init() {}
func (c *DemoComponent) AfterInit() {}
func (c *DemoComponent) BeforeShutdown() {}
func (c *DemoComponent) Shutdown() {}
Copy the code
- Init: called when the component is initialized.
- AfterInit: Called after the component has been initialized.
- BeforeShutdown: The component is called before it is destroyed.
- Shutdown: called when the component is destroyed.
The entire component lifecycle looks pretty clear.
Describe the business in one sentence
- Users can join specific rooms
- The user can see all members in the room
- Users can send messages in the current room
Business specific analysis
- Users can join specific rooms
- Request to join (
Request
) – >Request
The correspondingnano
A message type - Required response (
Response
-> Allow to join ->Response
The correspondingnano
A message type
- Request to join (
- The user can see all members in the room
- Server active push (
Push
) All members of the roomMembers
->Push
The correspondingnano
A message type - Server active broadcast 📢(
Push
) Other members of the room, there are new people to joinNew user
- Server active push (
- Users can send messages in the current room
- User sent (
Notify
) message to current room ->Notify
The correspondingnano
A type of message that does not require the server to respond to it - The server will message 📢(
Push
) To the rest of the room
- User sent (
So far, we’ve seen the business, and from the business we’ve seen the four messaging types of applications for the Nano.
Demo source parsing
demo/1-nano-chat
type (
// The definition of a room
Room struct {
// Manage all sessions in the room
group *nano.Group
}
// RoomManager represents a component containing a bunch of rooms. It is a Nano component that hooks logic during its lifetime
RoomManager struct {
// Inherit the Nano component and have a full life cycle
component.Base
// After the component is initialized, do some scheduled tasks
timer *scheduler.Timer
// Multiple rooms, key-value storage
rooms map[int]*Room
}
// represents a message definition sent by a user
UserMessage struct {
Name string `json:"name"`
Content string `json:"content"`
}
// Receive a new user message (broadcast) when a new user joins the room
NewUser struct {
Content string `json:"content"`
}
// Contains the UID of all members
AllMembers struct {
Members []int64 `json:"members"`
}
// indicates the response result of joining the room server
JoinResponse struct {
Code int `json:"code"`
Result string `json:"result"`
}
// Traffic statistics
Stats struct {
// Inherit the Nano component and have a full life cycle
component.Base
// After the component is initialized, do some scheduled tasks
timer *scheduler.Timer
// Egress traffic statistics
outboundBytes int
// Statistics on incoming traffic
inboundBytes int})// Count the outbound traffic, which will define the pipeline to nano
func (stats *Stats) outbound(s *session.Session, msg *pipeline.Message) error {
stats.outboundBytes += len(msg.Data)
return nil
}
// Count the entry traffic, which will define the pipeline to nano
func (stats *Stats) inbound(s *session.Session, msg *pipeline.Message) error {
stats.inboundBytes += len(msg.Data)
return nil
}
// When the component is initialized, it is called
// The flow rate at the lower exit and inlet is printed per minute
func (stats *Stats) AfterInit(a) {
stats.timer = scheduler.NewTimer(time.Minute, func(a) {
println("OutboundBytes", stats.outboundBytes)
println("InboundBytes", stats.outboundBytes)
})
}
func (st *Stats) Nil(s *session.Session, msg []byte) error {
return nil
}
const (
// Test the room ID
testRoomID = 1
// Test room key
roomIDKey = "ROOM_ID"
)
// Initialize RoomManager
func NewRoomManager(a) *RoomManager {
return &RoomManager{
rooms: map[int]*Room{},
}
}
// RoomManager will be called when the initialization is complete
func (mgr *RoomManager) AfterInit(a) {
// This will be called when the user disconnects
// Remove it from the room
session.Lifetime.OnClosed(func(s *session.Session) {
if! s.HasKey(roomIDKey) {return
}
room := s.Value(roomIDKey).(*Room)
// Remove this session
room.group.Leave(s)
})
// A scheduled task to print the number of members in the room per minute
mgr.timer = scheduler.NewTimer(time.Minute, func(a) {
for roomId, room := range mgr.rooms {
println(fmt.Sprintf("UserCount: RoomID=%d, Time=%s, Count=%d",
roomId, time.Now().String(), room.group.Count()))
}
})
}
// Add business logic to the room
func (mgr *RoomManager) Join(s *session.Session, msg []byte) error {
// Note: The demo only adds 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() // This is just a simulation of the UID with the sessionId
s.Bind(fakeUID) // Bind the UID to the session
s.Set(roomIDKey, room) // Set the room to which the current session is associated
// Push all members of the room to the current session
s.Push("onMembers", &AllMembers{Members: room.group.Members()})
// Other members of the broadcast room, there are new people to join
room.group.Broadcast("onNewUser", &NewUser{Content: fmt.Sprintf("New user: %d", s.ID())})
// Add the session to the room group
room.group.Add(s)
// Reply that the current user joined successfully
return s.Response(&JoinResponse{Result: "success"})}// Synchronize the latest news to all members of the room
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)
/ / radio
return room.group.Broadcast("onMessage", msg)
}
func main(a) {
// Create a new component container instance
components := &component.Components{}
// Register the component
components.Register(
// Component instance
NewRoomManager(),
// Override the component name
component.WithName("room"),
// Override the handler name of the component, in lowercase
component.WithNameFunc(strings.ToLower),
)
// Traffic statistics
pip := pipeline.New()
var stats = &stats{}
// Join Outbound pipeline
pip.Outbound().PushBack(stats.outbound)
// Enter the Inbound pipeline
pip.Inbound().PushBack(stats.inbound)
// Register the traffic statistics component
components.Register(stats, component.WithName("stats"))
// Set the log print format
log.SetFlags(log.LstdFlags | log.Llongfile)
// Web static resource processing
http.Handle("/web/", http.StripPrefix("/web/", http.FileServer(http.Dir("web"))))
/ / start the nano
nano.Listen(": 3250"./ / the port number
nano.WithIsWebsocket(true), // Whether to use Websocket
nano.WithPipeline(pip), // Whether to use pipeline
nano.WithCheckOriginFunc(func(_ *http.Request) bool { return true }), // Allow cross-domain
nano.WithWSPath("/nano"), // WebSocket connection address
nano.WithDebugMode(), // Enable the debug mode
nano.WithSerializer(json.NewSerializer()), // Use the JSON serializer
nano.WithComponents(components), // Load the component)}Copy the code
The front-end code is very simple, so you can look directly at cloud-native Game-server
Docker builds a development debugging environment
Dockerfile
Dockerfile.dev
FROM Golang: 1.14
WORKDIR /workspace
# ali cloud
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
Copy the code
Building the Image:
docker build -f Dockerfile.dev -t cloud-native-game-server:dev .
Copy the code
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"
Copy the code
Start the development environment (Live Reload support)
If I want to develop 1-nano-chat
DEMO=1-nano-chat docker-compose up demo
Copy the code
Go to localhost:3250/web/ to see the effect.
Start the mode environment
If I want to debug 1-nano-chat
DEMO=1-nano-chat docker-compose up demo-debug
Copy the code
reference
- The official making
- How to Build your first Nano App
- The official Demo – starx – chat – Demo