Flatterapfel hat geschrieben: ↑Sonntag 24. April 2022, 18:10
hab mittlerweile herausgefunden, wo das Problem liegt und frage daher: Kann man s.accept() irgendwie im Hintergrund laufen lassen bzw. einfach nur ausführen, wenn ein Client versucht sich zu verbinden?
Ja das geht, im große nun ganzen gibt es 3 übliche Wege sowas zu machen und eine Matrix von Mischlösungen zwischen den drei Möglichkeiten
1.) du könntest Threads verwenden ein Thread lauscht auf neue Verbindungen und sobald diese kommen übernimmt ein Worker thread diese Verbindung. Dies Modell eignet sich vor allen, wenn jeder Client seine eigenen Geschäfte mit dem Server macht. Der Apache Webserver macht das zum Beispiel so (Okay genau genommen hat der noch viel mehr Magie, aber im Großen ist das sein Modell)
2.) du könntest dein Programm Event basiert machen, in Python wäre das Stichwort async. Im Großen und Ganzen basiert async darauf, dass sich Callbacks auf Events registrieren. Eine sehr elegante Idee, das Problem zu lösen. Dieses Modell eignet sich recht gut, wenn man viele Clients mit wenig Ressourcen versorgen will und die Interaction mit dem Client schnell geht. Problematisch ist in diesen Kontext oft das Debuggen, da es schwer sein kann, die Stadtmaschine des Programms zu verstehen. Nicht umsonst gibt es die Meme bei Events gibt es nur 2 dinge zu beachten
2.) das alles in der richtigen Reihenfolge kommt.
1.) das jede Nachricht nur genau einmal kommt.
2.) das alles in der richtigen Reihenfolge kommt.
Aber alles in allen ist dieser Ansatz sehr modern und wie gesagt Python async ist ein sehr robustes Framework für solche Dinge und meine Erfahrungen aus anderen Programmiersprachen sagen das Event basiert sehr performante Systeme mit wenig Ressourcen verbrauch, möglich sind.
3.) eine andere Möglichkeit, dass dein Programm im quasi im Hintergrund auf weitere Verbindungen wartet, sind die Systemcalls welche du in dem Python Module select findest. Allen ist gemein, dass du Ihnen eine Gruppe von filedeskriptoren geben kannst und sagen kannst auf welche Art von Aktion du warten willst (in deinen Fall Lesen)
sollte dann einer Der Filedeskriptoren daten haben, sagt die Select/Poll & Co. welcher der Gruppe erfolgreich gelesen werden kann und dein Programm kann sich angemessen verhalten. Der Vorteil von select und Co ist wieder das alle Clients im Gleichen Prozess bedient werden, was es für Anwendungen wie Chats ideal macht. Unter Python würde ich aber die Abstraktion aus dem selectors Module verwenden, schon um mich um Portable zu bleiben und mich nicht um die Lowlevel Probleme kümmern zu müssen.
Eine einfache Chatserver Klasse die dann mit bis zu 100 Clients reden kann sieht so aus.
Code: Alles auswählen
import socket
import selectors
class Server:
def __init__(self, host, port):
srvsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
srvsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
srvsock.bind((host, port))
srvsock.listen(100)
self.selector = selectors.DefaultSelector()
self.selector.register(srvsock, selectors.EVENT_READ, self.accept)
def accept(self, sock: socket.socket):
conn, addr = sock.accept()
self.selector.register(conn, selectors.EVENT_READ, self.recv)
def remove(self, sock: socket.socket):
self.selector.unregister(sock)
sock.close()
def recv(self, sock: socket.socket):
data = sock.recv(8192)
if not data or data == b'+stop+\n':
# connection close on remote side
self.remove(sock)
else:
# send message to all active clients
for idx in self.selector.get_map():
key = self.selector.get_key(idx)
if key.data == self.recv and key.fileobj != sock:
key.fileobj.sendall(data)
def run(self):
while True:
for key, mask in self.selector.select(1):
if mask & selectors.EVENT_READ:
# some socket wants attention
callback = key.data
callback(key.fileobj)
else:
# Timout from select, time for houskeeping or remove else tree
pass
def main():
srv = Server('localhost', 2345)
srv.run()
if __name__ == '__main__':
main()