Server - Client Problem

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
Antworten
hans_py
User
Beiträge: 13
Registriert: Sonntag 29. Januar 2012, 12:26

Hallo,

sicher wurde das Thema schon behandelt, aber es wär trotzdem nett, wenn ihr mir helfen könntet. Nachstehend habe ich mein eingeschränktes Server - Client Programm mal reingestellt. Es klappt auch soweit, allerdings hab ich das Problem, dass die ankommenden Nachrichten beim Client erst mit einem Enter ausgelsen werden. Das ist mir unklar, denn empfangen und senden sind doch in einem Thread gekapselt. Nehme ich noch einen Client dazu, ist das Problem logischerweise dasselbe. Empfangen und senden laufen irgendwie nicht nebenläufig. Vielleicht könnt ihr mir helfen. Nachstehend der Code (verkürzt).

Code: Alles auswählen

from threading import *
from socket import *
from convert import *   # Byteumwandlung in utf-8
import string 

class NetServer:

    def __init__(self):

        self.__a  = Thread(target=self.__accept)
        self.__a.start()
        self.__t        = []
        self.__clients = []
                
    def __accept(self):

        try:
          while True:
           sys_name = gethostname()
           ip       = str(gethostbyname(sys_name))  
           s = socket(AF_INET, SOCK_STREAM)   # Verbindungssocket         
           s.bind((ip,50007))                            # Adressobjet (IP Nummer, Portnummer) 
           s.listen(5)
           print ("Server N° " + ip + " lauscht gebannt")
           komm, addr =s.accept()                   # Kommunikationssocket                                 
           self.__clients+=[(komm, addr)]           

           i=len(self.__clients)-1           
           print ("Anmeldung von Client No >> " +str(i))
           
           cl = Thread(target=self.__wait, args=[i])           
           cl.start()          
        finally: s.close()   
                 
    def __wait (self,ID):        
      self.__clients[ID][0].send(str2byte('Client No '+str(ID)+' verbunden\nEingabe >>'))           
      while True:
         t   = byte2str(self.__clients[ID][0].recv(1024))
         print (t)         
         if not t: self.__clients[ID][0].close()   # schließe Kommunikationssocket
         
         al   = t.find ('-all')     
         if al  >=1 :
                    for i in range (0,len(self.__clients)):
                      self.__clients[i][0].send(str2byte('Rundansage\t>> '+str(t)))                                         
n = NetServer()

... und der Client

Code: Alles auswählen

from threading import *
from socket import *
from convert import *

class client1:

    def __init__(self):

        self.__s        = None
        self.__data     = None
        self.angemeldet = False
        
    def anmelden(self,server):        
        self.__server = server
        try :
          self.__s = socket()
          self.__s.connect((self.__server,50007))
        except : self.angemeldet = False
        
        self.__a1 = Thread(target= self.__wait)
        self.__a1.start()
        self.__s1 = Thread(target= self.__senden, args=[])
        self.__s1.start()
        self.angemeldet = True
        return self.angemeldet 

    def __wait(self):
        while 1:
            antwort = self.__s.recv(1024)
            print ( byte2str(antwort))         
        
    def __senden(self):       
        while 1:
            a = str(input ("Nachricht"))
            self.__s.send(str2byte(a))
            print ("gesendet")
                   
c = client1()
if c.anmelden(" ..... "): print("client 1 angemeldet") #ipv4 - Adrsse
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

Hallo Hans,
warum fangen alle Deine Variablennamen mit zwei __ an?
warum versuchst Du innerhalb der while-Schleife auf Port 50007 zu binden?
Spätestens beim zweiten Durchgang geht das schief (Address alread in use).
An Listen werden Elemente mit append angehängt.
Statt ständig self.__clients[ID][0] zu lesen würde man besser eine eigene lokale Variable benutzen.
Es ist nicht garantiert, dass immer genau eine Zeile mit weniger als 1024 Zeichen komplett am Stück gesendet wird.
Wenn der Socket geschlossen wird, solltest Du auch die Schleife beenden.
Über Listen iteriert man nicht mit range(0,len(lst)) sondern mit for i in lst.
Strings mit variablem Inhalt nicht mit + zusammenstückeln sondern % oder format benutzen.

