socket.recv

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
Antworten
Milan
User
Beiträge: 1078
Registriert: Mittwoch 16. Oktober 2002, 20:52

HI. Ich bin gerade am schreiben eines Servers und rätsle wie ich Daten über sockets sicher empfangen kann. Mit sicher meine ich, dass der socket nicht mein Programm blockiert, weil keine Daten mehr zum empfangen da sind. Da das ganze sessionbasiert werden soll (POP3 lässt grüßen :wink:) will ich ein Timeout von 600 Sekunden setzen. das setzt den Socket in timeout mode. Nun hatte ich aber vor Daten über ein Fileobjekt zeilenweise zu lesen (socket.makefile("rb",-1)) . Das dumme ist, die Docu meint dazu, man sollte Fileobjekte nur dann benutzen, wenn der Socket im blocking mode ist.
Timeout mode internally sets the socket in non-blocking mode. The blocking and timeout modes are shared between file descriptors and socket objects that refer to the same network endpoint. A consequence of this is that file objects returned by the makefile() method should only be used when the socket is in blocking mode; in timeout or non-blocking mode file operations that cannot be completed immediately will fail.
Was hat das für Konsequenzen für mich? Kann ich Daten auch anders empfangen? Oder was wäre empfehlenswert?

thx, Milan
Milan
User
Beiträge: 1078
Registriert: Mittwoch 16. Oktober 2002, 20:52

Hier vielleicht mal ein kleiner Ausschnitt Code, falls es nützt. Momentan ist nur quit implementiert und wenn ich das clientseitig aufrufe hängt er. Also muss da ein Socket blockieren, was mir gar nicht lieb ist... :oops: Es wird automatisch vom Server aus setup, dann handle und dann finish gerufen, aber das dürfte nebensache sein, da das der Server regelt (SocketServer.ThreadingTCPServer). In self.request steckt der Socket, der mit dem Client verbunden ist.

Code: Alles auswählen

class POP3Session(SocketServer.BaseRequestHandler):
    """One POP3 Session from login to quit"""
    greeting="PyEC POP3 server ready"
    goodbye="PyEC POP3 server signing off"
    states = { "AUTHORIZATION":0, "TRANSACTION":1, "UPDATE":2 }
     
    def echo(self,message):
        """override this to (for example) log to a file"""
        pass
    
    def _response(self,s):
        self.request.send("%s%s" % (s, CRLF))
        self.echo(s)
    def _pos_response(self,s):
        self._response("+OK %s" % s)
    def _neg_response(self,s):
        self._response("-ERR %s" % s)
    def setup(self):
        lookup= { "QUIT":(self.quit, (0,1,2)),
                  "STAT":(self.stat, (1,)),
                  "LIST":(self.list, (1,)),
                  "RETR":(self.retr, (1,)),
                  "DELE":(self.dele, (1,)),
                  "NOOP":(self.noop, (1,)),
                  "RSET":(self.rset, (1,)),
                  "TOP":(self.top, (1,)),
                  "UIDL":(self.uidl, (1,)),
                  "USER":(self.user, (0,)),
                  "PASS":(self.pass_, (0,)),
                  "APOP":(self.apop, (0,)) }
        self.state=self.states["AUTHORIZATION"]
        timestamp=md5.new(time.ctime())
        for i in xrange(random.randint(1,11)):
            timestamp.update(str(random.random()))
        self.timestamp=timestamp.hexdigest()
        del timestamp
        self.lastcmd=""
        self.state_of_last_cmd=""
        self.request.settimeout(options.get("timeout",None))
        self.rfile=self.request.makefile("rb",-1)
    def handle(self):
        self.echo("connected by %s on port %s\n" % self.client_address)
        self._pos_response("%s <%s>" % (self.greeting, self.timestamp))
        while 1:
            command_line=self.rfile.readline()
            if (not command_line): break  # connection closed at client end?
            command_split = command_line.split()
            command = command_split[0].upper()
            self.echo("%s: %s" % (self.peername, command_line))
            if command == "QUIT":
                self.quit()
                break
            # Do what the command asks, if there is a method to do it.
            if self.lookup.has_key(command):
                try:
                    handle_cmd, allowed_states = self.lookup[command]
                    if self.state in allowed_states:
                        self.state_of_last_cmd = handle_cmd(command_split[1:])
                        self.lastcmd=command
                    else:
                        self._neg_response('command "%s" not allowed in this state' % command)
                except:
                    pass
            else:
                self._neg_response('unknown command: "%s"' % command)
        return
    def finish(self):
        if self.state == self.states["UPDATE"]:
            pass #update the mailbox by deleting markes emails
        return
    def quit(self):
        self._pos_response(self.goodbye)
        if self.state == self.states["TRANSACTION"]:
            self.state = self.states["UPDATE"]
