[Python] socket & file, dubbi

Linguaggi di programmazione: php, perl, python, C, bash e tutti gli altri.
Avatar utente
nuzzopippo
Entusiasta Emergente
Entusiasta Emergente
Messaggi: 1671
Iscrizione: giovedì 12 ottobre 2006, 11:34

[Python] socket & file, dubbi

Messaggio da nuzzopippo »

Salve :)

Finalizzando ad una idea, mi sto guardando il trasferimento di files per vedere come trasferirne i metodi nell'ambito di un sistema integrante anche altro genere di comunicazione (essenzialmente testuale).
Sulla scorta di esempi nella docs ed in rete lo scambio "puro" dei file mi è riuscito agevolmente ma andando a verificare in uno scambio dati "misto" mi è apparso chiaro che ci ho capito ben poco (sono ignorantissimo in inglese), dai test effettuati credo proprio che i miei problemi derivino dalla gestione del buffer di comunicazione, come sembra evidente dall'output client sotto (in calce al post metto il codice)

Codice: Seleziona tutto

@nzp:~/test/skt/cli$ python3 cli_02.py
Aperto file per registrazione dati
Ricezione dati ...
data = b'1 2 3 4 5 6 7 8 9 10\n\\x0Grazie per la vostra visita'
1 2 3 4 5 6 7 8 9 10
\x0Grazie per la vostra visita
Ricezione dati ...
data = b''
File ricevuto con successo
Messaggio dal server :  
Connessione chiusa
essendo l'output "data = b'1 2 3 4 5 6 7 8 9 10\n\\x0Grazie per la vostra visita'" comprendente ben tre eventi "data = s.recv(1024)" e corretto, per piccoli files, svuotando il buffer ( istruzione "data = []" ), metodo che NON funziona per grossi file che hanno, per altro, un output "strano", giusto per dare idea:

Codice: Seleziona tutto

1 2 3 4 5 6 7 8 9 10
1 2 3 4 5 6 7
Ricezione dati ...
data = b' 8 9 10\n1 2 3 4 5 6 7 8 9 10\n1 2 3 4 5 6 7 8 9 10\n1 2 3 4 5 6 7 8 9 10\n1 2 3 4 5 6 7 8 9 10\n1 2 3 4 5 6 7 8 9 10\n1 2 3 4 5 6 7 8 9 10\n1 2 3 4 5 6 7 8 9 10\n1 2 3 4 5 6 7 8 9 10\n1 2 3 4 5 6 7 8 9 10\n1 2 3 4 5 6 7 8 9 10\n1 2 3 4 5 6 7 8 9 10\n1 2 3 4 5 6 7 8 9 10\n1 2 3 4 5 6 7 8 9 10\n1 2 3 4 5 6 7 8 9 10\n1 2 3 4 5 6 7 8 9 10\n1 2 3 4 5 6 7 8 9 10\n1 2 3 4 5 6 7 8 9 10\n1 2 3 4 5 6 7 8 9 10\n1 2 3 4 5 6 7 8 9 10\n1 2 3 4 5 6 7 8 9 10\n1 2 3 4 5 6 7 8 9 10\n1 2 3 4 5 6 7 8 9 10\n1 2 3 4 5 6 7 8 9 10\n1 2 3 4 5 6 7 8 9 10\n\\x0Grazie per la vostra visita'
 8 9 10
....
1 2 3 4 5 6 7 8 9 10
1 2 3 4 5 6 7 8 9 10
\x0Grazie per la vostra visita
Ricezione dati ...
data = b''
File ricevuto con successo
Messaggio dal server :  
Connessione chiusa
Il file in questo secondo test è semplice testo con la stessa riga ripetuta 532 volte, una singola volta nel primo output.

È da un po' che cerco di capire coerentemente come funziona il buffer di scrittura/lettura tra socket ma l'unica cosa che ho appreso è che ci ho capito ben poco e che mi manca la funzione "flush()" usata con altri linguaggi (esiste per python?)

