/* * 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 }