ich möchte euch gerne zwei Projekte vorstellen. Diese behandeln das gleiche Thema und sind im gleichen Respository auf Github zu finden (siehe: https://github.com/Domroon/MessengerOne). Ich werde den Quellcode beider Dateien (chat.py (Projekt 1) und messenger.py (Projekt 2)) aber auch am Ende dieses Beitrags reinstellen.
ACHTUNG: Diese beiden Projekte sollen keinesfalls guten Programmierstyl präsentieren und sind meiner Meinung nach ein manchen Stellen etwa "vermurckst". Soll bedeuten, dass alles unübersichtlich und schwer zu verstehen sein kann. Tut mir leid an dieser Stelle. Diese Projekte sollten dafür dienen, um das TCP-Protokoll, Sockets und Verbindungen zwischen zwei Rechnern zu verstehen. Macht euch bitte deshalb keine Mühe den Programmcode zu verbessern. Ich werde diese beiden Programme sowieso nicht weiter verbessern. Ich werde mich stattdessen zwei weiteren Projekten (3 und 4) widmen:
Projekt 3: Ein "ordentliches" Chatprogramm, welches sauberen, gut lesbaren und "pythonischen" Programmcode enthält. Ich werde hier weniger mit "nackten Sockets" arbeiten. Vielmehr werde ich mich dem Modul "socketserver" widmen, welches mir viele Aufgaben abnimmt die ich Versucht habe zu programmieren. Die Klasse "ThreadingMixIn" sieht hier sehr vielversprechend aus. Ich möchte gerne eine eigene Klasse schreiben welche von dieser erbt. (An dieser Stelle möchte ich meine Begeisterung für Vererbung aussprechen. Hiermit ist es möglich den Gedankengang von dem Programmierer von "socketserver" zu verstehen und in den eigenen Programmcode zu implementieren - das ist auch der Grund warum ich neue Projekte anfangen werde, anstatt meine Programme zu verbessern)
Projekt 4: Ein Programm aufbauend auf dem Chatprogramm, welches Nachrichten verschlüsselt übertragen kann. Vielversprechend hört sich hier das ssl-Modul an welches ja als "TLS/SSL wrapper for socket objects" "beworben" wird. Da ich mich bestimmt erstmal in das Thema Verschlüsselung einfinden muss, ist dies der Grund das dieses Chatprogramm ein weiteres Projekt wird.
Aber nun erstmal zurück zu meinen beiden Projekten (1 und 2), welche schon existieren:
chat.py (Projekt 1):
Ich möchte hier grob beschreiben, was mein Programm tut.
Generell muss "Wait Client" auf einem beliebigem Rechner ausgeführt werden. Auf dem gleichen Rechner oder einem anderen Rechner im lokalen Netzwerk muss das Programm nochmals als "Search Client" gestartet werden.
A) Das macht der Wait-Client:
1. In einem Thread wird ein socket-Objekt erzeugt, welche auf einem vorgegeben Port "lauscht".
2. Nachdem dieses Socket-Objekt eine Verbindungsanfrage vom Saerch-Client bekommen hat, akzeptiert es diese Verbindung, speichert IP und Port vom Verbindungs-Socket ab, öffnet mit diesen Daten ein weiteren Kommunikationsocket und gibt das "OK", dass nun ein weiterer Thread (siehe Schritt 3) gestartet werden kann. Anschließend wartet dieser Kommunikationsocket ausschließlich auf Nachrichten vom Search-Client und gibt diese in der Kommandozeile aus. Dieser "blockiert" mit dieser Aufgabe den kompletten Thread.
3. Nachdem also nun Nachrichten empfangen werden können, wird nun ein weiterer Thread gestartet, welcher sich wie in Schritt 2 mithilfe eines Verbindungs-Sockets auf einem weiteren Port mit dem Search-Client verbindet und dann ein weiteres Kommunikations-Socket-Objekt erstellt.
B) Das macht der Search-Client:
Dieser macht die Schritte in A im Prinzip genau gegenteilig. Tut also das was in A vom "Search-Client" erwartet wird.
Ich wollte in diesem Projekt erreichen, dass sich nur zwei Clients miteinander verbinden können. Ich habe zwei Verbindungssockets benutzt, da es nicht möglich ist mit einem Socket gleichzeitig zu "lauschen" und zu senden. Wenn doch, dann lass ich mich gerne eines besseren belehren.
messenger.py (Projekt 2):
In diesem Projekt lag der Fokus darauf, dass sich mehrere Clients mit einem Server verbinden können und zu diesem Nachrichten schicken können. Im nächsten Schritt hätte ich alle Nachrichten und IPs der Clients gespeichert und dann mithilfe dieser Daten an alle Clients die mit dem Server verbunden sind zurück geschickt. Da mir die Programmstruktur zu unübersichtlich geworden ist und mir das Design generell nicht gefällt, habe ich wie gesagt erstmal mit diesem Projekt abgeschlossen und möchte nun stattdessen mit "socketserve" in Projekt 3 und 4 beginnen. Der Programmablauf von diesem Projekt ist ähnlich dem von Projekt , wobei hier jeweils kein zweites Socket erzeugt wird, welcher dann ausschließlich zum senden an den Client benutzt werden würde.
Meine Fragen:
a) Wenn ihr euch Projekt 1 und 2 anschaut und meinen Text dazu liest, kann man dann daraus schließen, dass ich Sockets und das TCP-Protokoll grundsätzlich verstanden habe, oder bin ich ganz oder teilweise auf dem Holzweg?
b) Ist meine Vorgehensweise sinnvoll meine beiden Projekte 1 und 2 fallen zu lassen um mit "socketserver" besser durchdachte Chatprogramme zu schreiben?
Anmerkungen zur Ausführung der Programme:
-> Beide sind getestet und funktionieren. Natürlich gibt es Fehler welche nicht abgefangen werden, tut mir leid dafür. Wenn man im lokalen Netzwerk kommunizieren möchte, dann muss man für den python-Interpreter eine Ausnahme in der Firewall der Computer eingerichtet werden mit denen man kommunizieren möchte. (Bis ich das herausbekommen habe vergingen Welten

"chat.py" (Projekt 1):
- > wait_client auf beliebigem Rechner starten
-> search_client auf dem selben oder beliebigen Rechner im lokalen Rechner starten und IP-Adresse von wait_client eingeben (Ausnahme für Firewall auf beiden Rechnern nicht vergessen)
- > um sauber zu beenden müssen wait_client und search_client jeweils 'exit' senden
"messenger.py" (Projekt 2):
-> host auf beliebigen Rechner starten
-> beliebig viele Clients auf dem selben Rechner oder auf Rechnern im lokalen Netzwerk starten und IP-Adresse vom Host angeben (auch hier Firewall nicht vergessen

-> die clients können mit der Nachricht 'exit' einen "disconnect" vom server durchführen
Hier also die beiden Quellcodes (wie gesagt auch auf Github zu finden)
chat.py (nicht wundern, das time-Modul habe ich nur zu testzwecken genutzt und vergessen den import zu löschen):
Code: Alles auswählen
import socket
import threading
import time
HEADER = 64
PORT = 5050
PORT_2 = 5051
SERVER = socket.gethostbyname(socket.gethostname())
ADDR = (SERVER, PORT)
FORMAT = 'utf-8'
DISCONNECT_MESSAGE = "!DISCONNECT"
CLIENT_NAME = "HOST"
class Chat:
def __init__(self):
self.message = ""
self.target_ip = 0
self.receive_target_connected = False
self.transreceive_target_connected = False
def receive_message(self, conn):
msg_length = conn.recv(HEADER).decode(FORMAT)
if msg_length:
msg_length = int(msg_length)
msg = conn.recv(msg_length).decode(FORMAT)
return msg
def send_message(self, conn, msg):
message = msg.encode(FORMAT)
msg_length = len(message)
send_length = str(msg_length).encode(FORMAT)
send_length += b' ' * (HEADER - len(send_length))
conn.send(send_length)
conn.send(message)
def receive(self, port):
receive_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
receive_socket.bind((SERVER, port))
print("[STARTING] Receiver is starting...")
receive_socket.listen()
print(f"[LISTENING] Receiver is listening on {SERVER}:{port}")
conn, addr = receive_socket.accept()
self.target_ip, conn_port = addr # save ip for transceive socket
self.receive_target_connected = True # set connection status for transceive socket
print(f"[CONNECTED] Receiver connected to {self.target_ip}:{port}")
while True:
self.message = self.receive_message(conn)
print(self.message)
if self.message == DISCONNECT_MESSAGE:
conn.close()
break
def transreceive(self, ip_adress, port, name):
transreceive_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print("[STARTING] Transceiver ist starting...")
transreceive_socket.connect((ip_adress, port))
self.transreceive_target_connected = True #set connection status for receive socket
print(f"[CONNECTED] Transceiver connected to {ip_adress}:{port}")
while True:
user_msg = input()
msg = f"\n{name}: {user_msg}"
self.send_message(transreceive_socket, msg)
if user_msg == "exit":
self.send_message(transreceive_socket, DISCONNECT_MESSAGE)
break
def wait_client(self):
name = input("Enter your Nickname: ")
receive_thread = threading.Thread(target=self.receive, args=([PORT]))
receive_thread.start()
while True:
if self.receive_target_connected:
transceiver_thread = threading.Thread(target=self.transreceive, args=([str(self.target_ip), PORT_2, name]))
transceiver_thread.start()
break
def search_client(self):
name = input("Enter your Nickname: ")
ip_adress = input("IP Address: ")
transceive_thread = threading.Thread(target=self.transreceive, args=([str(ip_adress), PORT, name]))
transceive_thread.start()
while True:
if self.transreceive_target_connected:
receive_thread = threading.Thread(target=self.receive, args=([PORT_2]))
receive_thread.start()
break
def main():
chat = Chat()
print("1 - Wait Client\n2 - Search Client")
choice = input("Input: ")
if choice == '1':
chat.wait_client()
elif choice == '2':
chat.search_client()
else:
print("Wrong Input")
if __name__ == '__main__':
main()
Code: Alles auswählen
import socket
import threading
import subprocess
class Host:
def __init__(self, ip_address, port):
self.ip_address = ip_address
self.port = port
self.header = 64
self.format = 'utf-8'
self.disconnect_message = "!DISCONNECT"
self.client_nickname = None
self.messages = []
self.connections = []
def start_server(self):
print("[STARTING] Server is starting...")
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind((self.ip_address, self.port))
print(f"[LISTENING] Server is listening on {self.ip_address}:{self.port}")
server.listen()
while True:
communication_socket, client_ip_port = server.accept()
thread = threading.Thread(target=self.handle_client, args=(communication_socket, client_ip_port))
thread.start()
print(f"[ACTIVE CONNECTIONS] {threading.active_count() - 1}")
def handle_client(self, communication_socket, client_ip_port):
client_ip, client_port = client_ip_port
print(f"[NEW CONNECTION] Communication - Socket from client at {client_ip}:{client_port}")
self.connections.append(client_ip_port)
#only for testing: show connections
print(self.connections)
# first: client sending an empty header that have the length 'header - message_length'
# communication_socket receive it
message_header = communication_socket.recv(self.header).decode(self.format)
# second: client will sending the message; communication socket receive it
# first message is always the nickname
if message_header: # if not None
message_length = int(message_header)
self.client_nickname = communication_socket.recv(message_length).decode(self.format)
# Host can now receive messages from this client
while True:
# first: receive header
message_header = communication_socket.recv(self.header).decode(self.format)
# second: receive message
if message_header: # if not None
message_length = int(message_header)
message = communication_socket.recv(message_length).decode(self.format)
if message == self.disconnect_message:
print(f"[DISCONNECT] '{self.client_nickname}' ({client_ip}:{client_port}) disconnected")
self.connections.remove(client_ip_port)
#only for testing: show connections list
print(self.connections)
break
print(f"[RECEIVE] '{self.client_nickname}' ({client_ip}:{client_port}) send a message:")
print(f"{message}")
# HERE: save message in an list an send it to all clients!
send_message = f"{self.client_nickname}: {message}"
self.messages.append(send_message)
# only for testing: show message list
print(f"[MESSAGES LIST]")
print(self.messages)
# for testing: send message back to client
communication_socket.close()
def sending_messages():
#this should be a new Thread that sends every message in the messages list to every ip_address in connections list
pass
class Client:
def __init__(self, host_ip_address, host_port, nickname):
self.host_ip_address = host_ip_address
self.host_port = host_port
self.format = 'utf-8'
self.header = '64'
self.disconnect_message = "!DISCONNECT"
self.nickname = nickname
def start_client(self):
print("[STARTING] Client is starting...")
communication_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# try accept block around this should make sense
# for timeout error(no computer on the other end) or ConnectionRefusedError(no listener on port)
communication_socket.connect((self.host_ip_address, self.host_port))
print(f"[CONNECTED] Connected with host ({self.host_ip_address}:{self.host_port})")
# first message is always the nickname
self.send_message(self.nickname, communication_socket)
# now the client can send messages
while True:
message = input()
if message == 'exit':
self.send_message(self.disconnect_message, communication_socket)
break
self.send_message(message, communication_socket)
def send_message(self, msg, communication_socket):
# first: client sending an empty header that have the length 'header - message_length'
message = msg.encode(self.format)
message_length = len(message)
send_length = str(message_length).encode(self.format)
send_length += b' ' * (int(self.header) - len(send_length))
communication_socket.send(send_length)
# second: client will sending the message
communication_socket.send(message)
def main():
print("1 - HOST\n2 - CLIENT")
user_input = input()
if user_input == '1':
host_ip_address = socket.gethostbyname(socket.gethostname())
host_port = 50500
host = Host(host_ip_address, host_port)
host.start_server()
elif user_input == '2':
nickname = input("Enter a nickname: ")
host_ip_address = input("Host IP: ")
host_port = 50500
client = Client(host_ip_address, host_port, nickname)
client.start_client()
else:
print("Wrong Input.")
if __name__ == '__main__':
main()