Sorgenkind
User
Beiträge: 34
Registriert: Samstag 24. Juli 2004, 19:25
Kontaktdaten:

hast du dir schon mal asyncore und asynchat angeschaut? da hätte man keine probleme mit blockierenden sockets.
und wozu das makefile? nur für das readline?

naja hab mal ein beispiel für dich (ungetestet)

Code: Alles auswählen

import asyncore,asynchat,socket,time

class POP3Server(asyncore.dispatcher):

    def __init__(self,sessionclass,port=25):
        asyncore.dispatcher.__init__(self)
        self.sessionclass=sessionclass
        self.create_socket(socket.AF_INET,socket.SOCK_STREAM)
        self.bind(("",port))
        self.listen(1)

    def handle_accept(self):
        client,addr=self.accept()
        self.sessionclass(client,addr)
        self.listen(1)

class POP3Session(asynchat.async_chat):

    def __init__(self,client_socket,client_address):

        self.addr=client_address
        self.buf_in=""
        asynchat.async_chat.__init__(self,client_socket)
        self.last_data=0

    def _timeout(self,timeout=60):
        return time.time()-self.last_data>=timeout

    def found_terminator(self):
        return

    def collect_incoming_data(self,data):
        self.last_data=time.time()
        lines=(self.buf_in+data).splitlines()
        self.buf_in=lines[-1]
        for line in lines[:-1]:
            self.handle_line(line)

    def handle_line(self,line):
        """was mit der zeile machen..."""
        pass

def loop_once(timeout=60,selecttimeout=0.3):
    for session in asyncore.socket_map.values():
        if isinstance(session,POP3Session) and session._timeout(timeout):
            session.close()
    if asyncore.socket_map:
        asyncore.poll(selecttimeout,asyncore.socket_map)
    else:
        time.sleep(timeout)

def loop(*a):
    while 1:
        loop_once(*a)

def start():
    server=POP3Server(POP3Session,25)
    loop()
so könnte das grundgerüst aussehen...
Milan
User
Beiträge: 1078
Registriert: Mittwoch 16. Oktober 2002, 20:52

HI. Das ist aber für mich keine akzeptable Lösung, da die Sockets ja genauso eingestellt werden können und es sich nicht um einen asynchronen Server handelt. Bei POP3 gibt es ja immer nur eine einzige Befehlszeile die an den Server gesendet wird und von daher müsste man die doch auch mit readline auslesen können. Nur das will er nicht. Außerdem wollte ich halt den bereits vorhandenen ThreadingServer nutzen, da ich nur noch eine Handlerclass schreiben muss und schon würde alles stehen... Die Handlerclass kann ja auf Posixsystemen dann auch an ForkingServer übergeben werden, was noch einen Schritt besser wäre, aber leider nicht plattformunabhängig funktionieren wird.
Milan
User
Beiträge: 1078
Registriert: Mittwoch 16. Oktober 2002, 20:52

Hi. Das Problem ist erst einmal in sofern gelöst, dass ich den Störenfried gefunden habe. Ich hatte self.peername nicht definieren lassen, dadurch ist der Thread abgestürzt, aber das Programm weitergelaufen. Logisch, dass der Client nix empfangen konnte. Soweit funtioniert der Server jetzt also, ich muss jetzt zum Glück nur noch die Kommandos sauber implementieren :D .

Eine Frage hab ich aber noch, was bedeutet nun der Text hier für mich:
Timeout mode internally sets the socket in non-blocking mode. The blocking and timeout modes are shared between file descriptors and socket objects that refer to the same network endpoint. A consequence of this is that file objects returned by the makefile() method should only be used when the socket is in blocking mode; in timeout or non-blocking mode file operations that cannot be completed immediately will fail.
Momentan benutze ich ja noch ein file-objekt zum lesen, das ist bequemer. Falls sich hierbei Probleme ergeben muss ich das ganze umformen, nur ist das notwendig?
Antworten