Zum eigentlichen Thema: ich kann Deinen Fehler nicht nachvollziehen.

Grüße
Sirius
Benutzeravatar
/me
User
Beiträge: 3556
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

Zu ergänzen wären noch die bösen Sternchen-Importe. Damit holt man sich alles mögliche in den Namensraum und hat keine wirkliche Kontrolle mehr darüber. Spätestens wenn man einmal from os import * geschrieben hat und anschließend bei einem open() auf die Nase gefallen ist weiß man, welche Probleme man sich damit einhandelt.
hans_py
User
Beiträge: 13
Registriert: Sonntag 29. Januar 2012, 12:26

Eine Teil der Hinweise kann ich nachvollziehen, einen anderen nicht.
(__ verdeckte Attribute, append ist eine Möglichkeit += [..] eine andere ..)

Das Programm läuft. Mein Problem, das der client die Nachricht erst bei der Eingabe (Enter) anzeigt, ist immer noch offen.
Der Server bekommt die Daten auch bei mehr als zwei clients, der client empfängt auch die Daten, welche der Server sendet.
Diese werden jedoch nicht angezeigt bzw. genauer erst nachdem der client etwas sendet. Das ist mein Problem.

Danke für die Hinweise.
BlackJack

@hans_py: Die beiden führenden Unterstriche verdecken ein Attribut nicht, sie schützen bei Mehrfachvererbung und tiefen Klassenhierarchien davor *dass* die Attribute verdeckt werden. Beides ist hier nicht der Fall und ich sehe auch die Gefahr davon nicht. Wenn Du ein Attribut als Implementierungsdetail kennzeichnen möchtest, dann ist *ein* Unterstrich das Mittel der Wahl. Und die Namen dürfen auch gerne aussagekräftiger sein als `a` und `t`. So ein Name soll dem Leser vermitteln wofür der Wert dahinter im Kontext des Programms steht. Das ist bei einbuchstabigen Namen in der Regel nicht der Fall, insbesondere wenn es sich um so „langlebige” Namen wie Attribute handelt.

`append()` ist *die* Möglichkeit einzelne Elemente einer Liste hinzuzufühen. ``+=`` ist das *nicht*, denn damit fügt man einer Liste *mehrere* Elemente hinzu. Das ist der Grund warum Du das eine Element erst unnötigerweise in eine Liste stecken musst.
hans_py
User
Beiträge: 13
Registriert: Sonntag 29. Januar 2012, 12:26

Danke für die Hinweise, ich habe versucht einige Fehler rauszunehmen. Das ursprüngliche Problem ist leider immer noch nicht klar. Wäre für weitere Hinweise
dankbar. Darüber hinaus, wie schließe ich den Loop, wenn der socket geschlossen wird und welcher Port ist sinnvoll ?

Code: Alles auswählen

from threading import *
from socket import *
from convert import *
import string 

class NetServer:

    def __init__(self):

        self.lauschen  = Thread(target=self.accept)
        self.lauschen.start()        
        self.clients = []
                
    def accept(self):

        try:
          while True:
           sys_name = gethostname()
           ip       = str(gethostbyname(sys_name))  
           
           s = socket(AF_INET, SOCK_STREAM)         # Verbindungssocket         
           s.bind((ip,50008))                       # Adressobjet (IP Nummer, Portnummer) 
           s.listen(5)
           print ('Server N° %s lauscht gebannt' %(ip))
           komm, addr =s.accept()                   # Kommunikationssocket                                 
           self.clients.append((komm, addr))           

           i=len(self.clients)-1           
           print ('Anmeldung von Client No >> %s'%(str(i)))
           
           cl = Thread(target=self.wait, args=[i])           
           cl.start()           
        
        finally:
           s.close()          
                 
    def wait (self,ID):
        
      self.clients[ID][0].send(str2byte('Client No %s verbunden'%(str(ID))))           
      while True:
         t   = byte2str(self.clients[ID][0].recv(1024))
                 
         if not t: self.clients[ID][0].close()   # schließe Kommunikationssocket
         
         al   = t.find ('-all')     
         if al  >=1 :
            for client in self.clients:
                
                      client[0].send(str2byte('Rundansage >> %s '%(str(t))))                                     
    
