Java TCP-Sockets transmettent des fichiers de plus de 4 go [dupliquer]


Cette question a déjà une réponse ici:

J'essaie de transférer un fichier supérieur à 4 go en utilisant Java SocketsAPI. Je le lis déjà via InputStreams et je l'écris via OutputStreams. Cependant, en analysant les paquets transmis dans Wireshark, je me rends compte que la séquence le nombre des paquets TCP est incrémenté par la longueur d'octet du paquet, qui semble être 1440byte.

Cela conduit au comportement selon lequel lorsque j'essaie d'envoyer un fichier supérieur à 4 go, la taille totale du champ Numéro de séquence de TCP est dépassée, ce qui entraîne de nombreux paquets d'erreurs, mais aucune erreur en Java.

Mon code de transmission ressemble actuellement à ceci:

 DataOutputStream fileTransmissionStream = new DataOutputStream(transmissionSocket.getOutputStream());
                    FileInputStream fis = new FileInputStream(toBeSent);
                    int totalFileSize = fis.available();
                    fileTransmissionStream.writeInt(totalFileSize);
                    while (totalFileSize >0){
                        if(totalFileSize >= FileTransmissionManagementService.splittedTransmissionSize){
                            sendBytes = new byte[FileTransmissionManagementService.splittedTransmissionSize];
                            fis.read(sendBytes);
                            totalFileSize -= FileTransmissionManagementService.splittedTransmissionSize;
                        } else {
                            sendBytes = new byte[totalFileSize];
                            fis.read(sendBytes);
                            totalFileSize = 0;
                        }
                        byte[] encryptedBytes = DataEncryptor.encrypt(sendBytes);
                        /*byte[] bytesx = ByteBuffer.allocate(4).putInt(encryptedBytes.length).array();
                        fileTransmissionStream.write(bytesx,0,4);*/
                        fileTransmissionStream.writeInt(encryptedBytes.length);
                        fileTransmissionStream.write(encryptedBytes, 0, encryptedBytes.length);

Qu'ai-je fait de mal dans cette situation, ou n'est-il pas possible de transmettre des fichiers supérieurs à 4 go via une Socket?

Author: aWdas, 2015-05-27

2 answers

TCP peut gérer des flux de données infiniment longs. Il n'y a pas de problème avec le numéro de séquence qui s'enroule. Comme il est initialement aléatoire, cela peut arriver presque immédiatement, quelle que soit la longueur du flux. Les problèmes sont dans votre code:

DataOutputStream fileTransmissionStream = new DataOutputStream(transmissionSocket.getOutputStream());
FileInputStream fis = new FileInputStream(toBeSent);
int totalFileSize = fis.available();

Utilisation abusive classique de available(). Jetez un œil au Javadoc et voyez à quoi il sert vraiment. C'est également là que réside votre problème de base, car les valeurs > 2G ne rentrent pas dans un int,, il y a donc une troncature. Vous devriez utiliser File.length(), et stocker il en un long.

fileTransmissionStream.writeInt(totalFileSize);
while (totalFileSize >0){
    if(totalFileSize >= FileTransmissionManagementService.splittedTransmissionSize){
        sendBytes = new byte[FileTransmissionManagementService.splittedTransmissionSize];
        fis.read(sendBytes);

Ici, vous ignorez le résultat de read() ici. Il n'est pas garanti de remplir le tampon: c'est pourquoi il renvoie une valeur. Voir, encore, le Javadoc.

        totalFileSize -= FileTransmissionManagementService.splittedTransmissionSize;
    } else {
        sendBytes = new byte[totalFileSize];

Ici, vous supposez que la taille du fichier correspond à un int, et que les octets correspondent à la mémoire.

        fis.read(sendBytes);

Voir ci-dessus re read().

        totalFileSize = 0;
    }
    byte[] encryptedBytes = DataEncryptor.encrypt(sendBytes);
    /*byte[] bytesx = ByteBuffer.allocate(4).putInt(encryptedBytes.length).array();
    fileTransmissionStream.write(bytesx,0,4);*/

Nous ne sommes pas intéressés par votre code commenté.

    fileTransmissionStream.writeInt(encryptedBytes.length);
    fileTransmissionStream.write(encryptedBytes, 0, encryptedBytes.length);

Vous n'avez pas besoin de tout ce crud. Utilisez un CipherOutputStream pour prendre soin du cryptage, ou mieux encore SSL, et utilisez la boucle de copie suivante:

byte[] buffer = new byte[8192]; // or much more if you like, but there are diminishing returns
int count;
while ((count = in.read(buffer)) > 0)
{
    out.write(buffer, 0, count);
}
 2
Author: user207421, 2015-05-27 08:21:07

Il semble que votre protocole pour la transmission soit:

  1. Envoyer la longueur totale du fichier dans un int.
  2. Pour chaque groupe d'octets lire,
    1. Envoyer le nombre d'octets cryptés à l'avance dans un int,
    2. Envoyez les octets entrypted eux-mêmes.

Le problème de base, au-delà des interprétations erronées de la documentation qui ont été soulignées dans la réponse de @EJP, concerne ce protocole même.

Vous supposez que la longueur du fichier peut être envoyée four dans un int. Cela signifie que la longueur qu'il envoie ne peut pas être supérieure à Integer.MAX_VALUE. Bien sûr, cela vous limite aux fichiers de longueur 2G (rappelez-vous que les entiers Java sont signés).

Si vous jetez un oeil à la Files.size() la méthode, qui est une méthode pour obtenir la taille réelle du fichier en octets, vous verrez qu'il retourne long. Un long peut accueillir des fichiers de plus de 2 Go et de plus de 4 Go. Donc, en fait, votre protocole devrait à tout le moins être défini pour commencer par un long plutôt que un champ int.

Le problème de taille n'a vraiment rien à voir avec les paquets TCP.

 0
Author: RealSkeptic, 2015-05-27 08:28:45