Skip to content

Commit

Permalink
Example client code refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
ccie18643 committed Sep 9, 2024
1 parent d08feb9 commit a3a305e
Show file tree
Hide file tree
Showing 13 changed files with 249 additions and 195 deletions.
158 changes: 158 additions & 0 deletions examples/lib/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
#!/usr/bin/env python3

################################################################################
## ##
## PyTCP - Python TCP/IP stack ##
## Copyright (C) 2020-present Sebastian Majewski ##
## ##
## This program is free software: you can redistribute it and/or modify ##
## it under the terms of the GNU General Public License as published by ##
## the Free Software Foundation, either version 3 of the License, or ##
## (at your option) any later version. ##
## ##
## This program is distributed in the hope that it will be useful, ##
## but WITHOUT ANY WARRANTY; without even the implied warranty of ##
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ##
## GNU General Public License for more details. ##
## ##
## You should have received a copy of the GNU General Public License ##
## along with this program. If not, see <https://www.gnu.org/licenses/>. ##
## ##
## Author's email: [email protected] ##
## Github repository: https://github.com/ccie18643/PyTCP ##
## ##
################################################################################


"""
The 'user space' generic client base class used in examples.
examples/lib/client.py
ver 3.0.2
"""


from __future__ import annotations

import threading
import time
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING

import click

from pytcp.lib import socket
from pytcp.lib.net_addr.ip_address import IpAddress

if TYPE_CHECKING:
from pytcp.lib.socket import Socket


class Client(ABC):
"""
Generic client support class.
"""

_protocol_name: str
_client_name: str
_local_ip_address: IpAddress
_local_port: int
_remote_ip_address: IpAddress
_remote_port: int
_run_thread: bool

def start(self) -> None:
"""
Start the service thread.
"""

click.echo(
f"Starting the {self._protocol_name} {self._client_name} service."
)
self._run_thread = True
threading.Thread(target=self._thread__client).start()
time.sleep(0.1)

def stop(self) -> None:
"""
Stop the service thread.
"""

click.echo(
f"Stopping the {self._protocol_name} {self._client_name} service."
)
self._run_thread = False
time.sleep(0.1)

def _get_client_socket(self) -> Socket | None:
"""
Create and bind the client socket.
"""

match (
self._local_ip_address.version,
self._remote_ip_address.version,
self._protocol_name,
):
case 6, 6, "TCP":
client_socket = socket.socket(
family=socket.AF_INET6, type=socket.SOCK_STREAM
)
case 4, 4, "TCP":
client_socket = socket.socket(
family=socket.AF_INET4, type=socket.SOCK_STREAM
)
case 6, 6, "UDP":
client_socket = socket.socket(
family=socket.AF_INET6, type=socket.SOCK_DGRAM
)
case 4, 4, "UDP":
client_socket = socket.socket(
family=socket.AF_INET4, type=socket.SOCK_DGRAM
)
case _:
raise ValueError("Invalid IP version or protocol.")

click.echo(
f"Client {self._protocol_name} {self._client_name}: "
f"Created socket [{client_socket}]."
)

try:
client_socket.bind((str(self._local_ip_address), self._local_port))
click.echo(
f"Client {self._protocol_name} {self._client_name}: Bound socket "
f"to {self._local_ip_address}, port {self._local_port}."
)
except OSError as error:
click.echo(
f"Client {self._protocol_name} {self._client_name}: Unable to bind socket "
f"to {self._local_ip_address}, port {self._local_port} - {error!r}.",
)
return None

try:
client_socket.connect(
(str(self._remote_ip_address), self._remote_port)
)
click.echo(
f"Client {self._protocol_name} {self._client_name}: Connection opened "
f"to {self._remote_ip_address}, port {self._remote_port}."
)
except OSError as error:
click.echo(
f"Client {self._protocol_name} {self._client_name}: Connection to "
f"{self._remote_ip_address}, port {self._remote_port} failed - {error!r}."
)
return None

return client_socket

@abstractmethod
def _thread__client(self) -> None:
"""
Client thread.
"""

raise NotImplementedError
34 changes: 12 additions & 22 deletions examples/lib/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,18 +55,10 @@ class Service(ABC):
"""

_protocol_name: str

def __init__(
self, *, service_name: str, local_ip_address: IpAddress, local_port: int
) -> None:
"""
Initialize the Service object.
"""

self._service_name = service_name
self._local_ip_address = local_ip_address
self._local_port = local_port
self._run_thread = False
_service_name: str
_local_ip_address: IpAddress
_local_port: int
_run_thread: bool

def start(self) -> None:
"""
Expand All @@ -91,33 +83,31 @@ def stop(self) -> None:
self._run_thread = False
time.sleep(0.1)

def _get_listening_socket(self) -> Socket | None:
def _get_service_socket(self) -> Socket | None:
"""
Create and bind the listening socket.
Create and bind the service socket.
"""