n = NetServer()
und ein client

Code: Alles auswählen

from threading import *
from socket import *
from convert import *

class client2:

    def __init__(self):

        self._s        = None
        self._data     = None
        self.angemeldet = False
        
    def anmelden(self,server):        
        self.server = server
        try :
          self._s = socket()
          self._s.connect((self.server,50008))
        except : self.angemeldet = False
        
        self.a1 = Thread(target= self.wait)
        self.a1.start()
        self.s1 = Thread(target= self.senden, args=[])
        self.s1.start()
        self.angemeldet = True
        return self.angemeldet 

    def wait(self):
        while 1:
            antwort = self._s.recv(1024)
            print ( byte2str(antwort))
          
        
    def senden(self):       
        while 1:
            a = str(input ("Nachricht"))
            self._s.send(str2byte(a))
            print ("gesendet")
                   
c = client2()
if c.anmelden(" Hier ipv4 Adresse "): print("client 2 angemeldet")
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

Hallo Hans,
socket-bind-listen ist immer noch innerhalb der while-Scheife.
geschlossene clients werden nicht aus der clients-Liste entfernt.
Loops werden mit break abgebrochen.
Es ist immer noch nicht klar, wie eine Nachricht aussieht, tcp sieht nur einen Stream.
Wenn Du also Teilstücke dieses Streams nach '-all' untersuchst, werden also beliebige
Teilstücke an alle, andere gar nicht geschickt. Ist zwischen '-a' und 'll' zum Beispiel
eine Lesegrenze, findest Du gar kein -all.
while 1: -> while True:

Zum eigentlichen Thema: ich kann Deinen Fehler immer noch nicht nachvollziehen. Bei mir tut alles wie programmiert.

Grüße
Sirius
hans_py
User
Beiträge: 13
Registriert: Sonntag 29. Januar 2012, 12:26

Code: Alles auswählen

def accept(self):

        try:
          sys_name = gethostname()
          ip       = str(gethostbyname(sys_name))  
           
          s = socket(AF_INET, SOCK_STREAM)         # Verbindungssocket         
          s.bind((ip,50008))                       # Adressobjet (IP Nummer, Portnummer) 
          s.listen(5)
          print ('Server N° %s lauscht gebannt' %(ip))  
          while 1:
           
           komm, addr =s.accept()                   # Kommunikationssocket                                 
           self.clients.append((komm, addr))           

           i=len(self.clients)-1           
           print ('Anmeldung von Client No >> %s'%(str(i)))
           
           cl = Thread(target=self.wait, args=[i])           
           cl.start()           
        
        finally:
           s.close()          
                 
    def wait (self,ID):
        
      self.clients[ID][0].send(str2byte('Client No %s verbunden'%(str(ID))))           
      while 1:
         t   = byte2str(self.clients[ID][0].recv(1024))
                 
         if not t:
               self.clients[ID][0].close()   # schließe Kommunikationssocket
               self.clients.remove (self.clients [ID])
         
         al   = t.find ('-all')     
         if al  >=1 :
            for client in self.clients:                
                      client[0].send(str2byte('Rundansage >> %s '%(str(t))))  
Ich habe einige Änderungen vorgenommen, Danke. Im Moment läuft es so, wenn ein client eine Nachricht an den Server sendet, wird diese nach -all untersucht. Wird dieser Teilstring gefunden, sendet der Server an alle angemeldeten clients den Inhalt mit der Erweiterung Rundansage. Funktioniert auch. Nun die Fragen.

1) Wenn ich einen client gewaltsam abwürge, kommt eine Fehlermeldung, wie baue ich eine Information ein, die die Fehlermeldung unterdrückt und statt dessen
eine Info liefert (Codeschnipsel wär super).
2) Wo soll ein break rein, der Server lauscht doch ununterbrochen, bis zum Ende des Programms.
3) Mein Hauptproblem: Wenn der Server die Nachricht sendet, kommt Sie auch beim client an, erscheint aber erst bzw. wird angezeigt, wenn etwas eingegeben
wird oder halt Enter
gedrückt wird. Theoretisch müsste der client das doch auch so anzeigen, da senden und empfangen in einem jeweils separaten Thread laufen.
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

