################################ # Modulo di supporto al TCP/IP # # Versione per Python 3.x # # Ultimo aggiorn. 03/03/2013 # ################################ import socket import threading SCKSTAT_OK = 0 SCKSTAT_EC = 1 SCKSTAT_ER = 2 BACKLOG = 5 DOT = '.' EMPTYSTR = '' ENDLINE = "\r\n" LINEFEED = '\n' RETURN = '\r' #################################### # Procedure e funzioni di utilita` # #################################### def rip(s): # # Crea e riporta una nuova stringa eliminando # dalla stringa "s" i RETURN e i LINEFEED finali. # return s.rstrip(ENDLINE) def rtrim(s): # # Crea e riporta una nuova stringa eliminando # dalla stringa "s" gli spazi finali. # return s.rstrip() def ltrim(s): # # Crea e riporta una nuova stringa eliminando # dalla stringa "s" gli spazi iniziali. # return s.lstrip() def trim(s): # # Crea e riporta una nuova stringa eliminando # dalla stringa "s" gli spazi iniziali e finali. # return s.strip() def resolve(s): # # Converte un indirizzo IP in formato # ASCII - sia numerico puntato sia # alfanumerico - in binario rappresentato # sia come intero, sia come bytes (2-tuple). # # # Indirizzo numerico puntato. # try: bas=socket.inet_aton(s) except socket.error: # # Indirizzo alfanumerico. # try: bas=socket.inet_aton(socket.gethostbyname(s)) except socket.error: # # Errore. # bas=b'0' ban=0 for i in range(len(bas)): ban=256*ban+bas[i] return (ban,bas) # End of resolve def ntoa(l): # # Converte un indirizzo IP binario # nel formato ASCII numerico puntato, # che riporta come stringa. # return socket.inet_ntoa(l) def dirsolve(name): # # Converte un indirizzo IP in formato # ASCII alfanumerico nel formato ASCII # numerico puntato, che riporta come # stringa. # return socket.gethostbyname(name) def bcksolve(ip): # # Converte un indirizzo IP in formato # numerico puntato in hostname completo, # che riporta come stringa. # hn=socket.gethostbyaddr(ip) return hn[0] def getBinLocalAddress(): # # Riporta l'Indirizzo Internet locale nei formati # numerico-puntato, binario e bytes (3-tuple). # locip=socket.gethostbyname(socket.gethostname()) binad=resolve(locip) return (locip,binad[0],binad[1]) def getLocalAddress(): # # Riporta l'Indirizzo Internet locale # nel formato numerico-puntato. # return socket.gethostbyname(socket.gethostname()) def getLocalName(): # # Riporta il nome della macchina locale. # locname=socket.gethostname() p=locname.find(DOT) if p!=-1: return locname[:p] else: return locname def getLocalDomain(): # # Riporta il nome del dominio locale. # locdomn=bcksolve(socket.gethostbyname(socket.gethostname())) p=locdomn.find(DOT) if p!=-1: return locdomn[p+1:] else: return EMPTYSTR ############################# # Supporto al modo "server" # ############################# class ChildThread(threading.Thread): def __init__(self,netclient,servfunz,parent): # # Costruttore (rilancia innanzitutto quello della classe genitore). # threading.Thread.__init__(self) # # Memorizza il socket (oggetto di classe # "tcpStream") su cui si deve lavorare. # self.netClient=netclient # # Memorizza la funzione di gestione del servizio. # self.serverFun=servfunz # # Memorizza il socket (oggetto di classe # "tcpStream") su cui il server e' in ascolto, # per poterne eventualmente comandare l'arresto # tramite il flag "canContinue". # self.netServer=parent # # Setta "daemon=False" in modo che questo thread # rimanga in vita quando terminera' il main, fino # alla chiusura del collegamento in corso. # Vedi: # http://stackoverflow.com/questions/5127401/setdaemon-function-in-thread # "In contrast, when t.daemon is set to True, the thread # is terminated when the main thread ends." # self.daemon=False def run(self): goon=self.serverFun(self.netClient) self.netClient.close() # # E' opportuno separare l'assegnazione di "goon" da # quella di "canContinue" per essere certo che anche # l'istruzione dopo il ritorno da "self.serverFun" # venga sicuramente eseguita! # self.netServer.canContinue=goon class MasterLoopThread(threading.Thread): def __init__(self,servfunz,parent): # # Costruttore (rilancia innanzitutto quello della classe genitore). # threading.Thread.__init__(self) # # Memorizza la funzione di gestione del servizio. # self.serverFun=servfunz # # Memorizza il socket (oggetto di classe # "tcpServer") su cui il server e' in ascolto. # self.netServer=parent # # Setta "daemon=True" in modo che questo thread e # tutti quelli da esso generati (che ereditano questo # valore) vengano terminati quando terminera' il main. # Vedi: # http://stackoverflow.com/questions/5127401/setdaemon-function-in-thread # "In contrast, when t.daemon is set to True, the thread # is terminated when the main thread ends." # self.daemon=True def run(self): # # Master Loop. # while True: # # Attende ed onora una connessione da un processo client. # ATTENZIONE: la funzione "acceptConnection" e` bloccante, # se ne esce solo o per connessione accettata o per errore. # L'eccezione viene intercettata per evitare un messaggio # di errore in caso di arresto forzato del server. # try: netClient=self.netServer.acceptConnection() except: return # # Crea un nuovo thread per gestire la connessione. # ChildThread(netClient,self.serverFun,self.netServer).start() class tcpServer(socket.socket): def __init__(self,servport,servfunz): # # Costruttore (rilancia innanzitutto quello della classe genitore). # socket.socket.__init__(self,family=socket.AF_INET,type=socket.SOCK_STREAM,proto=0,fileno=None) # # Memorizza la funzione di gestione del servizio. # self.servFun=servfunz # # Imposta il socket come bloccante, lo # collega alla porta specificata e si # mette in ascolto. # self.settimeout(None) self.bind(('',servport)) self.listen(BACKLOG) def run(self,multitask): # # Questa classe tcpServer permette di realizzare # sia server iterativi (single task), sia server # concorrenti (multi task basato su threads). # if multitask: # # Il flag "canContinue" serve a gestire le richieste # di shutdown della connessione da parte del client. # self.canContinue=True # # Crea un nuovo thread per gestire l'accettazione # delle richieste di servizio e gli passa la funzione # di gestione del servizio e un riferimento a se stesso. # MasterLoopThread(self.servFun,self).start() else: # # Master Loop. # while True: # # Attende ed onora una connessione da un processo client. # ATTENZIONE: la funzione "acceptConnection" e` bloccante, # se ne esce solo o per connessione accettata o per errore. # L'eccezione viene intercettata per evitare un messaggio # di errore in caso di arresto forzato del server. # try: netClient=self.acceptConnection() except: return # # Per convenzione interna, la funzione di gestione # del servizio (self.servFun) riporta True se il # server deve restare in esecuzione (e quindi tornare # ad aspettare una nuova connessione) quando lei termina, # riporta False per richiedere lo shutdown del server. # if not self.servFun(netClient): return def acceptConnection(self): # # Attende ed accetta la connessione sul socket e ne riporta il # nuovo stream TCP (gia' aperto); e' una funzione bloccante. # (fd,ad)=self._accept() try: net=tcpStream(fileno=fd) except socket.error: return None if socket.getdefaulttimeout() is None and self.gettimeout(): net.setblocking(True) return net ##################### # Classe principale # ##################### class tcpStream(socket.socket): def __init__(self,fileno=None): # # Costruttore (rilancia semplicemente quello della classe genitore). # socket.socket.__init__(self,family=socket.AF_INET,type=socket.SOCK_STREAM,proto=0,fileno=fileno) def open(self,serverName,port,timeOut=None): # # Apre uno stream TCP in modo CLIENT, # inizializzando un socket verso la porta TCP "port" del # server "serverName". # Riporta: # 0: connessione stabilita; # 1: errore nella risoluzione del nome del server; # 2: connessione rifiutata dal server. # self.settimeout(timeOut) # # Modo CLIENT. # Stabilisce l'host remoto con # cui ci si vuole collegare. # binadd=resolve(serverName)[1] if binadd!=b'0': serverIP=ntoa(binadd) else: return 1 # # Effettua la connessione. # try: self.connect((serverIP,port)) except socket.error: return 2 return 0 # End of open ################################################################## # Procedure e funzioni di inizializzazione, controllo e chiusura # ################################################################## def getRemoteAddress(self): # # Riporta l'Indirizzo Internet del corrispondente # remoto nel formato numerico-puntato. # return self.getpeername()[0] def getRemoteName(self): # # Riporta l'Indirizzo Internet del corrispondente # remoto nel formato ASCII alfanumerico. # return bcksolve(self.getpeername()[0]) def stat(self): # # Controlla lo stato del socket riportando: # SCKSTAT_OK se ci sono dati pronti da leggere; # SCKSTAT_EC se la connessione risulta chiusa; # SCKSTAT_ER se NON ci sono dati da leggere. # timeOut=self.gettimeout() # Acquisisce il timeout corrente self.settimeout(0) # Imposta il socket NON bloccante try: # Lettura NON distruttiva ch=self.recv(1,socket.MSG_PEEK).decode() # da bytes a string self.settimeout(timeOut) # Reimposta il timeout originale if ch!=EMPTYSTR: return SCKSTAT_OK else: return SCKSTAT_EC except socket.error as errmsg: errno=int(str(errmsg)[7:12]) if errno!=10035: # No data queued to be read print(str(errmsg)) self.settimeout(timeOut) # Reimposta il timeout originale return SCKSTAT_ER # End of stat def recvready(self): # # Riporta "True" se e solo se # ci sono dati pronti da leggere # sul socket. # return (self.stat()==SCKSTAT_OK) ######################################## # Procedure e funzioni "C-Stream-Like" # ######################################## def getc(self): return self.recv(1).decode() # da bytes a string def putc(self,c): self.send(c.encode()) # da string a bytes def gets(self): ch=EMPTYSTR st=EMPTYSTR while ch!=LINEFEED: ch=self.recv(1).decode() # da bytes a string if ch!=RETURN and ch!=LINEFEED: st=st+ch return st def printf(self,frm): self.send(frm.encode()) # da string a bytes return SCKSTAT_OK def eof(self): # # Riporta "True" se e solo se # il corrispondente ha chiuso la # comunicazione dalla sua parte. # return (self.stat()==SCKSTAT_EC) # End of class tcpStream