Erstmal ein wenig auseinander frimeln …
Du sagst “Socket <-> ServerSocket”, das impliziert das du TCP nutzt, und TCP ist grundsätzlich eine bi-direktionale Verbindung, es können also über eine Verbindung in beide Richtungen Daten ausgetauscht werden. Damit fällt dein Ansatz bezüglich zweier unterschiedlicher Ports schon mal raus.
[off-top]An jeden der den Einwand bringen will : ja, ich weis selbst wie FTP abläuft und das es dort so gemacht wird, aber darauf komme ich später.[/off-top]
Dann hast du noch erwähnt das du von EINEM InputStream mit ZWEI Threads “parallel” liest. Schon mal ne ganz schlechte Idee. Der Grund liegt darin das es normalerweise interne Buffer gibt so das wenn man in einem Thread was liest gewisse Informationen für den anderen Thread verloren und somit die Daten “kaputt gehen”. Davon abgesehen ist es bei Nebenläuffigkeit ohne weitere synchronisation nicht vorhersehbar was wann wie passiert, also ob halt erst Thread1 an die Reihe kommt oder Thread2.
Von daher würde ich empfehlen : nutze nur EINEN Thread der die Daten liest, auswertet/verarbeitet, und wenn nötig an andere Threads weitergibt.
Weiter im Text : deine Aussage bezüglich “der InputStream der anderen Seite blockiert” musst du zumindest mir bitte noch mal deutlicher erklären. Natürlich blockiert InputStream.read() so lange bis Daten vorliegen die dann auch eingelesen werden können, das besagt schon die DOC weshalb man solche Methoden auch “blockierende Methoden” nennt : der Aufruf bleibt an dieser Stelle so lange stehen bis ein Ergebnis geliefert werden kann. Wenn du also einen Thread hast der den InputStream liest, und einen zweiten der gerade was in den OutputStream zu Gegenstelle reinschreibt, dann bleibt read() trotzdem stehen da ja noch keine Daten von der Gegenstelle gekommen sind.
Von deiner Problembeschreibung her lässt sich darauf schließen das du eine Race-Condition bzw. einen Deadlock hast : beide Seiten warten auf die jeweils andere.
Was auch durchschimmert : du hast kein Protokoll, also keine Festlegung in welcher Form die Daten übertragen werden sondern sendest halt munter drauf los und hoffst dass das ganze auf der Gegenseite schon irgendwie zusammenpassend ankommt. Auch das kann natürlich zu weiteren Fehlern, Datenverlust oder ganz anderen Dingen führen.
Vielleicht solltest du dir einfach noch mal einen Zettel und einen Stift zur Hand nehmen und dir Schritt für Schritt überlegen wie das Ganz ablaufen soll. Auch solltest du dir dabei gleich überlegen wie die eine Seite der anderen Commandos übermittelt und wie diese von Payload-Daten getrennt werden.
Ein einfacher Anfang ist es das man die Länge mitsendet : man baut sich auf Seite A die gesamte Nachricht zusammen, ermittelt dann ihre Länge, und schickt diese Information voran. Auf der Gegenseite liest man dann erstmal nur die Länge und weis dann wie viele Daten kommen werden. Das kann man jetzt natürlich noch weiter unterteilen das man erstmal sagt : Länge des nächsten Commandos, Commando, Payload. So weis dann Seite B : aha, jetzt muss ich erstmal 10Byte an Commando lesen, kann dieses dann auswerten, und weis dann durch dieses Commando was an Payload kommt. Parktisch sollte man dann noch in das Commando selbst bzw anschließend die Länge des Payloads packen damit man weis wie viel kommt.
Auch kann man es bei binären Protokollen etwas sparsamer machen in dem man die ersten 1 oder 2 Bytes nicht für die Länge des Commandos nimmt sondern das Commando selbst in diesen festgelegten Header drückt.
Kleines Beispiel : sagen wir mein Protokoll sieht folgendermaßen aus : 2Byte Commando + 4Byte Länge des Payload + Payload
Dann bleibt mir :
2Byte Commando = 16 bit = 65536 Möglichkeiten für ein bestimmtes Commando was ich mit diesen 2 Bytes darstellen kann. Das sollte mehr als ausreichen sein. Normalerweise reicht sogar genau 1Byte = 8Bit = 256Möglichkeiten, weil was bitte ist so umfangreich das man mehr als 256 verschiedene Commando-Sequenzen braucht, aber egal.
4Byte Länge des Payload = 32 Bit = 4294967296 Möglichkeiten. Normalerweise wird ein “signed Int” genutzt, also 31Bit = 2GigaByte. Ich kann also mit den folgenden 4Bytes die Größe der nachfolgenden Nutzdaten bis zu 2GB angeben. Und so große Daten sendet man eigentlich nicht in einem Stück.
Du siehst : mit 6Bytes kann ich bereits angeben WAS gemacht werden soll und WIE VIEL noch an Nutzdaten kommt die dann dem Commando entsprechend zu verarbeiten sind. Diese 6Bytes sind fester Bestandteil des Protokolls, kommen also grundsätzlich immer als erstes. Jetzt kann man seinen Code so aufbauen das der main-Thread mit einem InputStream.read(byte[6]) darauf wartet genau diese 6 Bytes von der Gegenstelle zu lesen und dann zu verarbeiten. Wenn man dann weis was mit den folgenden Daten gemacht werden soll und wie viel nocht kommt, dann liest man diese Daten entsprechend hintereinander ein und übergibt sie dann an z.B. an einen weiteren Thread zur Bearbeitung so das man währenddessen schon die nächsten Daten lesen kann.
Um noch mal auf FTP zu kommen was ich vorhin angerissen hatte : bei FTP läuft das alles ein wenig anders ab. Es gibt nämlich eigentlich 2 Verbindungen : einen Steuer-Kanal (TCP/21) und einen Daten-Kanal (TCP/20). Auf dem Steuer-Kanal werden nur reine Commandos und Status-Rückmeldungen übertragen aber keinerlei Nutzdaten. Auf dem Daten-Kanal entsprechend umgekehrt.
So, was passiert nun : der Client baut eine Verbindung über den Daten-Kanal TCP/21 zum Server auf und meldet sich darüber bei diesem. Es wird dann ein bisschen an Infos ausgetauscht bis es dann zur ersten Eigentlichen Datenübertragung kommt : LIST , dem Auflisten des aktuellen Verzeichnis. Beim “aktiven” FTP baut dann der Server eine Verbindung zum Client auf TCP/20 auf (zu passiv komm ich gleich) und tauscht dann darüber die Daten mit dem Client aus. Wenn also der Client über den Steuer-Kanal ein LIST schickt dann meldet der Server darüber zurück OK , dann die Länge wie viele Daten kommen, und dann die eigentlichen Daten über den Daten-Kanal. Die Information wie viele Daten kommen ist dabei natürlich für den Client sehr wichtig damit er weis wann die Übertragung beendet ist.
Heutzutage findet hingegen fast ausschließlich das “passive” FTP anwendung : dabei wird nicht vom Server eine Verbindung zum Client aufgebaut, sondern der Client sendet dem Server ein PASV, dieser macht darauf hin einen zweiten Socket auf und übermittelt dessen Daten über den Steuer-Kanal zurück an den Client. Dieser baut dann zu diesem Socket auch eine Verbindung auf und nutzt diese für den Daten-Austausch. Wichtig dabei ist das der Server anhand der dem Client übermittelten Daten auch selbst noch wissen muss das dieser zweite Socket zu dieser bestimmten Client-Session gehört. Ist an sich nicht kompliziert und schnell umgesetzt, kann bei Fehlern aber verdammt nach hinten losgehen.
Vielleicht noch als Tipp : dank Base64 kann man auch Binär-Daten über eine Text-Verbindung übertragen, das wäre dann mit den in Java vorhanden Mitteln vermutlich erstmal die einfachste Art da du auf Dinge wie z.B. readLine() eines Reader zurückgreifen kannst und dich erstmal nicht weiter mit der Länge rumschlagen musst. Sollte man aber nur nutzen um erstmal die Kommunikation an sich ans Laufen zu bekommen.
Wenn du noch mehr Ansätze brauchst kann ich dir gerne mal was einfaches zusammenbasteln damit du ein ganz grobe Struktur hast womit man dann erstmal ein paar Daten hin und her senden kann.