У меня проблемы с тем, как правильно построить игровой сервер, используя библиотеку сокетов 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 можно легко скопировать в другие игры.