Improved server

This commit is contained in:
Eduard Urbach 2024-01-26 00:29:05 +01:00
parent 816337ede5
commit 0172a81bdf
Signed by: akyoto
GPG Key ID: C874F672B1AF20C0
17 changed files with 286 additions and 181 deletions

View File

@ -1,8 +1,11 @@
extends PacketHandler
var auth_token: String
var instance_id := OS.get_process_id() % 4
var username := "user%d" % instance_id
func _ready():
DisplayServer.window_set_title(username)
send_login()
func handle_packet(data: PackedByteArray, _peer: PacketPeer):
@ -17,10 +20,11 @@ func handle_packet(data: PackedByteArray, _peer: PacketPeer):
func send_login():
if is_logged_in():
return
var password := "password"
var buffer := StreamPeerBuffer.new()
buffer.put_8(Packet.LOGIN)
buffer.put_data(JSON.stringify(["user1", "password"]).to_utf8_buffer())
buffer.put_data(JSON.stringify([username, password]).to_utf8_buffer())
%Client.socket.put_packet(buffer.data_array)
print("[Client] Connecting...")

View File

@ -17,9 +17,14 @@ func handle_packet(data: PackedByteArray, _peer: PacketPeer):
print(server_position)
var player := spawn_player()
player.name = player_name
player.position = server_position
Global.player = player
player.set_character_name(player_name)
if false:
Global.player = player
var controller := PlayerController.new()
controller.character = Global.player
Global.player.add_child(controller)
func spawn_player() -> Player:
var player = player_scene.instantiate()

View File

@ -1,7 +1,6 @@
class_name Player
extends Character
func _ready():
var controller := PlayerController.new()
controller.character = self
add_child(controller)
func set_character_name(new_name: String):
name = new_name
get_node("Label").text = name

View File

@ -1,8 +1,7 @@
[gd_scene load_steps=6 format=3 uid="uid://2lcnu3dy54lx"]
[gd_scene load_steps=5 format=3 uid="uid://2lcnu3dy54lx"]
[ext_resource type="Script" path="res://player/Player.gd" id="1_8gebs"]
[ext_resource type="PackedScene" uid="uid://2bbycjulf00g" path="res://character/health/HealthComponent.tscn" id="2_np5ag"]
[ext_resource type="Script" path="res://player/controller/PlayerController.gd" id="3_oox5k"]
[sub_resource type="PrismMesh" id="PrismMesh_y7abh"]
size = Vector3(0.5, 1.6, 0.5)
@ -29,6 +28,7 @@ shape = SubResource("CapsuleShape3D_2f50n")
[node name="Health" parent="." instance=ExtResource("2_np5ag")]
[node name="Controller" type="Node" parent="." node_paths=PackedStringArray("character")]
script = ExtResource("3_oox5k")
character = NodePath("..")
[node name="Label" type="Label3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.8, 0)
billboard = 1
text = "ABC"

View File

@ -68,10 +68,10 @@ camera_attributes = ExtResource("9_w4cdu")
[node name="Trees" type="Node3D" parent="World"]
[node name="Tree" parent="World/Trees" instance=ExtResource("11_wlyv1")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5.11323, 0, -4.64839)
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5.11323, 0, -7.57074)
[node name="Tree2" parent="World/Trees" instance=ExtResource("11_wlyv1")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5.11323, 0, 5.35161)
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5.11323, 0, 7.80548)
[node name="Enemies" type="Node3D" parent="World"]
@ -118,6 +118,7 @@ follow_speed = 5.0
[node name="PostProcessing" type="MeshInstance3D" parent="Viewport/SubViewport/CameraPivot/Camera"]
unique_name_in_owner = true
visible = false
extra_cull_margin = 16384.0
mesh = SubResource("QuadMesh_7yiqd")

View File

@ -1,31 +0,0 @@
package game
import (
"net"
"time"
)
// Client represents a logged in client.
type Client struct {
address *net.UDPAddr
lastPacket time.Time
authToken string
player Player
}
// NewClient creates a new client.
func NewClient(address *net.UDPAddr) *Client {
return &Client{
address: address,
}
}
// String shows the client address.
func (c *Client) String() string {
return c.address.String()
}
// KeepAlive sets the last packet time to now.
func (c *Client) KeepAlive() {
c.lastPacket = time.Now()
}

67
server/game/Game.go Normal file
View File

@ -0,0 +1,67 @@
package game
import (
"fmt"
"os"
"os/signal"
"server/game/packet"
"time"
)
// Game represents the entire state of the game server.
type Game struct {
server *Server
players *PlayerManager
}
// New creates a new game.
func New() *Game {
return &Game{
server: NewServer(),
players: NewPlayerManager(),
}
}
// Run starts all game systems.
func (game *Game) Run() {
game.start()
close := make(chan os.Signal, 1)
signal.Notify(close, os.Interrupt)
<-close
}
// start starts all game systems.
func (game *Game) start() {
go game.network()
go game.physics()
go game.statistics()
}
// network will listen for new packets and process them.
func (game *Game) network() {
game.server.SetHandler(packet.Ping, game.Ping)
game.server.SetHandler(packet.Login, game.Login)
game.server.Run(4242)
}
// physics periodically runs the Tick function for each player.
func (game *Game) physics() {
updater := time.NewTicker(20 * time.Millisecond)
for range updater.C {
game.players.Each(func(c *Player) bool {
c.Tick()
return true
})
}
}
// statistics periodically shows server statistics on the command line.
func (game *Game) statistics() {
ticker := time.NewTicker(time.Second)
for range ticker.C {
fmt.Printf("%d players | %d packets\n", game.players.Count(), game.server.PacketCount())
game.server.ResetPacketCount()
}
}