Ho in mente metodi diversi da testare ma sarebbe enormemente gradita una mano in proposito (e anche info su eventuali possibili problemi di codifica numerica tra sistemi diversi).

Graie per l'attenzione, il codice in corso :
server

Codice: Seleziona tutto

# _*_ coding: utf-8 _*_

# utilizzare python 3

import socket					# importa il modulo socket

porta = 7679					# definisce la porta di ascolto
s = socket.socket()				# crea il socket
host = socket.gethostname()		# rileva la macchina locale
s.bind((host, porta))			# inizializza la porta
s.listen(5)						# si pone in ascolto

print('Server in ascolto...')

while True:
	conn, ind = s.accept()		# stabilisce la connessione
	print('Stabilita connessione con', ind)
	data = conn.recv(1024)		# riceve il saluto dal client
	print('Ricevuto : ', data.decode('utf-8'))
	
	filename = 'mytext.txt'
	f = open(filename, 'rb')
	l = f.read(1024)
	while (l):
		conn.send(l)
		print('Trasmesso : ', repr(l))
		l = f.read(1024)
		
	f.close()
	
	conn.send(b'\\x0')
	print('Fine trasmissione')
	conn.send('Grazie per la vostra visita'.encode('utf-8'))
	conn.close()
client

Codice: Seleziona tutto

# _*_ coding: utf-8 _*_

# utilizzare python 3

import socket					# importa il modulo socket

porta = 7679					# definisce la porta di ascolto
s = socket.socket()				# crea il socket
host = socket.gethostname()		# rileva la macchina locale

s.connect((host, porta))			# tenta la connessione
s.send('Ciao server!'.encode('utf-8'))			# invia un saluto

with open('file_ricevuto_02', 'wb') as f:
	print('Aperto file per registrazione dati')
	while True:
		print('Ricezione dati ...')
		data = s.recv(1024)
		print('data = %s' % (data))
		if data.decode('utf-8') == '\\x0':
			break
		elif not data:
			break
		else:
			print(data.decode('utf8'))
			# scrive i dati nel file
			f.write(data)
			data = []
f.close()
print('File ricevuto con successo')
data = s.recv(1024)
print('Messaggio dal server : ', data.decode('utf-8'))
s.close()
print('Connessione chiusa')
Fatti non foste a viver come bruti ...
Avatar utente
Claudio_F
Entusiasta Emergente
Entusiasta Emergente
Messaggi: 1463
Iscrizione: lunedì 28 maggio 2012, 18:49
Desktop: Mate/Gnome
Distribuzione: Ubu22.04

Re: [Python] socket & file, dubbi

Messaggio da Claudio_F »

Prova sendall al posto di send.
Avatar utente
nuzzopippo
Entusiasta Emergente
Entusiasta Emergente
Messaggi: 1671
Iscrizione: giovedì 12 ottobre 2006, 11:34

Re: [Python] socket & file, dubbi

Messaggio da nuzzopippo »

Grazie, @Claudio_F

Provato a cambiare tutti i send tanto nel server quanto nel client, la faccenda non cambia, rientrano nel buffer anche i dati "extra" e vengono scritti sul file, cosa che non dovrebbe essere.

A parte l'andarsi a spulciare per bene la marea di opzioni e metodi, appena avrò un ritaglio proverò a vedere sendfile() su di uno stream, al momento riesco ad immaginare due possibilità :
1 - comunicare la dimensione del file in trasmissione e gestire il buffer in maniera da scriverci solo i bytes corrispondenti (qualche perplessità, dico dopo);
2 - creare oggetti specifici per la trasmissione/ricezione dei soli file, in pratica nuovi socket su porta diversa, più pratico ma problematico per numerosi file.

