Как правильно структурировать игровой сервер, используя сокет Python3 - PullRequest
1 голос
/ 16 марта 2020

У меня проблемы с тем, как правильно построить игровой сервер, используя библиотеку сокетов python3. Моя игра - серверная игра, в которой клиент отправляет на сервер команды basi c, которые интерпретируются и запускаются сервером. Затем сервер возвращает GameState-Object клиенту.

Поскольку игра представляет собой серверную игру, на серверной части выполняется Game-Instace, которая включает в себя gamel oop и игровую логику * 1068. *.

В данный момент код работает, но я бы хотел, чтобы класс Server и класс Game были двумя независимыми единицами (или классами), но они не правы сейчас же. Таким образом, я мог бы просто скопировать и вставить свой класс Game-Server в другие проекты без изменений.

Прямо сейчас моя установка такова («->» означает «владеет экземпляром» of):

Сервер -> Игра -> (игровые объекты)

Однако я бы хотел, чтобы это было так:

Игра -> Сервер

Игра -> Игровые объекты

Моя цель состоит в том, чтобы сделать сервер независимым модулем, который может быть создан и использован несколькими игровыми объектами. «Разъедините» сервер и игру, так сказать.

Я предоставлю код из Server, Client и GameInstance ниже и опишу , почему я еще не смог решить эту проблему:

import sys
import pickle
from _thread import *
from ..Settings.Settings import *
from ..Events.EventBus import EventBus
from ..Events.Event import Event
from ..Events.EventType import EventType
from ..API_Entities.Tank import Tank
from ..Events.IEventListener import IEventListener
from ..Clients.ConnectedClient import ConnectedClient
from .GameState import GameState
from ..CommandIntepreter.ClientCommandInterpreter import ClientCommandInterpreter

class Game():
    def __init__(self):
        pygame.init()
        pygame.mixer.quit()
        self.clock = pygame.time.Clock()

        self.state = GameState.Paused
        self.clients = {}

        self.__EventBus = EventBus()
        self.__EventBus.InitializeEventBus()

        start_new_thread(self.GameLoop,())

    def StartGameLoop(self):
        self.state = GameState.Running

    def PauseGameLoop(self):
        self.state = GameState.Paused

    def GameLoop(self):
        while True:
            if(self.state == GameState.Running):
                self.dt = self.clock.tick(FPS)
                self.__EventBus.ProcessEvents()

    ### NOT USED FOR NOW
    def UpdateClients(self, clients : list):
        self.clients = clients

    def AddClient(self, id : int) -> None:
        NewClient = ConnectedClient(id)
        self.__EventBus.SubscribeMultiple([EventType.Movement,
            EventType.Rotation,EventType.Shooting],NewClient.Tank)
        self.clients[id] = NewClient

    def RemoveClient(self, id : int) -> None:
        DisconnectedClient = self.clients[id]
        self.__EventBus.UnsubscribeAll(DisconnectedClient.Tank)
        del self.clients[id]


    def ProcessServerCommands(self, msg : str):
        if msg == "pause":
            self.PauseGameLoop()
        elif msg == "start":
            self.StartGameLoop()
        elif msg == "status":
            print(self.state)
        elif msg == "players":
            print(len(self.clients))
        elif msg == "sub":
            __tank = Tank(1,360,0)
            self.__EventBus.Subscribe(EventType.GameState,__tank)
        elif msg == "post":
            self.PostEvent(Event(EventType.GameState,"Heeey!",0))
        elif msg == "move":
            self.PostEvent(Event(EventType.Movement,"Moving!",0))
        elif msg == "reply":
            self.PostEvent(Event(EventType.Output,"Hi Server!",0))
        elif msg == "map":
            self.PostEvent(Event(EventType.Output,"map"),0)

    def PostEvent(self,_Event : Event): 
        if self.state == GameState.Running:
            self.__EventBus.PostEvent(_Event)

    def InterpretUserInput(self, UserInput, id : int):
        ClientCommandInterpreter.InterpretateCommand(UserInput,id)
        return "Hej : Fra serveren"

    def AddServerToEventBus(self, _Server):
        self.__EventBus.Subscribe(EventType.Output, _Server)

    def ChangeMap(self):
        return "New map"


# TODO
# Add Running,Pause to __EventBus
# ADD list of EventTypes to subscribe to in IEventListener 
# Call InterpretUserInput from Server - add method here
import pickle
from _thread import *
import socket
from ..Clients.ConnectedClient import ConnectedClient
from ..Settings.Settings import *
from ..Game.Game import Game
from ..Events.EventBus import EventBus
from ..Events.IEventListener import IEventListener
from ..Events.Event import Event
from ..Events.EventType import EventType
import typing