match self._local_ip_address.version, self._protocol_name:
case 6, "TCP":
listening_socket = socket.socket(
service_socket = socket.socket(
family=socket.AF_INET6, type=socket.SOCK_STREAM
)
case 4, "TCP":
listening_socket = socket.socket(
service_socket = socket.socket(
family=socket.AF_INET4, type=socket.SOCK_STREAM
)
case 6, "UDP":
listening_socket = socket.socket(
service_socket = socket.socket(
family=socket.AF_INET6, type=socket.SOCK_DGRAM
)
case 4, "UDP":
listening_socket = socket.socket(
service_socket = socket.socket(
family=socket.AF_INET4, type=socket.SOCK_DGRAM
)

try:
listening_socket.bind(
(str(self._local_ip_address), self._local_port)
)
service_socket.bind((str(self._local_ip_address), self._local_port))
click.echo(
f"Service {self._protocol_name} {self._service_name}: Socket created, "
f"bound to {self._local_ip_address}, port {self._local_port}."
Expand All @@ -130,7 +120,7 @@ def _get_listening_socket(self) -> Socket | None:
)
return None

return listening_socket
return service_socket

@abstractmethod
def _thread__service(self) -> None:
Expand Down
2 changes: 1 addition & 1 deletion examples/lib/tcp_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def _thread__service(self) -> None:
Service thread.
"""

if listening_socket := self._get_listening_socket():
if listening_socket := self._get_service_socket():
listening_socket.listen()
click.echo(
f"Service {self._protocol_name} {self._service_name}: Socket "
Expand Down
2 changes: 1 addition & 1 deletion examples/lib/udp_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,5 @@ def _thread__service(self) -> None:
Service initialization.
"""

if listening_socket := self._get_listening_socket():
if listening_socket := self._get_service_socket():
self._service(socket=listening_socket)
8 changes: 3 additions & 5 deletions examples/tcp_daytime_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,9 @@ def __init__(
Class constructor.
"""

super().__init__(
service_name="Daytime",
local_ip_address=local_ip_address,
local_port=local_port,
)
self._service_name = "Daytime"
self._local_ip_address = local_ip_address
self._local_port = local_port

self._message_count = message_count
self._message_delay = message_delay
Expand Down
9 changes: 4 additions & 5 deletions examples/tcp_discard_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,10 @@ def __init__(self, *, local_ip_address: IpAddress, local_port: int):
"""
Class constructor.
"""
super().__init__(
service_name="Discard",
local_ip_address=local_ip_address,
local_port=local_port,
)

self._service_name = "Discard"
self._local_ip_address = local_ip_address
self._local_port = local_port

@override
def _service(self, *, socket: Socket) -> None:
Expand Down
89 changes: 26 additions & 63 deletions examples/tcp_echo_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@

import click

from examples.lib.client import Client
from pytcp import TcpIpStack, initialize_interface
from pytcp.lib import socket
from pytcp.lib.net_addr import (
ClickTypeIp4Address,
ClickTypeIp4Host,
Expand All @@ -59,11 +59,14 @@
)


class TcpEchoClient:
class TcpEchoClient(Client):
"""
TCP Echo client support class.
"""

_protocol_name = "TCP"
_client_name = "Echo"

def __init__(
self,
*,
Expand Down Expand Up @@ -112,72 +115,32 @@ def _thread__client(self) -> None:
Client thread.
"""

match self._local_ip_address.version:
case 6:
client_socket = socket.socket(
family=socket.AF_INET6, type=socket.SOCK_STREAM
)
case 4:
client_socket = socket.socket(
family=socket.AF_INET4, type=socket.SOCK_STREAM
if client_socket := self._get_client_socket():
message_count = self._message_count
while message_count:
message = "[------START------] "
for i in range(self._message_size - 2):
message += f"[------{i + 1:05}------] "
message += "[-------END-------]\n"

try:
client_socket.send(bytes(message, "utf-8"))
except OSError as error:
click.echo(f"Client TCP Echo: send() error - {error!r}.")
break

click.echo(
f"Client TCP Echo: Sent {len(message)} bytes of data to "
f"{self._remote_ip_address}, port {self._remote_port}."
)
time.sleep(self._message_delay)
message_count = min(message_count, message_count - 1)

click.echo(f"Client TCP Echo: Created socket [{client_socket}].")

try:
client_socket.bind((str(self._local_ip_address), self._local_port))
click.echo(
"Client TCP Echo: Bound socket to "
f"{self._local_ip_address}, port {self._local_port}."
)
except OSError as error:
click.echo(
"Client TCP Echo: Unable to bind socket to "
f"{self._local_ip_address}, port {self._local_port} - "
f"{error!r}.",
)
return

try:
client_socket.connect(
(str(self._remote_ip_address), self._remote_port)
)
click.echo(
"Client TCP Echo: Connection opened to "
f"{self._remote_ip_address}, port {self._remote_port}."
)
except OSError as error:
click.echo(
f"Client TCP Echo: Connection to {self._remote_ip_address}, "
f"port {self._remote_port} failed - {error!r}."
)
return

message_count = self._message_count
while message_count:
message = "[------START------] "
for i in range(self._message_size - 2):
message += f"[------{i + 1:05}------] "
message += "[-------END-------]\n"

try:
client_socket.send(bytes(message, "utf-8"))
except OSError as error:
click.echo(f"Client TCP Echo: send() error - {error!r}.")
break

client_socket.close()
click.echo(
f"Client TCP Echo: Sent {len(message)} bytes of data to "
"Client TCP Echo: Closed connection to "
f"{self._remote_ip_address}, port {self._remote_port}."
)
time.sleep(self._message_delay)
message_count = min(message_count, message_count - 1)

client_socket.close()
click.echo(
"Client TCP Echo: Closed connection to "
f"{self._remote_ip_address}, port {self._remote_port}."
)


@click.command()
Expand Down
Loading

0 comments on commit a3a305e

Please sign in to comment.