Riguardo al primo punto, nel Socket Programming How-to, ho trovato questo :
Ma se pianificate di riusare il vostro socket per ulteriori trasferimenti, dovete rendervi conto che non esiste una cosa come un ``EOT'' (End of Transfer - Fine del Trasferimento) su un socket. Ripeto: se send o recv di un socket ritorna dopo aver gestito 0 byte, la connessione è stata interrotta. Se la connessione non è stata interrotta, aspetterete un recv all'infinito, in quanto il socket non vi dirà che non c'è più niente da leggere (per ora). Ora, se ci pensate su un po', arriverete a comprendere una fondamentale verità sui socket: i messaggi devono essere di una ``determinata'' lunghezza (sigh!), o essere delimitati (tzè...!?), o indicare quanto sono lunghi (molto meglio!), o finire facendo cadere la connessione. La scelta è interamente vostra, (ma alcune strade sono più giuste di altre).
Mi sembra che avverta pari pari della situazione che accade, la logica che ho applicato sarebbe, quindi, insufficiente, mi sa che dovrò studiarmi le indicazioni sulla "lunghezza" o le delimitazioni.

[Edit] ... e, purtroppo, pare proprio che l'indicazione sopra riportata sia vera in ogni circostanza, addirittura assegnando a due variabili diverse due (teoricamente) successive letture, da una veloce verifica in un momento di pausa appare evidente che vengono comunque caricati nel primo buffer disponibile successivi invii, dato il seguente codice rimaneggiato per saggiare la dimensione dei dati :
server

Codice: Seleziona tutto

# _*_ coding: utf-8 _*_

# utilizzare python 3

import socket               # importa il modulo socket
import os					# importa il modulo os

porta = 7679               # definisce la porta di ascolto
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)            # crea il socket
host = socket.gethostname()      # rileva la macchina locale
s.bind((host, porta))         # inizializza la porta
s.listen(5)                  # si pone in ascolto

print('Server in ascolto...')

try:
	while True:
		conn, ind = s.accept()      # stabilisce la connessione
		print('Stabilita connessione con', ind)
		data = conn.recv(1024)      # riceve il saluto dal client
		print('Ricevuto : ', data.decode('utf-8'))

		# definisce il file e ne trasmette i dati
		filename = 'my_text1.txt'
		dimfile = str(os.stat(filename).st_size)
		fdescr = filename + ',' + dimfile
		conn.sendall(fdescr.encode('utf-8'))
		
		
		f = open(filename, 'rb')
		l = f.read(1024)
		while (l):
			conn.sendall(l)
			print('Trasmesso : ', repr(l))
			l = f.read(1024)
		  
		f.close()

		print('Fine trasmissione')
		conn.sendall('Grazie per la vostra visita'.encode('utf-8'))
		conn.close()
except KeyboardInterrupt:
	s.close()
	print("Server chiuso dall'utente : Uscita")
client

Codice: Seleziona tutto

# _*_ coding: utf-8 _*_

# utilizzare python 3

import socket               							# importa il modulo socket

porta = 7679               								# definisce la porta di ascolto
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)	# crea il socket
host = socket.gethostname()								# rileva la macchina locale

s.connect((host, porta))								# tenta la connessione
s.sendall('Ciao server!'.encode('utf-8'))				# invia un saluto

# riceve i dati del file
data = s.recv(1024)
fdata = data.decode('utf-8').split(',')
print('Ricevuto : ', fdata)
fnome = fdata[0]
fdim = int(fdata[1])
# una variabile di controllo
fresto = fdim
# apre il file
f = open(fnome, 'wb')
# e cerca di scaricarlo e scriverlo
while fresto > 0:
	datafile = s.recv(1024)
	f.write(datafile)
	# sottrae i byte ricevuti
	fresto -= len(datafile)
	# azzera la variabile dei dati ricevuti
	datafile = []
	# stampa per "controllare"
	print('Bytes restanti : ', fresto)
	
f.close()
print('File ricevuto con successo??')
data = s.recv(1024)
print('Messaggio dal server : ', data.decode('utf-8'))
s.close()
print('Connessione chiusa')
Si ha questo sconfortante output ed il saluto scritto nel file

