added SimplePacket and VariableLengthBytesPacket to simplify some Packet type

This commit is contained in:
Faraphel 2023-02-28 18:15:08 +01:00
parent 37350926b2
commit b7d147fc3c
12 changed files with 134 additions and 88 deletions

View file

@ -4,7 +4,6 @@ from typing import TYPE_CHECKING, Any
from source.gui import scene from source.gui import scene
from source.network import game_network from source.network import game_network
from source.network.packet import PacketUsername, PacketSettings from source.network.packet import PacketUsername, PacketSettings
from source.network.packet.abc import Packet
from source.utils import StoppableThread from source.utils import StoppableThread
from source.utils.thread import in_pyglet_context from source.utils.thread import in_pyglet_context
@ -35,7 +34,7 @@ class Client(StoppableThread):
print(f"[Client] Connecté avec {connection}") print(f"[Client] Connecté avec {connection}")
settings: Any = PacketSettings.from_connection(connection) settings: Any = PacketSettings.from_connection(connection)
PacketUsername(username=self.username).instance_send_connection(connection) PacketUsername(username=self.username).send_data_connection(connection)
packet_username = PacketUsername.from_connection(connection) packet_username = PacketUsername.from_connection(connection)
game_scene = in_pyglet_context( game_scene = in_pyglet_context(

View file

@ -44,9 +44,9 @@ class Host(StoppableThread):
print(f"[Serveur] Connecté avec {address}") print(f"[Serveur] Connecté avec {address}")
self.settings.instance_send_connection(connection) self.settings.send_data_connection(connection)
packet_username = PacketUsername.from_connection(connection) packet_username = PacketUsername.from_connection(connection)
PacketUsername(username=self.username).instance_send_connection(connection) PacketUsername(username=self.username).send_data_connection(connection)
game_scene = in_pyglet_context( game_scene = in_pyglet_context(
self.window.set_scene, self.window.set_scene,

View file

@ -1,17 +1,17 @@
from dataclasses import dataclass from dataclasses import dataclass
from source.network.packet.abc import Packet from source.network.packet.abc import SimplePacket
@dataclass @dataclass
class PacketBoatPlaced(Packet): class PacketBoatPlaced(SimplePacket):
""" """
A packet that signal that all the boat of the player have been placed A packet that signal that all the boat of the player have been placed
""" """
def to_bytes(self): def to_bytes(self) -> bytes:
return b"" return b""
@classmethod @classmethod
def from_bytes(cls, data: bytes): def from_bytes(cls, data: bytes) -> "PacketBoatPlaced":
return cls() return cls()

View file

@ -2,12 +2,12 @@ import struct
from dataclasses import dataclass, field from dataclasses import dataclass, field
from source.network.packet.abc import Packet from source.network.packet.abc import SimplePacket
from source.type import Point2D from source.type import Point2D
@dataclass @dataclass
class PacketBombPlaced(Packet): class PacketBombPlaced(SimplePacket):
""" """
A packet that signal that a bomb have been placed on the board A packet that signal that a bomb have been placed on the board
""" """
@ -16,11 +16,11 @@ class PacketBombPlaced(Packet):
packet_format: str = ">BB" packet_format: str = ">BB"
def to_bytes(self): def to_bytes(self) -> bytes:
x, y = self.position x, y = self.position
return struct.pack(self.packet_format, x, y) return struct.pack(self.packet_format, x, y)
@classmethod @classmethod
def from_bytes(cls, data: bytes): def from_bytes(cls, data: bytes) -> "PacketBombPlaced":
x, y = struct.unpack(cls.packet_format, data) x, y = struct.unpack(cls.packet_format, data)
return cls(position=(x, y)) return cls(position=(x, y))

View file

@ -2,12 +2,12 @@ import struct
from dataclasses import dataclass, field from dataclasses import dataclass, field
from source.core.enums import BombState from source.core.enums import BombState
from source.network.packet.abc import Packet from source.network.packet.abc import SimplePacket
from source.type import Point2D from source.type import Point2D
@dataclass @dataclass
class PacketBombState(Packet): class PacketBombState(SimplePacket):
""" """
A packet that signal how a bomb exploded on the board A packet that signal how a bomb exploded on the board
""" """
@ -17,11 +17,11 @@ class PacketBombState(Packet):
packet_format: str = ">BBb" packet_format: str = ">BBb"
def to_bytes(self): def to_bytes(self) -> bytes:
x, y = self.position x, y = self.position
return struct.pack(self.packet_format, x, y, self.bomb_state.value) return struct.pack(self.packet_format, x, y, self.bomb_state.value)
@classmethod @classmethod
def from_bytes(cls, data: bytes): def from_bytes(cls, data: bytes) -> "PacketBombState":
x, y, bomb_state = struct.unpack(cls.packet_format, data) x, y, bomb_state = struct.unpack(cls.packet_format, data)
return cls(position=(x, y), bomb_state=BombState(bomb_state)) return cls(position=(x, y), bomb_state=BombState(bomb_state))

View file

@ -1,40 +1,20 @@
import socket
import struct
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import Optional
from source.network.packet.abc import Packet from source.network.packet.abc import VariableLengthBytesPacket
@dataclass @dataclass
class PacketChat(Packet): class PacketChat(VariableLengthBytesPacket):
""" """
A packet that represent a message from the chat A packet that represent a message from the chat
""" """
message: str = field() message: str = field()
packet_format = ">I" @property
def data(self) -> bytes:
def to_bytes(self) -> bytes: return self.message.encode("utf-8")
message: bytes = self.message.encode("utf-8")
message_len: int = len(message)
# envoie la taille du message, suivi des données du message
return struct.pack(f"{self.packet_format}{message_len}s", message_len, message)
@classmethod @classmethod
def from_connection(cls, connection: socket.socket) -> "Packet": def from_bytes(cls, data: bytes) -> "PacketChat":
message_len, *_ = struct.unpack( return cls(message=data.decode("utf-8"))
cls.packet_format,
connection.recv(struct.calcsize(cls.packet_format))
)
format_: str = f">{message_len}s"
message, *_ = struct.unpack(
format_,
connection.recv(struct.calcsize(format_))
)
return cls(message=message.decode("utf-8"))

View file

@ -14,7 +14,7 @@ class PacketSettings(Packet):
packet_format: str = ">BB?I" packet_format: str = ">BB?I"
def to_bytes(self): def to_bytes(self) -> bytes:
boats_len: int = len(self.boats_length) boats_len: int = len(self.boats_length)
return struct.pack( return struct.pack(
@ -30,7 +30,7 @@ class PacketSettings(Packet):
) )
@classmethod @classmethod
def from_connection(cls, connection: socket.socket) -> "Packet": def from_connection(cls, connection: socket.socket) -> "PacketSettings":
grid_width, grid_height, host_start, boats_len = struct.unpack( grid_width, grid_height, host_start, boats_len = struct.unpack(
cls.packet_format, cls.packet_format,
connection.recv(struct.calcsize(cls.packet_format)) connection.recv(struct.calcsize(cls.packet_format))

View file

@ -1,34 +1,17 @@
import socket
import struct
from dataclasses import dataclass, field from dataclasses import dataclass, field
from source.network.packet.abc import Packet from source.network.packet.abc import VariableLengthBytesPacket
@dataclass @dataclass
class PacketUsername(Packet): class PacketUsername(VariableLengthBytesPacket):
username: str = field() username: str = field()
packet_format: str = ">I" @property
def data(self) -> bytes:
def to_bytes(self): return self.username.encode("utf-8")
username = self.username.encode()
username_len = len(username)
return struct.pack(f"{self.packet_format}{username_len}s", username_len, username)
@classmethod @classmethod
def from_connection(cls, connection: socket.socket) -> "PacketUsername": def from_bytes(cls, data: bytes) -> "PacketUsername":
username_len, *_ = struct.unpack( return cls(username=data.decode("utf-8"))
cls.packet_format,
connection.recv(struct.calcsize(cls.packet_format))
)
format_: str = f">{username_len}s"
username, *_ = struct.unpack(
format_,
connection.recv(struct.calcsize(format_))
)
return cls(username=username.decode("utf-8"))

View file

@ -1,18 +1,30 @@
import socket import socket
import struct
from abc import abstractmethod, ABC from abc import abstractmethod, ABC
from inspect import isabstract
from typing import Type, Optional from typing import Type, Optional
class Packet(ABC): class Packet(ABC):
packed_header: bytes """
A packet that can be sent on a socket.
Multiple subtype of packet can be sent and received in an easier way.
The to_bytes and from_connection method need to be defined.
"""
packet_types: set[Type["Packet"]] = set()
packet_header: bytes
packet_id: int = 0 packet_id: int = 0
packet_format: str = ">"
def __init_subclass__(cls, **kwargs): def __init_subclass__(cls, **kwargs):
cls.packet_header = Packet.packet_id.to_bytes(1, "big") # give a header to the newly created subclass cls.packet_header = Packet.packet_id.to_bytes(1, "big") # give a header to the newly created subclass
Packet.packet_id = Packet.packet_id + 1 # increment by one the packet header for the next subclass Packet.packet_id = Packet.packet_id + 1 # increment by one the packet header for the next subclass
if not isabstract(cls):
# si la classe n'est pas abstraite, ajoute-la aux types de packet enregistré.
cls.packet_types.add(cls)
@abstractmethod @abstractmethod
def to_bytes(self) -> bytes: def to_bytes(self) -> bytes:
""" """
@ -23,14 +35,14 @@ class Packet(ABC):
def send_connection(self, connection: socket.socket): def send_connection(self, connection: socket.socket):
""" """
Send the packet directly into a socket. Send the packet data preceded with the header directly into a socket.
:param connection: the socket where to send the packet to. :param connection: the socket where to send the packet to.
""" """
connection.send(self.packet_header + self.to_bytes()) connection.send(self.packet_header + self.to_bytes())
def instance_send_connection(self, connection: socket.socket): def send_data_connection(self, connection: socket.socket):
""" """
Send the packet directly into a socket. Send the packet data directly into a socket.
:param connection: the socket where to send the packet to. :param connection: the socket where to send the packet to.
""" """
connection.send(self.to_bytes()) connection.send(self.to_bytes())
@ -42,20 +54,12 @@ class Packet(ABC):
:param packet_header: the header to find the corresponding subclass :param packet_header: the header to find the corresponding subclass
:return: the class associated with this header :return: the class associated with this header
""" """
return next(filter( return next(filter(
lambda subcls: subcls.packet_header == packet_header, lambda subcls: subcls.packet_header == packet_header,
cls.__subclasses__() cls.packet_types
)) ))
@classmethod
def from_bytes(cls, data: bytes) -> "Packet":
"""
Convert a bytes object into a packet.
:param data: the data to convert into a packet. Should be "packet_size" long.
:return: a packet corresponding to the bytes.
"""
pass
@classmethod @classmethod
def type_from_connection(cls, connection: socket.socket) -> Optional[Type["Packet"]]: def type_from_connection(cls, connection: socket.socket) -> Optional[Type["Packet"]]:
try: try:
@ -67,12 +71,11 @@ class Packet(ABC):
return cls.type_from_header(packet_header) return cls.type_from_header(packet_header)
@classmethod @classmethod
@abstractmethod
def from_connection(cls, connection: socket.socket) -> "Packet": def from_connection(cls, connection: socket.socket) -> "Packet":
""" """
Receive a packet from a socket. Receive a packet from a socket.
:param connection: the socket where to get the data from :param connection: the socket where to get the data from
:return: the packet, or None if there was nothing in the socket to receive. :return: the packet, or None if there was nothing in the socket to receive.
""" """
pass
packet_size: int = struct.calcsize(cls.packet_format)
return cls.from_bytes(connection.recv(packet_size))

View file

@ -0,0 +1,31 @@
import socket
import struct
from abc import ABC, abstractmethod
from source.network.packet.abc import Packet
class SimplePacket(Packet, ABC):
"""
A packet with a simple packet format.
Only the from_bytes and to_bytes method need to be implemented.
"""
packet_format: str = ">"
@classmethod
@abstractmethod
def from_bytes(cls, data: bytes) -> "Packet":
"""
Convert a bytes object into a packet.
:param data: the data to convert into a packet. Should be "packet_size" long.
:return: a packet corresponding to the bytes.
"""
pass
@classmethod
def from_connection(cls, connection: socket.socket) -> "Packet":
# récupère la taille du packet en fonction du format et charge
# les données dans une nouvelle instance.
packet_size: int = struct.calcsize(cls.packet_format)
return cls.from_bytes(connection.recv(packet_size))

View file

@ -0,0 +1,47 @@
import socket
import struct
from abc import ABC, abstractmethod
from source.network.packet.abc import Packet
class VariableLengthBytesPacket(Packet, ABC):
"""
A Packet that represent a single value that can be encoded with a variable length.
The property "data" and the method "from_bytes" need to be defined.
"""
packet_format: str = ">I"
@property
@abstractmethod
def data(self) -> bytes:
pass
def to_bytes(self) -> bytes:
data: bytes = self.data
data_len: int = len(data)
# envoie la taille du message, suivi des données du message
return struct.pack(f"{self.packet_format}{data_len}s", data_len, data)
@classmethod
@abstractmethod
def from_bytes(cls, data: bytes):
pass
@classmethod
def from_connection(cls, connection: socket.socket) -> "Packet":
data_len, *_ = struct.unpack(
cls.packet_format,
connection.recv(struct.calcsize(cls.packet_format))
)
format_: str = f">{data_len}s"
data, *_ = struct.unpack(
format_,
connection.recv(struct.calcsize(format_))
)
return cls.from_bytes(data)

View file

@ -1 +1,4 @@
from .Packet import Packet from .Packet import Packet
from .SimplePacket import SimplePacket
from .VariableLengthBytesPacket import VariableLengthBytesPacket