ich hätte gerne eine Rückmeldung zu meinem Quelltext, den ich später so im Unterricht einsetzen möchte.
Vorher muss ich aber ein wenig ausholen
Wir setzen seit diesem Schuljahr Python (vorher Java) bei uns im Unterricht ein und ich bin gerade dabei die von NRW vorgegebene Java-Klassen, die die Schüler im Abitur kennen sollen, in Python umzusetzen.
Die nächste Unterrichtsreihe behandelt Netzwerke, so dass ich gerade dabei bin, dafür Beispiele und Aufgaben zu erstellen. Natürlich starten wir erst mal mit den Grundlagen. Das ist mit dem Socket-Modul und kleineren Skripten ohne OOP auch kein Problem. Ich plane aber gerne rückwärts, so dass ich weiss, welche Grundlagen notwendig sein werden. Zum Abschluss der Reihe sollen die Schülerinnen und Schüler ein eigenes Projekt umsetzen, dass ein wenig anspruchsvoller ist, z.B. ein Netzwerkspiel (Schiffe versenken, TicTacToe, Vier gewinnt, ...) oder ein Chat-Programm.
Beim Zentralabitur mit Java ist die Vorgabe, dass eine Server- und eine Clientklasse zur Verfügung gestellt wird, die nachher als Oberklasse verwendet werden sollen. Die Java-Quelltexte findet man hier: http://www.standardsicherung.schulminis ... ?file=3183
(dort unter abiturklassen -> netzklassen)
Dieses Prinizip mit den zwei Oberklassen habe ich nun versucht in Python umzusetzen und habe ein Chat-Programm geschrieben. Die beiden Oberklassen:
client.py
Code: Alles auswählen
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import socket
import sys
import threading
class Client(object):
"""
Ein Client, der Nachrichten schicken kann und auf eingehnede
Nachrichten wartet.
Ankommende Strings werden nach unicode dekodiert und beim Versenden
vorher wieder enkodiert.
"""
def __init__(self):
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
def connect_to_server(self, ip, port):
try:
self.socket.connect((ip, port))
print "Verbindung hergestellt."
except socket.error, error:
print "Fehler beim Verbindungsaufbau:", error
try:
thread = threading.Thread(target=self.receive_data,
args=[self.socket])
thread.daemon = True
thread.start()
except KeyboardInterrupt:
sys.exit()
def receive_data(self, socket):
while True:
data = socket.recv(1024).strip().decode('utf-8')
# Jeder Befehl vom Server endet mit '\n'
data_split = data.split('\n')
if not data:
self.socket.close()
break
for data in data_split:
self.process_data(data)
def send_data(self, data):
try:
self.socket.sendall(data.encode('utf-8'))
except socket.error, error:
print "Fehler beim Senden:", error
def process_data(self, data):
print data
Code: Alles auswählen
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import socket
import threading
class Server(object):
"""
Ein Server, der mehrere Clients bedienen kann. Alle empfangenen Daten
werden per Default an den Client zurückgeschickt.
Die Funktionalität kann verändert werden, wenn die Methoden
process_data, process_new_connection und process_close_connection
überschrieben werden.
"""
def __init__(self, host='', port=9999):
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.socket.bind((host, port))
self.socket.listen(10)
self.clients = []
def _start(self):
print "Server laeuft! Mit Strg+C Server stoppen!"
while True:
clientsocket, (host, port) = self.socket.accept()
self._handle_connection(clientsocket, host, port)
def _handle_connection(self, clientsocket, host, port):
"""
startet für jeden neuen Client einen Thread, indem auf ankommende
Nachrichten des Clients reagiert werden kann.
Der Client wird einer Liste aller verbundenen Clients angehängt"
"""
try:
thread = threading.Thread(target=self._connection_handler,
args=(clientsocket, host))
thread.daemon = True
thread.start()
self.clients.append(clientsocket)
self.process_new_connection(clientsocket)
except (KeyboardInterrupt, SystemExit):
for client in self.clients:
self._disconnet_client(clientsocket)
def _connection_handler(self, clientsocket, host):
while True:
data = clientsocket.recv(1024).strip()
if not data:
self._disconnect_client(clientsocket)
break
self.process_message(clientsocket, data.decode('utf-8'))
def _disconnect_client(self, clientsocket):
"""handelt alle Vorgänge beim Abmelden eines Clients ab"""
self.process_close_connection(clientsocket)
self.clients.remove(clientsocket)
clientsocket.close()
def _send_data(self, clientsocket, data):
"""
Vorgabe: An jede Nachricht wird '\n' angehaengt,
damit der Client weiss, dass der Befehl zu Ende ist.
"""
try:
clientsocket.sendall('{}\n'.format(data.encode('utf-8')))
except socket.error, error:
print "Fehler beim Versenden: {}".format(error)
def _send_to_all(self, data):
for clientsocket in self.clients:
self._send_data(clientsocket, data)
def process_new_connection(self, clientsocket):
"""in abgeleiteter Klasse ggf. überschreiben"""
pass
def process_close_connection(self, clientsocket):
"""in abgeleiteter Klasse ggf. überschreiben"""
pass
def process_message(self, clientsocket, data):
"""default: Echo-Server, ggf. in abgeleiteter Klasse überschreiben"""
self._send_data(clientsocket, data)
if __name__ == '__main__':
server = Server()
server._start()
Code: Alles auswählen
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from server import Server
from datetime import datetime
class ChatServer(Server):
def __init__(self, host='', port=9999):
Server.__init__(self, host, port)
self.names = []
def process_message(self, clientsocket, data):
data = data.split()
command = data[0]
if command == 'NAME':
name = "".join(data[1:])
if name in self.names:
self._send_data(clientsocket, "FAIL")
self._disconnect_client(clientsocket)
else:
self._send_data(clientsocket, "OK")
self.names.append(name)
self._send_to_all(u"{} hat den Chat betreten.".format(name))
self._send_to_all(u'{} {}'.format('LIST',' '.join(self.names)))
print u"Verbindung von {} aufgebaut.".format(name)
elif command == 'ALL':
message = u' '.join(data[1:])
name = self.names[self.clients.index(clientsocket)]
time_now = str(datetime.now().strftime("%H:%M:%S"))
self._send_to_all(u"({}) {}: {}".format(time_now, name, message))
print u"{} schickt Nachricht an alle: {}".format(name, message)
elif command == 'ONE':
name = data[1]
message = ' '.join(data[2:])
time_now = str(datetime.now().strftime("%H:%M:%S"))
sender_name = self.names[self.clients.index(clientsocket)]
data = u"({}) Private Nachricht von {}: {}".format(time_now,
sender_name,
message)
index = self.names.index(name)
self._send_data(self.clients[index], data)
print u"{} schickt private Nachricht an {}: {}".format(sender_name,
name,
message)
elif command == 'QUIT':
self._disconnect_client(clientsocket)
def process_close_connection(self, clientsocket):
try:
name = self.names[self.clients.index(clientsocket)]
self.names.remove(name)
print u"Verbindung von {} beendet.".format(name)
self._send_to_all(u"{} hat den Chat verlassen.".format(name))
self._send_to_all(u'{} {}'.format('LIST',' '.join(self.names)))
except IndexError:
print u"Name nicht in der Liste"
if __name__ == '__main__':
chatserver = ChatServer()
chatserver._start()
https://dl.dropboxusercontent.com/u/107 ... ient_qt.py
https://dl.dropboxusercontent.com/u/107 ... ient_qt.ui
zur Implementation:
Ich bin mir durchaus bewusst, dass die Implementation des "Protokolls" durch die if-elif Kaskadierung unschön ist und ggf. besser durch eine eigene Protokoll-Klasse gelöst werden sollte, jedoch orientiere ich mich da an den Vorgaben des Zentralabiturs, wo die Schüler im Endeffekt die process_message Methode überschreiben müssen. Die jeweilige Teilaufgabe im Abitur lautet dann, die für das Problem nötige process_message Methode zu schreiben.
Auch fehlen zum Teil Fehlerprüfungen.
Die Chat-Anwendung funktioniert, jedoch habe ich noch das Problem, dass beim Abmelden von Clients beim Server einen Fehler kommt, den ich so nicht zuordnen kann:
Code: Alles auswählen
Exception in thread Thread-2:
Traceback (most recent call last):
File "/usr/lib/python2.7/threading.py", line 810, in __bootstrap_inner
self.run()
File "/usr/lib/python2.7/threading.py", line 763, in run
self.__target(*self.__args, **self.__kwargs)
File "/home/******/nas/*****/schule/Informatik/python/Skript Oberstufe/quelltext/netzwerk/server.py", line 47, in _connection_handler
data = clientsocket.recv(1024).strip()
File "/usr/lib/python2.7/socket.py", line 170, in _dummy
raise error(EBADF, 'Bad file descriptor')
error: [Errno 9] Bad file descriptor
Außerdem wäre ich dankbar für jegliche Rückmeldungen bzgl. der beiden Oberklassen auch im Hinblick auf Verbesserungen, da es mit Sicherheit noch etwas zu verbessern gibt, mir aber auch die Erfahrung bzgl. Netzwerkprogrammierung fehlt.
Berücksichtigt bitte aber auch, dass Schüler die Klassen noch verstehen können sollen, deswegen würde ich sie gerne unnötig aufblähen und mich auf das notwendige reduzieren, so dass die oben beschriebenen Anwendungsfälle damit realisierbar sind. Ich weiss, dass ich damit das Rad teilweise neu erfinde und z.B. twisted eine gute Alternative wäre, jedoch möchte ich es den Schülern, die im Abitur die Java-Quelltexte verstehen sollen, nicht schwieriger machen als notwendig. Deswegen der Spagat mit eigenen Klassen.
Gruß EmaNymton