Gleichzeitiger Text- + Dateitransfer über rohe TCP Sockets

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
Antworten
tomba
User
Beiträge: 21
Registriert: Montag 14. Dezember 2015, 15:53

Guten Abend, ich habe eine Frage:

Im Rahmen einer Remote-Kommandozeile (vielleicht tue ich die nachher mal in "Showcase") probiere ich möglichst mit einem Socket für die Netzwerkkommunikation zu arbeiten. Nun stellt sich folgendes Problem:

In Version 0.1 ist es nicht möglich gewesen, Dateien zu übertragen.
In Version 0.3 habe ich Dateitransfer implementiert
In Version 1.2 hat das Programm eine optionale GUI bekommen womit man einen "Remote Explorer" hat. (Ist ein grafischer Explorer mit direkter Downloadmöglichkeit vom anderen PC)

Nun bin ich bei Version 1.9 angekommen und will das alte Netzwerkkonzept generalüberholen.
Es soll nämlich möglich sein, dass ich gleichzeitig Kommandos sende und mehrere Dateien übertrage. Dies funktionierte bisher nicht, da TCP ja ein Stream ist (also hängt die Datei im Stream bevor das neue Kommando ankommen kann) Das wollte ich so realisieren:

(Im Folgenden beschreibe ich ein /get Kommando. Also Dateitransfer von Server zum Client)