Codice: Seleziona tutto

:~/my_tmp/test/skt/cli$ python3 cli_04.py
Ricevuto :  ['my_text1.txt', '20']
Bytes restanti :  -27
File ricevuto con successo??
Messaggio dal server :  
Connessione chiusa
... Sembra evidente che sia necessario un contemporaneo sforzo si sincronizzazione tra server e client in merito al controllo della quantità di dati negoziati, ammesso sia possibile farlo.

[Ri-Edit]
Sinceramente mi sembra strano ma con uno sporco trucco parrebbe funzionare, controllando nel ciclo del client la quantità di dati ricevuti si possono estrapolare i dari del file e sriverlo correttamente ... forse ;)
alterando il client in questo modo

Codice: Seleziona tutto

# _*_ coding: utf-8 _*_

# utilizzare python 3

import socket               							# importa il modulo socket

porta = 7679               								# definisce la porta di ascolto
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)	# crea il socket
host = socket.gethostname()								# rileva la macchina locale

s.connect((host, porta))								# tenta la connessione
s.sendall('Ciao server!'.encode('utf-8'))				# invia un saluto

# riceve i dati del file
data = s.recv(1024)
fdata = data.decode('utf-8').split(',')
print('Ricevuto : ', fdata)
fnome = fdata[0]
fdim = int(fdata[1])
# una variabile di controllo
fresto = fdim
# apre il file
f = open(fnome, 'wb')
# e cerca di scaricarlo e scriverlo
while fresto > 0:
	datafile = s.recv(1024)
	# verifica che i byte ricevuti non eccedato il resto
	if len(datafile) > fresto:
		data = datafile[fresto:]
		datafile = datafile[:fresto]
	#scrive nel file
	f.write(datafile)
	# sottrae i byte ricevuti
	fresto -= len(datafile)
	# azzera la variabile dei dati ricevuti
	datafile = []
	# stampa per "controllare"
	print('Bytes restanti : ', fresto)
	
f.close()
print('File ricevuto con successo??')
if len(data) > 0:
	print('È restato : ', data.decode('utf-8'))
	
data = s.recv(1024)
print('Messaggio dal server : ', data.decode('utf-8'))
s.close()
print('Connessione chiusa')
ed applicando la ricezione su due files diversi, uno di una riga e l'altro di 512 i files sono stati trascritti correttamente ... solo che l'output dei due casi fa tremare i polsi

Codice: Seleziona tutto

:~/my_tmp/test/skt/cli$ python3 cli_04.py
Ricevuto :  ['my_text1.txt', '20']
Bytes restanti :  0
File ricevuto con successo??
È restato :  Grazie per la vostra visita
Messaggio dal server :  
Connessione chiusa
:~/my_tmp/test/skt/cli$ python3 cli_04.py
Ricevuto :  ['my_text2.txt', '10240']
Bytes restanti :  9216
Bytes restanti :  8192
Bytes restanti :  7168
Bytes restanti :  6144
Bytes restanti :  5120
Bytes restanti :  4096
Bytes restanti :  3072
Bytes restanti :  2048
Bytes restanti :  1024
Bytes restanti :  0
File ricevuto con successo??
È restato :  my_text2.txt,10240
Messaggio dal server :  Grazie per la vostra visita
Connessione chiusa
perché indicano che è stato solo un caso, essendo, nella trasmissione "grossa", la rimanenza il messaggio di definizione dei dati del file :cry:
Fatti non foste a viver come bruti ...
Avatar utente
Claudio_F
Entusiasta Emergente
Entusiasta Emergente
Messaggi: 1463
Iscrizione: lunedì 28 maggio 2012, 18:49
Desktop: Mate/Gnome
Distribuzione: Ubu22.04

Re: [Python] socket & file, dubbi

Messaggio da Claudio_F »