class Server(IEventListener):
    def __init__(self,Ip_address : str, Port : str):
        self.Port = Port
        self.Ip_address = Ip_address
        self.Connections = {}

        self.Game = Game()
        self.Game.AddServerToEventBus(self)

        self.Running = True 

        self.Server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        self.Server.setsockopt(socket.SOL_IP,socket.SO_REUSEADDR,1)

    def StartServer(self):
        try:
            self.Server.bind((self.Ip_address,self.Port))
        except socket.error as e:
            str(e)

        self.Server.listen()
        start_new_thread(self.CommandLoop,())
        print('Waiting for connection. Server started at ip:',self.Ip_address)

    def ServerLoop(self):
        CurrentPlayerId = 0

        while self.Running:
            print("serverloop running")
            conn, addr = self.Server.accept()
            print("Connected to", addr)
            ###
            start_new_thread(self.__ThreadedClient,(conn,CurrentPlayerId))
            CurrentPlayerId += 1

    def CommandLoop(self):
        while True:
            Command = input("Command : ")
            self.Game.ProcessServerCommands(Command)

    def __ThreadedClient(self, connection, playerid : int): 
        self.__AddConnection(connection,playerid)
        ### dummy for test
        connection.send(pickle.dumps("start"))

        while True:
            data = self.RecieveFromClient(connection)
            if not data:
                break
            else:
                self.SendToClient(
                        connection,self.Game.InterpretUserInput(data,playerid))

        ## On disconnection
        self.__RemoveConnection(playerid)
        print("Disconnected : ",connection)

    def __AddConnection(self, connection, playerid : int):
        self.Connections[playerid] = connection
        self.Game.AddClient(playerid)

    def __RemoveConnection(self,playerid : int):
        del self.Connections[playerid]
        self.Game.RemoveClient(playerid)

    def __PostEventToGame(self,command : str):
        _event = None

    def SendToClient(self, connection : socket, data):
        try:
            connection.sendall(pickle.dumps(data))
            return True
        except:
            return False

    def SendToClientById(self, playerid : id, data):
        return self.SendToClient(self.Connections[playerid],data)

    def SendToAllClients(self, data):
        for key in self.Connections:
            self.SendToClient(self.Connections[key],data)

    def RecieveFromClient(self, connection : socket):
        try:
            return pickle.loads(connection.recv(2048))
        except Exception as e:
            return False

    def RecieveFromClientById(self, playerid : id):
        return self.RecieveFromClient(self.Connections[playerid])

    def ProcessEvent(self, ET : EventType, EV : Event):
        if ET == EventType.Output:
            if Ev.msg == "map":
                print("Server : Changed map")
                self.SendToAllClients(self.Game.ChangeMap())


import pickle
from .ip_address import Ip_address

#Client
class Network:
    def __init__(self,serverip):
        self.client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        self.serverip = serverip
        self.port = 5555
        self.addr = (self.serverip,self.port)
        self.p = self.connect()

    def connect(self):
        try:
            self.client.connect(self.addr)
            return pickle.loads(self.client.recv(2048))
        except:
            print("server not found!")

    def getInit(self):
        return self.p

    def send(self,data):
        try:
            self.client.send(pickle.dumps(data))
            return pickle.loads(self.client.recv(2048))
        except socket.error as e:
            print(e)

Проблема 1: В классе Server.py класс ThreadedClient () fun c обрабатывает соединение (передачу данных) между сервером и клиент. Клиент ожидает, что Сервер ответит данными из Game-класса, однако Сервер не должен получать доступ к данным из Game, если я хочу полное разъединение.

Проблема 2: Сервер, Все клиенты и игры выполняются в разных циклах и поэтому не синхронизированы.

Проблема 3: Я хочу использовать Server.SendToClient (params) для связи с Game -> Server -> Клиент - однако Сервер должен ответить клиенту сразу из _ThreadedClient (params) и поэтому не может полагаться на функцию SendToClient (params).

Я подумал о следующих решениях; однако я хотел бы знать хороший и правильный способ решения этой проблемы:

Решение 1: Наличие некоторого объекта Data-Queue-Object на сервере для обработки асинхронизации между, Клиент, сервер и игра.

Решение 2: Используйте забаву ThreadedClient (params) c, чтобы просто пропинговать между клиентом и сервером для поддержания соединения. Затем Server.SendToClient (params) можно изменить для обработки «реальной» передачи данных между Клиентом и Сервером.

Решение 3: Создать GameInstanceInterface, используемый Сервером, для обработки вызовов функций с сервера на игру. Он не будет разделен на 100%, однако сервер с GameInstanceInterface можно легко скопировать в другие игры.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...