Zum zeilenweisen Lesen brauchst Du ungefähr sowas:

Code: Alles auswählen

def recv_lines(socket):
    buf = ''
    while True:
        data = byte2str(socket.recv(1024))
        if not data:
            if buf: yield buf
            break
        lines = (buf+data).split('\n')
        buf = lines.pop()
        for line in lines:
            yield line
Dann sieht Dein wait so aus:

Code: Alles auswählen

    def wait (self,ID):
        this_client = self.clients[ID]
        try:
            this_client[0].send(str2byte('Client No %s verbunden'%(str(ID))))           
            for line in recv_lines(this_client[0]):
                if '-all' in line:
                    for client in self.clients:
                        try:               
                            client[0].send(str2byte('Rundansage >> %s '%(str(t))))
                        except IOError:
                            pass # ignore corrupt clients
        finally:
            self.clients.remove(this_client)
            this_client[0].close()
achte darauf, dass wenn Du einen client entfernst, alle IDs sich ändern!

»senden« sollte jetzt auch noch einen Zeilenumbruch mitschicken:

Code: Alles auswählen

    def senden(self):       
        while True:
            a = input("Nachricht")
            self._s.send(str2byte(a+'\n'))
            print ("gesendet")     
Spätestens bei viel Traffic wird es Probleme geben, weil dann an viele Clienten gleichzeitig all-Nachrichten gesendet werden sollen.
hans_py
User
Beiträge: 13
Registriert: Sonntag 29. Januar 2012, 12:26

Ersteinmal danke für die ausführliche Antwort. Die Methode yield kannte ich noch nicht. Ist das ein return, bei dem sich das Programm die Stelle merkt von der returniert wurde und dann von dort beim nächstenmal den veränderten Wert liefert ? Leider funktioniert bei mir das Zeilenauslesen nicht. Im Prinzip ist das logisch, allerdings wenn ich nur den Buffer zurückgebe ist's ok., bei lines wird nichts übergeben (zumindest haben entsprechende Prints nichts angezeigt). Der Zeilenumbruch beim client führt nicht dazu, dass ich in der Python shell ohne Enter die Antwort sehe. Bei zwei clients zum Beispiel, ich poste das veränderte script für den Server nochmal.

Code: Alles auswählen

from threading import *
from socket import *
from convert import *
import string 

class NetServer:

    def __init__(self):

        self.a  = Thread(target=self.accept)
        self.a.start()        
        self.clients = []
                
    def accept(self):

        try:
          sys_name = gethostname()
          ip       = str(gethostbyname(sys_name))  
           
          s = socket(AF_INET, SOCK_STREAM)         # Verbindungssocket         
          s.bind((ip,50008))                       # Adressobjet (IP Nummer, Portnummer) 
          s.listen(5)
          print ('Server N° %s lauscht gebannt' %(ip))  
          while 1:
           
           komm, addr =s.accept()                   # Kommunikationssocket                                 
           self.clients.append((komm, addr))           

           i=len(self.clients)-1           
           print ('Anmeldung von Client No >> %s'%(str(i)))
           
           cl = Thread(target=self.wait, args=[i])           
           cl.start()           
        
        finally:
           s.close()          

    def recv_lines(self,socket):
      buf = ''
      while True:
         data = byte2str(socket.recv(1024))
         if not data:
            if buf: yield buf
            break
         lines = (buf+data).split('\n')
         buf = lines.pop()
         for line in lines:           
            yield line
            
    def wait (self,ID):
      this_client = self.clients[ID]
      try:
          this_client[0].send(str2byte('Client No %s verbunden'%(str(ID))))
          for line in self.recv_lines(this_client[0]):
              if '-all' in line:
                 print (line) 
                 for client in self.clients:                     
                     try:
                       client[0].send(str2byte('Rundansage >> %s '%(str(line))))
                     except IOError:
                       pass # ignore corrupt clients  
      finally:
          self.clients.remove(this_client)
          this_client[0].close()
          
n = NetServer()
Antworten