1. Client-Side Fehlerbehebung (Ist schon vorhanden? Ist genug Platz auf der Platte? Ist Schreibberechtigung im Zielverzeichnis)
2. Kommando für Dateitransfer von Client an Server
3. Server-Side Fehlerbehebung (Ist schon vorhanden? Ist im Zielordner Schreibberechtigung? -> Wenn nein und ja: Bestätigung der Übertragung
4 Öffnen eines neuen Sockets mit "Kommando-Port + 1" (Also Kommandoport 1337, Dateitransferport: 1338) client+server- side
5. Client sendet letzte Bestätigung -> Server sendet, Client empfängt
6. Nach Prüfung der Datei über SHA -> Client ---Bestätigung---> Server oder Anforderung von neuem Transfer
7. Schließen des Dateitransfersockets

Ist das plausibel? Ist das intelligent? Ist das praktikabel? (Technisch durchsetzbar ist es auf jeden Fall)

(Multiple Dateiübertragung will ich so realisieren: Bei Schritt 2 legt der Client eine ID für den Dateitransfer fest. Die Transferpackets sehen so aus:

[1-4] Transfer-ID [5-1024] Nutzdaten

Der Client macht das ca. so:

new_packet = sock.recv(1024) # Natürlich nur in diesem Bsp. Im echten Programm läuft alles vorher durch eine Entschlüsselungsfunktion)
packet_id = new_packet[0:3]
file_object = file_transfer_table[packet_id]
file_object.write(packet_id)

etc.
(Bei Beenden des Transfer [letztes packet] ist die ID 0000. Diese ist nicht als normale ID möglich. So weiß der Client -> Letztes Packet -> Schreiben, Dateiobjekt schließen, Prüfsummencheck. ...)

Also: Ist das ein gutes Konzept? (Vielleicht greife ich bei meinem ID-System noch auf Opcodes zurück, sodass 3 Bytes weniger Verlust sind.)

Danke und einen schönen Abend euch allen noch.
MfG, tomba
Science makes you fly to the moon, religion makes you fly into buildings.
BlackJack

@tomba: Das ist keine wirklich verständliche und vor allem keine Präzise Beschreibung eines Protokolls. Teilweise auch ein wenig verwirrend. Jeder Transfer bekommt eine ID damit mehrere Transfers gleichzeitig laufen können und ein Packet mit der ID 0 signalisiert das Ende eines Transfers? Von welchem Transfer denn wenn mehrere laufen? Und wie ist das mit dem Dateinamen? Was ist mit der Dateilänge? Wie können die Bytes 5 bis 1024 Nutzdaten sein wenn die Dateilänge nicht durch 1019 teilbar ist? Wie kann man vier Byte IDs durch kürzere ”Opcodes” darstellen? Wo wäre da der Unterschied dazu einfach eine kürzere ID zu verwenden?

Ich würde mich ja erst einmal nicht primär mit Bytes sparen beschäftigen sondern ein Protokoll auf einem bereits bestehenden Serialisierungsformat aufsetzen. Beispielsweise Messagepack.
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

@tomba: vor 30 Jahren hatte da mal jemand eine Idee: FTP, dafür gibt es sogar schon Server und Clients für alle möglichen Systeme.
tomba
User
Beiträge: 21
Registriert: Montag 14. Dezember 2015, 15:53

BlackJack hat geschrieben:@tomba: Das ist keine wirklich verständliche und vor allem keine Präzise Beschreibung eines Protokolls. Teilweise auch ein wenig verwirrend. Jeder Transfer bekommt eine ID damit mehrere Transfers gleichzeitig laufen können und ein Packet mit der ID 0 signalisiert das Ende eines Transfers? Von welchem Transfer denn wenn mehrere laufen? Und wie ist das mit dem Dateinamen? Was ist mit der Dateilänge? Wie können die Bytes 5 bis 1024 Nutzdaten sein wenn die Dateilänge nicht durch 1019 teilbar ist? Wie kann man vier Byte IDs durch kürzere ”Opcodes” darstellen? Wo wäre da der Unterschied dazu einfach eine kürzere ID zu verwenden?

Ich würde mich ja erst einmal nicht primär mit Bytes sparen beschäftigen sondern ein Protokoll auf einem bereits bestehenden Serialisierungsformat aufsetzen. Beispielsweise Messagepack.
Der Dateiname wird beim ersten Kommando übermittelt.

/get [dateiname] [opt. lokaler dateiname]

Bytes 5:1024 sind einfach 1019 Bytes aus der Datei gelesen. (fileobject.read(1019)) wo soll denn da ein Problem sein (wegen Teilbarkeit).
(Falls du meinst, dass das letzte Packet nicht genau 1019 Bytes lang ist: es wird einfach alles ab new_packet[9:1024] in die Datei geschrieben.)

Das mit der 0000 ID hatte ich so gelöst, dass das Packet die Alte ID hinter die 0000 schreibt. Sorry für's Vergessen ^^
Science makes you fly to the moon, religion makes you fly into buildings.
tomba
User
Beiträge: 21
Registriert: Montag 14. Dezember 2015, 15:53

Sirius3 hat geschrieben:@tomba: vor 30 Jahren hatte da mal jemand eine Idee: FTP, dafür gibt es sogar schon Server und Clients für alle möglichen Systeme.
Das ist mir klar. Hatte überlegt zu FTPS zu greifen. Will das aber lieber selber machen. (I know ist nicht besonders klug, aber dient Lernzwecken :D)
Außerdem habe ich ein wirklich stabiles Errorhandling. Also es ist nicht so, dass ich mir dadurch irgendwelche Fehler einbaue, welche über FTPS nicht auftreten könnten.
Science makes you fly to the moon, religion makes you fly into buildings.
BlackJack

@tomba: Wie übermittelt man denn eine 42 Bytes lange Datei wenn grundsätzlich immer 1019 grosse Nutzdatenhäppchen übertragen werden? Oder irgendeine andere Dateigrösse die nicht durch 1019 teilbar wäre, wo also mindestens ein Häppchen kleiner als 1019 sein müsste. Oder hast Du da vielleicht noch irgendwas in Deiner Protokollbeschreibung vergessen? ;-)
/get [dateiname] [opt. lokaler dateiname]
Hm, was dürfen Dateinamen denn enthalten? Oder auch nicht… Leerzeichen? Zeilenumbrüche? Beliebige Bytewerte? Wie gehst Du damit um?
tomba
User
Beiträge: 21
Registriert: Montag 14. Dezember 2015, 15:53

BlackJack hat geschrieben:@tomba: Wie übermittelt man denn eine 42 Bytes lange Datei wenn grundsätzlich immer 1019 grosse Nutzdatenhäppchen übertragen werden? Oder irgendeine andere Dateigrösse die nicht durch 1019 teilbar wäre, wo also mindestens ein Häppchen kleiner als 1019 sein müsste. Oder hast Du da vielleicht noch irgendwas in Deiner Protokollbeschreibung vergessen? ;-)
Nein eigentlich nicht. Dann wird halt direkt so gesendet: [0000IDID][Dateidaten]. Außerdem nutze ich beim letzten Packet wie schon vorher beschrieben Padding. Die Fehler die du ansprichst, sind Fehler die jedem einigermaßen fähigen Programmierer spätestens in der Testphase auffallen würden :D Das war nur ein Grundkonzept und kein fertiger Code.
Das hört sich jetzt halt vielleicht alles noch krude an, aber ich vertraue einfach mal auf mich, dass ich das hinbekomme und solche Fehler ausmerze.
BlackJack hat geschrieben:/get [dateiname] [opt. lokaler dateiname]
Hm, was dürfen Dateinamen denn enthalten? Oder auch nicht… Leerzeichen? Zeilenumbrüche? Beliebige Bytewerte? Wie gehst Du damit um?
Enthalten dürfen die alle Zeichen, die die in Linux und Windows enthalten dürfen. Oder hast du je einen "\n" in einem Dateinamen gesehen?
BlackJack hat geschrieben:@tomba: Wie übermittelt man denn eine 42 Bytes lange Datei wenn grundsätzlich immer 1019 grosse Nutzdatenhäppchen übertragen werden? Oder irgendeine andere Dateigrösse die nicht durch 1019 teilbar wäre, wo also mindestens ein Häppchen kleiner als 1019 sein müsste. Oder hast Du da vielleicht noch irgendwas in Deiner Protokollbeschreibung vergessen? ;-)
So soll das von der Interpretation her sein:

Code: Alles auswählen

import os
while True:
	command = raw_input("Command:")
	if len(command.split(" ")) > 1:
		cspl = command.split(" ")
		command = cspl[0]
		old = ""
		cspl.pop(0)
		for item in cspl:
			old += item + " "
		old = old[:-1]
	else:
		continue
	print("CMD:  {}".format(command))
	print("ARGS: {}".format(old))
	if command == "/get":
		cspl = old.split('"')
		if not len(cspl) > 1:
			src = cspl[0]
			dst = os.getcwd()
		else:
			src = cspl[0][:-1]
			dst = cspl[1]
		print("SRC:  {}".format(src))
		print("DST:  {}".format(dst))
	else:
		continue
Science makes you fly to the moon, religion makes you fly into buildings.
BlackJack

@tomba: Okay, ich vertraue nach dem Beitrag jetzt mal *nicht* darauf das Du das sauber hinbekommst. Insbesondere die Kombination von dem letzten Code und das Du so verdammt sicher bist das Du es hinbekommst scheinen mir ein Rezept zu einer Frickellösung zu sein in der Du dauernd irgendwelchen Fehlern hinterher patchst. Kleiner Tipp: Bei den meisten unter Linux üblichen Dateisystemen sind in Dateinamen alle Bytewerte ausser dem Pfadtrenner und dem Nullbyte legal, also bei Pfadangaben alles ausser dem Nullbyte. Inklusive doppelten Anführungszeichen und Zeilenumbrüchen. Und ja, ich habe Zeilenumbrüche schon in Dateinamen gesehen.
tomba
User
Beiträge: 21
Registriert: Montag 14. Dezember 2015, 15:53

BlackJack hat geschrieben:@tomba: Okay, ich vertraue nach dem Beitrag jetzt mal *nicht* darauf das Du das sauber hinbekommst. Insbesondere die Kombination von dem letzten Code und das Du so verdammt sicher bist das Du es hinbekommst scheinen mir ein Rezept zu einer Frickellösung zu sein in der Du dauernd irgendwelchen Fehlern hinterher patchst. Kleiner Tipp: Bei den meisten unter Linux üblichen Dateisystemen sind in Dateinamen alle Bytewerte ausser dem Pfadtrenner und dem Nullbyte legal, also bei Pfadangaben alles ausser dem Nullbyte. Inklusive doppelten Anführungszeichen und Zeilenumbrüchen. Und ja, ich habe Zeilenumbrüche schon in Dateinamen gesehen.
Hmm. Naja ich weiß nicht... Also eigentlich funktioniert es schon gut, aber das mit dem frickeln ist schon so. Also manche Teile sind wirklich relativ rustikal mit try except gehandled :p. Ich vertraue dir einfach mal, da ich mich noch nie so groß mit Dateisystemen auseinandergesetzt habe. Aber ich habe mal auf meinem debian-server probiert solche dateien mit zeilenumbruch - Namen anzulegen. Das funktionierte nie. Unter Windows geht es eh nicht. Da das Projekt clientseitig nur für Windows konzipiert ist, finde ich das so jetzt ganz okay. Bisher hat es bei jeder Möglichkeit funktioniert. Aber ich habe das Gefühl, dass es unsauber ist. Es läuft zwar, aber dieses Frickeln... Naja Erfahrung machts. (vielleicht ist dir aufgefallen, dass ich seit meinem letzten codebeitrag schon probiert habe meinen "Stil" zu bessern, wie du es empfohlen hattest. ). Letztendlich ist das auch alles mehr eine Lernsache mit dem Projekt und es ist immer eine große Hilfe, wenn erfahrenere Personen mir Tipps geben. Also ich probiere da mal mehr von dem code offen zu legen und dann kannst du ja auch mal drüberschauen.
Science makes you fly to the moon, religion makes you fly into buildings.
Antworten