Ho ritrovato un vecchio codice (non ricordo neppure se py2 o py3) in cui il trasferimento era affidato ad un protocollo di livello superiore (in questo caso HTTP). Nell'HTTP è sempre inclusa l'informazione content-length:

Codice: Seleziona tutto

#-CLIENT--------------------------------------------------------------
from http.client import HTTPConnection

conn = HTTPConnection( "localhost", 8080 )
conn.request( "POST", "/", body=open("my_file.txt", "rb") )
resp = conn.getresponse()
conn.close()

Codice: Seleziona tutto

#-SERVER--------------------------------------------------------------
from http.server import HTTPServer, BaseHTTPRequestHandler

def elab(x):
    print( "YOU HAVE ME SENT:\n" + x.decode("ascii") )

class Handler(BaseHTTPRequestHandler):

    def do_POST(self):
        len_data = int( self.headers.get("content-length") )
        data = self.rfile.read(len_data)
        elab(data)
        self.send_response(200)
        self.connection.shutdown(1)

HTTPServer( ("localhost", 8080), Handler ).serve_forever()
Avatar utente
nuzzopippo
Entusiasta Emergente
Entusiasta Emergente
Messaggi: 1671
Iscrizione: giovedì 12 ottobre 2006, 11:34

Re: [Python] socket & file, dubbi

Messaggio da nuzzopippo »

Claudio_F [url=https://forum.ubuntu-it.org/viewtopic.php?p=5004597#p5004597][img]https://forum.ubuntu-it.org/images/icons/icona-cita.gif[/img][/url] ha scritto:Ho ritrovato un vecchio codice (non ricordo neppure se py2 o py3) in cui il trasferimento era affidato ad un protocollo di livello superiore ...
Grazie della segnalazione, senz'altro ci saranno circostanze in cui mi sarà utile.

In realtà il mio "pianto" nel precedente post era affrettato, ho rivisto il codice ed avevo dimenticato di azzerare la variabile "data", quindi ne veniva esposto il contenuto.

Ho verificato a casa (li non ho connessione al momento) ed ho visto che le ultime versioni di codice postate funzionano correttamente, ho testato con grossi file in formato pdf, doc ed immagine ed in tutti i casi ha funzionato bene, nessuna corruzione dei files e si aprivano correttamente, in alcuni casi ho avuto come "resto" l'ultimo messaggio da server in altri no.

In sostanza, il principio delle problematiche che hanno originato questo post è l'impossibilità di svuotare il buffer di comunicazione tra un invio e l'altro ed il sovrapporsi nello stack di comunicazione di messaggi già rilevati e non, cosa che mi ha mandato in confusione e che i test mi stanno chiarendo (utile il sendall da Te indicato).

Comunque, il mio obiettivo, di cui ora sto testando le possibilità, è realizzare un sistema di comunicazione e comando che per un verso trasmetta dati da utilizzarsi per logins e comando o da elaborare in strutture dati su files di testo che andranno anche a costituire delle tabelle html da esporre in pagine statiche da costruire nell'applicazione, per altro verso trasmetta files che, ovviamente, dovranno essere "integri", al momento ho verificato che la cosa è fattibile ma ci sono da valutare notevoli problematiche di gestione dei buffer di comunicazione, è questo l'aspetto determinante ... forse sarebbe il caso di variare in tal senso il titolo del post?

Probabilmente, per altro, l'indicazione della dimensioni dei dati trasmessi può ottenersi anche in una gestione a basso livello dei socket.

Argomento complesso, credo, la gestione dei buffer di comunicazione tra socket in python, ovviamente son tutto orecchi qualora utenti edotti in materia riterranno di dare una mano.

Intanto grazie @Claudio_F :birra:
Fatti non foste a viver come bruti ...
Scrivi risposta

Ritorna a “Programmazione”

Chi c’è in linea

Visualizzano questa sezione: 0 utenti iscritti e 3 ospiti