View File

@ -1,4 +1,4 @@
package core
package game
import "net"

View File

@ -3,70 +3,78 @@ package game
import (
"crypto/rand"
"encoding/base64"
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"math"
"net"
"server/core"
"server/packet"
"server/game/packet"
)
var (
Clients = NewManager()
LoginSuccess = []byte{0}
LoginFailure = []byte{1}
)
// Login checks the account credentials and gives a network peer access to an account.
func Login(data []byte, address *net.UDPAddr, server *core.Server) error {
var loginRequest [2]string
err := json.Unmarshal(data, &loginRequest)
func (game *Game) Login(data []byte, address *net.UDPAddr, server *Server) error {
username, password, err := getLoginData(data)
if err != nil {
server.Send(packet.Login, LoginFailure, address)
return err
}
username := loginRequest[0]
password := loginRequest[1]
if password != "password" {
server.Send(packet.Login, LoginFailure, address)
return errors.New("login failure")
}
randomBytes := make([]byte, 32)
_, err = rand.Read(randomBytes)
player := game.players.Get(address)
player.AuthToken = createAuthToken()
player.Name = username
player.KeepAlive()
if err != nil {
server.Send(packet.Login, LoginFailure, address)
return err
if username == "user0" {
player.Position.X = 5.0
}
client := Clients.Get(address)
client.KeepAlive()
client.authToken = base64.StdEncoding.EncodeToString(randomBytes)
client.player.Name = username
client.player.Position.X = 5.0
playerState := []byte(client.player.Name + "\u0000")
playerState = appendVector3(playerState, client.player.Position)
if username == "user1" {
player.Position.X = -5.0
}
server.Send(packet.Login, append(LoginSuccess, []byte(client.authToken)...), address)
server.Send(packet.PlayerState, playerState, address)
if username == "user2" {
player.Position.Z = -5.0
}
if username == "user3" {
player.Position.Z = 5.0
}
server.Send(packet.Login, append(LoginSuccess, []byte(player.AuthToken)...), address)
player.OnConnect()
game.players.Each(func(other *Player) bool {
server.Send(packet.PlayerState, other.State(), address)
return true
})
fmt.Printf("%s logged in.\n", username)
return nil
}
func appendVector3(data []byte, vector Vector3) []byte {
bits := math.Float32bits(vector.X)
data = binary.LittleEndian.AppendUint32(data, bits)
func getLoginData(data []byte) (string, string, error) {
loginRequest := [2]string{}
err := json.Unmarshal(data, &loginRequest)
bits = math.Float32bits(vector.Y)
data = binary.LittleEndian.AppendUint32(data, bits)
if err != nil {
return "", "", err
}
bits = math.Float32bits(vector.Z)
return binary.LittleEndian.AppendUint32(data, bits)
username := loginRequest[0]
password := loginRequest[1]
return username, password, nil
}
func createAuthToken() string {
randomBytes := make([]byte, 32)
rand.Read(randomBytes)
return base64.StdEncoding.EncodeToString(randomBytes)
}

View File

@ -1,68 +0,0 @@
package game
import (
"net"
"sync"
"sync/atomic"
"time"
)
// Manager keeps tracks of all player connections.
type Manager struct {
clients sync.Map
count atomic.Int64
}
// NewManager creates a new manager.
func NewManager() *Manager {
manager := &Manager{}
timeout := 5 * time.Second
interval := time.Second
go func() {
ticker := time.NewTicker(interval)
defer ticker.Stop()
for range ticker.C {
now := time.Now()
manager.clients.Range(func(key, value interface{}) bool {
item := value.(*Client)
if !item.lastPacket.IsZero() && now.After(item.lastPacket.Add(timeout)) {
manager.clients.Delete(key)
manager.count.Add(-1)
}
return true
})
}
}()
return manager
}
// Get either returns a new or existing client for the requested address.
func (m *Manager) Get(addr *net.UDPAddr) *Client {
obj, exists := m.clients.Load(addr.String())
if exists {
return obj.(*Client)
}
client := NewClient(addr)
m.clients.Store(addr.String(), client)
m.count.Add(1)
return client
}
// Contains tells you whether the address is already a registered client.
func (m *Manager) Contains(addr *net.UDPAddr) bool {
_, exists := m.clients.Load(addr.String())
return exists
}
// Count returns the number of clients.
func (m *Manager) Count() int {
return int(m.count.Load())
}

View File

@ -2,16 +2,15 @@ package game
import (
"net"
"server/core"
"server/packet"
"server/game/packet"
)
// Ping is used as a heartbeat and latency check.
func Ping(data []byte, address *net.UDPAddr, server *core.Server) error {
func (game *Game) Ping(data []byte, address *net.UDPAddr, server *Server) error {
server.Send(packet.Ping, data, address)
if Clients.Contains(address) {
Clients.Get(address).KeepAlive()
if game.players.Contains(address) {
game.players.Get(address).KeepAlive()
}
return nil

View File

@ -1,6 +1,53 @@
package game
import (
"fmt"
"net"
"time"
)
// Player represents a logged in client.
type Player struct {
Name string `json:"name"`
Position Vector3 `json:"position"`
Name string `json:"name"`
Position Vector3 `json:"position"`
AuthToken string
address *net.UDPAddr
lastPacket time.Time
}
// NewClient creates a new client.
func NewClient(address *net.UDPAddr) *Player {
return &Player{
address: address,
}
}
// String shows the client address.
func (c *Player) String() string {
return c.address.String()
}
// KeepAlive sets the last packet time to now.
func (c *Player) KeepAlive() {
c.lastPacket = time.Now()
}
// Tick is run on every tick.
func (c *Player) Tick() {
// ...
}
// State returns the player state (name and position).
func (player *Player) State() []byte {
state := []byte(player.Name + "\u0000")
state = appendVector3(state, player.Position)
return state
}
func (player *Player) OnConnect() {
fmt.Printf("%s connected.\n", player.Name)
}
func (player *Player) OnDisconnect() {
fmt.Printf("%s disconnected.\n", player.Name)
}

View File

@ -0,0 +1,76 @@
package game
import (
"net"
"sync"
"sync/atomic"
"time"
)
// PlayerManager keeps tracks of all players.
type PlayerManager struct {
players sync.Map
count atomic.Int64
}
// NewPlayerManager creates a new player manager.
func NewPlayerManager() *PlayerManager {
m := &PlayerManager{}
timeout := 5 * time.Second
interval := time.Second
go func() {
ticker := time.NewTicker(interval)
defer ticker.Stop()
for range ticker.C {
now := time.Now()
m.players.Range(func(key, value interface{}) bool {
player := value.(*Player)
if !player.lastPacket.IsZero() && now.After(player.lastPacket.Add(timeout)) {
player.OnDisconnect()
m.players.Delete(key)
m.count.Add(-1)
}
return true
})
}
}()
return m
}
// Contains tells you whether the address is already a registered client.
func (m *PlayerManager) Contains(addr *net.UDPAddr) bool {
_, exists := m.players.Load(addr.String())
return exists
}
// Count returns the number of clients.
func (m *PlayerManager) Count() int {
return int(m.count.Load())
}
// Each calls the callback function for each client.
func (m *PlayerManager) Each(callback func(*Player) bool) {
m.players.Range(func(key, value any) bool {
return callback(value.(*Player))
})
}
// Get either returns a new or existing client for the requested address.
func (m *PlayerManager) Get(addr *net.UDPAddr) *Player {
obj, exists := m.players.Load(addr.String())
if exists {
return obj.(*Player)
}
client := NewClient(addr)
m.players.Store(addr.String(), client)
m.count.Add(1)
return client
}

View File

@ -1,4 +1,4 @@
package core
package game
import (
"fmt"
@ -12,8 +12,8 @@ type Server struct {
packetCount int
}
// New creates a new server.
func New() *Server {
// NewServer creates a new server.
func NewServer() *Server {
return &Server{}
}
@ -66,7 +66,7 @@ func listen(port int) *net.UDPConn {
// read is a blocking call which will read incoming packets and handle them.
func (s *Server) read() {
buffer := make([]byte, 4096)
buffer := make([]byte, 16384)
for {
n, addr, err := s.socket.ReadFromUDP(buffer)

View File

@ -1,7 +1,25 @@
package game
import (
"encoding/binary"
"math"
)
// Vector3 represents a 3-dimensional vector using 32-bit float precision.
type Vector3 struct {
X float32 `json:"x"`
Y float32 `json:"y"`
Z float32 `json:"z"`
}
// appendVector3 adds the raw bits of the vector to the given byte slice in XYZ order.
func appendVector3(data []byte, vector Vector3) []byte {
bits := math.Float32bits(vector.X)
data = binary.LittleEndian.AppendUint32(data, bits)
bits = math.Float32bits(vector.Y)
data = binary.LittleEndian.AppendUint32(data, bits)
bits = math.Float32bits(vector.Z)
return binary.LittleEndian.AppendUint32(data, bits)
}

View File

@ -1,29 +1,9 @@
package main
import (
"fmt"
"server/core"
"server/game"
"server/packet"
"time"
)
func main() {
// Init server
server := core.New()
server.SetHandler(packet.Ping, game.Ping)
server.SetHandler(packet.Login, game.Login)
// Show statistics
ticker := time.NewTicker(time.Second)
go func() {
for range ticker.C {
fmt.Printf("%d packets per second, %d clients\n", server.PacketCount(), game.Clients.Count())
server.ResetPacketCount()
}
}()
// Start listening
server.Run(4242)
game.New().Run()
}