/* * register creates a new client, assigns it an id and returns it */ func (reg *ClientRegistry) register(client *network.Conn) uint32 { var clientId uint32 // protect: // - increment the next available id // - client map write reg.mutex.Lock() // we have a new client, assign him an id. clientId = reg.allocId() reg.clients[clientId] = client // record the client id inside the connection, this is needed for later // retriving the clientId when we just have a connection clientData := ClientData{ Id: clientId, Name: "", Joined: false, } client.SetUserData(clientData) // Note: for now we just stupidly increment the next available id. // We will have other problems to solve before this overflows... reg.mutex.Unlock() log.WithFields(log.Fields{ "client": clientData, "addr": client.GetRawConn().RemoteAddr(), }).Info("Accepted a new client") return clientId }
/* * OnClose gets called by the server at connection closing, once by * connection. This gives us the chance to unregister a new client and perform * client cleanup */ func (srv *Server) OnClose(c *network.Conn) { log.WithField("addr", c.GetRawConn().RemoteAddr()).Debug("Connection closed") // unregister the client before anything clientData := c.GetUserData().(ClientData) srv.clients.unregister(clientData.Id) if clientData.Joined { // client is still JOINED so that's a disconnection initiated externally // send a LEAVE to the rest of the world msg := messages.New(messages.LeaveId, messages.Leave{ Id: uint32(clientData.Id), Reason: "client disconnection", }) srv.Broadcast(msg) } else { // the client was not marked as JOINED, so nobody knows about him // and we have nothing more to do } if srv.playerLeftCb != nil { // raise 'player left' external callback srv.playerLeftCb(clientData.Id) } }
/* * handleOperate processes a Operate message and fires a PlayerOperate event */ func (g *Game) handleOperate(c *network.Conn, msg interface{}) error { operate := msg.(messages.Operate) log.WithField("msg", operate).Info("Operate message") g.eventManager.PostEvent( events.NewEvent(events.PlayerOperateId, events.PlayerOperate{ Id: c.GetUserData().(protocol.ClientData).Id, EntityId: operate.Id, })) return nil }
/* * handleAttack processes a Attack message and fires a PlayerAttack event */ func (g *Game) handleAttack(c *network.Conn, msg interface{}) error { attack := msg.(messages.Attack) log.WithField("msg", attack).Info("Attack message") g.eventManager.PostEvent( events.NewEvent(events.PlayerAttackId, events.PlayerAttack{ Id: c.GetUserData().(protocol.ClientData).Id, EntityId: attack.Id, })) return nil }
/* * handleRepair processes a Repair message and fires a PlayerRepair event */ func (g *Game) handleRepair(c *network.Conn, msg interface{}) error { repair := msg.(messages.Repair) log.WithField("msg", repair).Info("Repair message") g.eventManager.PostEvent( events.NewEvent(events.PlayerRepairId, events.PlayerRepair{ Id: c.GetUserData().(protocol.ClientData).Id, BuildingId: repair.Id, })) return nil }
/* * handleBuild processes a Build message and fires a PlayerBuild event */ func (g *Game) handleBuild(c *network.Conn, msg interface{}) error { build := msg.(messages.Build) log.WithField("msg", build).Info("Build message") g.eventManager.PostEvent( events.NewEvent(events.PlayerBuildId, events.PlayerBuild{ Id: c.GetUserData().(protocol.ClientData).Id, Type: build.Type, Xpos: build.Xpos, Ypos: build.Ypos, })) return nil }
/* * handleMove processes a Move message and fires a PlayerMove event */ func (g *Game) handleMove(c *network.Conn, msg interface{}) error { move := msg.(messages.Move) log.WithField("msg", move).Info("Move message") g.eventManager.PostEvent( events.NewEvent( events.PlayerMoveId, events.PlayerMove{ Id: (c.GetUserData().(protocol.ClientData)).Id, Xpos: move.Xpos, Ypos: move.Ypos, })) return nil }
/* * Leave sends a LEAVE message to the client associated to given connection */ func (reg *ClientRegistry) Leave(reason string, c *network.Conn) { clientData := c.GetUserData().(ClientData) // send LEAVE to client leave := messages.New(messages.LeaveId, messages.Leave{ Id: uint32(clientData.Id), Reason: reason, }) if err := c.AsyncSendPacket(leave, 5*time.Millisecond); err != nil { // either the client received the LEAVE or not, we will close the // connection afterwards, so there's nothing more to do in order to // gracefully handle this error log.WithError(err).WithField("clientID", clientData.Id).Error("LEAVE message couldn't be sent") } else { log.WithField("clientID", clientData.Id).Info("LEAVE message has been sent") } // TODO: Remove this: the client should remain alive even when the connection // is closed. Example: when the lobby will be implemented // closes the connection, registry cleanup will be performed in OnClose go func() { time.Sleep(100 * time.Millisecond) if !c.IsClosed() { c.Close() } }() }
/* * OnIncomingMsg gets called by the server each time a message has been read * from the connection */ func (srv *Server) OnIncomingPacket(c *network.Conn, packet network.Packet) bool { clientData := c.GetUserData().(ClientData) raw := packet.(*messages.Message) log.WithFields( log.Fields{ "clientData": clientData, "addr": c.GetRawConn().RemoteAddr(), "type": raw.Type.String(), }).Debug("Incoming message") // decode the raw message msg := srv.factory.Decode(raw) // get handler handler, ok := srv.msgHandlers[raw.Type] if ok { if err := handler(c, msg); err != nil { log.WithError(err).Error("Error handling message") return false } } else { switch raw.Type { case messages.PingId: // immediately reply pong ping := msg.(messages.Ping) pong := messages.New(messages.PongId, messages.Pong{ Id: ping.Id, Tstamp: time.Now().UnixNano() / int64(time.Millisecond), }) if err := c.AsyncSendPacket(pong, time.Second); err != nil { log.WithError(err).Error("Error handling message") return false } case messages.JoinId: join := msg.(messages.Join) // JOIN is handled by the handshaker if srv.handshaker.Join(join, c) { // new client has been accepted if srv.playerJoinedCb != nil { // raise 'player joined' external callback srv.playerJoinedCb(clientData.Id, join.Type) } } } } return true }
func (reg *ClientRegistry) Join(join messages.Join, c *network.Conn) bool { clientData := c.GetUserData().(ClientData) log.WithFields(log.Fields{"name": join.Name, "clientData": clientData}).Info("Received JOIN from client") // client already JOINED? if clientData.Joined { reg.Leave("Joined already received", c) return false } // name length condition if len(join.Name) < 3 { reg.Leave("Name is too short", c) return false } // name already taken? nameTaken := false playerNames := make(map[uint32]string) // compute the list of joined players, populating the STAY message and // checking if name is taken as well reg.ForEach(func(cd ClientData) bool { nameTaken = cd.Name == join.Name playerNames[cd.Id] = cd.Name // stop iteration if name is taken return !nameTaken }) if nameTaken { reg.Leave("Name is already taken", c) return false } // create and send STAY to the new client stay := messages.Stay{Id: clientData.Id, Players: playerNames} err := c.AsyncSendPacket(messages.New(messages.StayId, stay), time.Second) if err != nil { // handle error in case we couldn't send the STAY message log.WithError(err).Error("Couldn't send STAY message to the new client") reg.Leave("Couldn't finish handshaking", c) return false } // fill a JOINED message joined := &messages.Joined{ Id: clientData.Id, Name: join.Name, Type: join.Type, } log.WithField("joined", joined).Info("Tell to the world this client has joined") reg.Broadcast(messages.New(messages.JoinedId, joined)) // at this point we consider the client as accepted clientData.Joined = true clientData.Name = join.Name c.SetUserData(clientData) return true }