socket nimmt immer nur eine Message von jedem Programmdurchlauf entgegen

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
Antworten
Flatterapfel
User
Beiträge: 1
Registriert: Sonntag 24. April 2022, 13:47

Also hallo erstmal.
Ich habe gerade mit der Progammierung von Sockets angefangen und es dank einiger Tutorials auch geschafft einen Server und einen Clienten (auf dem selben Gerät) zu bauen. Das Problem ist, dass ich für jede Message mein Clientprogramm neustarten muss und nicht einfach mehrere Nachrichten in einem Durchlauf abschicken kann. ist das normal oder mache ich etwas falsch.
hier meine Codes:
der server:

Code: Alles auswählen

import socket
from time import sleep
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
port=2345
s.bind(('*IP-Adresse*',port))
s.listen(2)
try:
    while True:
        (client_socket, addr) = s.accept()
        msg=client_socket.recv(1024)
        sleep(3)
        msg_rec=client_socket.send(bytes('recall','utf-8'))
        print(str(msg,'utf-8'))
        if str(msg,'utf-8')=='+stop+':
            break

finally:
    s.close()
    
der client:

Code: Alles auswählen

import socket
from time import sleep
def msg_send():
    message='annything'
    cs.send(bytes(message,"utf-8"))
    msg=cs.recv(1024)
    print(str(msg,'utf-8'))
cs=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
port=2345
cs.connect(('*IP-Adresse*',port))
try:
    msg_send()
    sleep(2)
    msg_send()
finally:
    cs.shutdown(1)
    cs.close()
    
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Da es ja Chat-Programme gibt, bei denen du NICHT jedes mal das Programm neu starten musst, um deiner Oma eine Nachricht zu schreiben, kannst du dir die Frage, ob das normal ist ja eigentlich selbst beantworten, oder?

Was macht denn dein Server substantiell anders, was dein client nicht macht, und weshalb er "endlos" laeuft?

Dein Programm zeigt im uebrigen auch die gleichen Fehler, die man bei 99.99% aller Socket-Programme sieht. Wir diskutieren das hier mit grosser Regelmaessigkeit. Sockets und Protokoll, sowie sendall sind gute Suchbegriffe hier im Forum (ggf. via Google, die Forumssuche selbst ist so naja).
Flatterapfel
User
Beiträge: 1
Registriert: Sonntag 24. April 2022, 13:47

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?
Zuletzt geändert von Flatterapfel am Sonntag 24. April 2022, 18:11, insgesamt 1-mal geändert.
imonbln
User
Beiträge: 149
Registriert: Freitag 3. Dezember 2021, 17:07

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()
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Meh, das hier ist doch wieder der übliche falsche, kaputte Mist den man überall im Internet findet:

Code: Alles auswählen

        data = sock.recv(8192)
        if not data or data == b'+stop+\n':
TCP ist nicht nachrichten- sondern datenstrombasiert und `sock.recv()` garantiert nicht wie gross der Ausschnitt aus dem Datenstrom ist, der geliefert wird, nur das mindestens 1 Byte geliefert wird, solange die Verbindung nicht geschlossen wurde. Damit ist der ``or``-Teil der Bedingung falsch.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
imonbln
User
Beiträge: 149
Registriert: Freitag 3. Dezember 2021, 17:07

__blackjack__ hat geschrieben: Samstag 18. Juni 2022, 13:25 TCP ist nicht nachrichten- sondern datenstrombasiert und `sock.recv()` garantiert nicht wie gross der Ausschnitt aus dem Datenstrom ist, der geliefert wird, nur das mindestens 1 Byte geliefert wird, solange die Verbindung nicht geschlossen wurde. Damit ist der ``or``-Teil der Bedingung falsch.
Stimmt das Recv dir nicht garantiert wie viele Bytes du bekommst und die Nachricht kann fragmentiert sein, alles richtig. Richtig ist aber auch, das hier ist ein Beispiel! In dem Beispiel geht es um den selector, das Beispiel hat auch keine Fehlerbehandlung und geht mit einer positiven Grundannahme an die Sache ran und in der Praxis wird das '+stop+' welches übrigens vom TO kommt in den meisten Fällen genau das machen, was es soll. Es ist also gut genug!

Sollte dich das stören kannst du gerne zeigen, was du für ein toller Hecht bist und das Beispiel verbessern, das ist das Tolle an so einem Forum, jeder darf Mitmachen und vielleicht lernt der eine oder andere auch noch was dabei.
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@imonbln: solange man das lokal auf dem eigenen Rechner testest, wird es wahrscheinlich immer funktionieren. Aber das ist halt das Problem an solchen „Beispielen“, dass irgendjemand glauben könnte, der Schrott würde tatsächlich immer funktionieren.
Und sobald Du select benutzt, darfst Du auch nicht mehr `sendall` benutzen, weil es bei Last den ganzen Server blockieren kann.
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@imonbln: Bei nicht-deterministischen Programmen ist „in den meisten Fällen funktioniert es“ nicht gut genug, sondern kaputt und falsch. Derjenige der hier was lernen müsste, tut es offenbar nicht.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Ok, hier mal ein einfaches Beispiel, mit einem einfachen Längen-basiertem Protokoll:

Code: Alles auswählen

import socket
import selectors


class Client:
    def __init__(self, socket, server):
        self.socket = socket
        self.server = server
        self.input_buffer = b""
        self.output_buffer = b""

    def write_packet(self, data):
        self.output_buffer += len(data).to_bytes(8, 'little') + data
        self.server.selector.modify(self.socket, selectors.EVENT_READ | selectors.EVENT_WRITE)

    def close(self):
        self.server.selector.unregister(self.socket)
        self.socket.close()

    def _recv(self):
        data = self.socket.recv(8196)
        if not data:
            self.close()
            return
        self.input_buffer += data
        while len(self.input_buffer) >= 8:
            length = int.from_bytes(self.input_buffer[:8], 'little')
            if len(self.input_buffer) < 8 + length:
                break
            data = elf.input_buffer[8 : 8 + length]
            self.input_buffer = self.input_buffer[8 + length:]
            if data == b'+stop+\n':
                self.close()
                return
            self.server.echo(self, s)

    def _send(self):
        length = self.socket.send(self.output_buffer)
        self.output_buffer = self.output_buffer[length:]
        if not self.output_buffer:
            self.server.selector.modify(self.socket, selectors.EVENT_READ)

    def process(self, mask):
        if mask & selectors.EVENT_READ:
            self._recv()
        if mask & selectors.EVENT_WRITE:
            self._send()


class Server:
    def __init__(self, host, port):
        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(5)

        self.selector = selectors.DefaultSelector()
        self.selector.register(self.socket, selectors.EVENT_READ, self)

    def process(self, mask):
        conn, addr = self.socket.accept()
        self.selector.register(conn, selectors.EVENT_READ, Client(conn, self))

    def echo(self, sender, data):
        # send message to all active clients
        for idx in self.selector.get_map():
            key = self.selector.get_key(idx)
            if key.data is not sender and key.data is not self:
                key.data.write_packet(data)

    def run(self):
        while True:
            for key, mask in self.selector.select():
                key.data.process(mask)


def main():
    server = Server('localhost', 2345)
    server.run()

if __name__ == '__main__':
    main()
Antworten