Transfer de fichier en 'chunks'

Problème lors de la construction du fichier envoyé

Le problème exposé dans ce sujet a été résolu.

Salut :) ,

Je suis dans la création d'un module de transfert de fichier. Je mettais penché vers socket.io-stream mais j'ai vite essayé de créer mon propre module en voyant ses performances médiocres…

Donc j'ai réfléchis et j'ai fais hier soir un petit code client/serveur:

  • le client choisit un fichier qu'il découpe en plusieurs chunks pour les envoyer au serveur.

  • le serveur réceptionne les chunks et reconstruit le fichier.

Sauf que lors de la réception, ça se passe pas totalement comme prévu, en effet en transférant une vidéo de 47mo, le serveur reconstruit un fichier de 67mo. En analysant les 2 fichier sur notepad, on remarque qu'il sont relativement proches mais pas totalement. Et je n'explique pas cette différence… Je me suis dis, ça vient peut être du découpage des chunks (en 65507o) mais en fait non ^^.

Voici les codes:

client.js :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
var net = require('net');
var fs = require("fs");

var client = new net.Socket();
client.connect(3333, '127.0.0.1', function() {
    console.log('Connected');
    var filepath = 'files/video.mp4';

    fs.readFile(filepath, {encoding: 'binary'}, function (err, data) {
        if (err) throw err;

        var position = 0;
        var chunkSize = 65507;
        var currentSize = 0;

        function chunking() {
            var next = position + chunkSize;
            var chunk = data.slice(position, next);
            client.write(chunk);
            currentSize += chunk.length;
            position += chunkSize;
            console.log(currentSize);
        }
        while(data.length != currentSize)
            chunking();
    });
});

client.on('data', function(data) {
    console.log('Received: ' + data);
});

client.on('close', function() {
    console.log('Connection closed');
});

server.js

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
var net = require('net');
var fs = require("fs");
var currentSize = 0;
var stream = fs.createWriteStream("files/video2.mp4");

var server = net.createServer(function(socket) {
    socket.on('data', function (data) {
        currentSize += data.toString().length;
        console.log(currentSize);
        stream.write(data);
    });
});

stream.on('finish', function () {
  console.log('finished writing');
  stream.end();
});

server.listen(3333, '127.0.0.1');
+0 -0

Oui j'ai justement fait des tests avec de plus petits fichiers textes, en changeant la taille des chunks, ça marche parfaitement. A part avec les accents, j'ai dû changer l'encoding binary en utf8. Mais dès que j'essaye avec une vidéo ou mm une petite image, ça ne marche plus. Le fichier créé par le serveur est plus gros, pourtant dans la console, j'affiche la taille de tous les chunks reçus et cela m'affiche une taille très proche du fichier envoyé par le client.

+0 -0

Ouais en fait je pense avoir compris:

image client :

image client

image serveur:

image serveur

On voit en comparant les images que c’est peut être un problème d’encoding. Mais je ne sais pas comment pallier ce problème.

+0 -0

Je pense avoir trouvé : quand tu créé ton writeStream dans le serveur, il faut indiquer l'encodage en binary (ou du moins, le même que celui du client).

Aussi, ligne 8, tu convertis ton buffer en string pour récupérer la taille alors qu'il faudrait récupérer la taille directement currentSize += data.length;.

J'ai fais ce que tu m'as dis, en ce qui concerne la ligne 8 du serveur, ça m'affiche dorénavant la bonne taille. En ce qui concerne l'encodage, j'ai mis côté serveur :

1
var stream = fs.createWriteStream("files/video2.mp4", {encoding: 'encodage'});

J'ai testé avec l'encodage binary et après utf8 (en adéquation avec le client et le serveur), je n'arrive pas à avoir le mm résultat à la réception par le serveur. On voit que c'est très proche mais l'encodage différe alors que j'ai mis le mm pour le client et le serveur. ET ça change considérablement la taille du fichier reçu. En effet, avec un fichier de 46mo, j'arrive à avoir un fichier de 87mo.

+0 -0

La première chose visible est que ce sont les octets qui sont dans la range 128-255 qui posent problème (ils sont au final remplacés par �). On peut donc supposer que ce n'est pas la taille des chunks qui pose problème. On peut donc faire des tests avec un fichier relativement petit.

Ensuite, il pourrait être intéressant d'afficher le contenu (en hexadécimal) du buffer à chaque étape : client ligne 9 (le contenu de data), client ligne 18 (le contenu de chunk) et serveur ligne 7 (le contenu de data). Ça permettra de mieux voir l'endroit qui pose soucis. Idéalement, il faudrait aussi analyser le trafic réseau pour voir ce qui passe réellement, mais ça risque d'être assez compliqué si tu ne sais pas comment faire.

Au passage, il faudrait que tu vérifies que tu as bien {encodage: 'binary'} à l'ouverture du fichier par le client et à la création de stream par le serveur. Vu que tu manipules des fichiers binaires, l'utf-8 ne convient pas.

Après quelques recherches, il semblerait que la séquence � (en ascii) corresponde à un caractère non reconnu en utf-8. Ce qui me laisse penser qu'il y a un endroit où tu lis des données binaires comme si c'était de l'utf-8.

Je n'arrive pas à comprendre ce qu'il se passe. J'ai comparé le premier chunk envoyé par le client et le premier chunk reçu par le serveur, ils sont toujours différents. Et ça vient toujours de l’encodage, en fait l'encodage binaire ne se fait pas, en effet, je devrais avoir des chunks de 0 et de 1 et c'est pas le cas … J'ai changé en héxa et là l'encodage se fait correctement niveau client et serveur mais on constate quelques choses de bizarre au niveau des chunks et du fichier reçu : il est parfaitement le double du fichier envoyé. Je suis un peu perdu, si quelqu’un se sent pouvoir tester mon code (je sais c'est beaucoup demandé). J'ai analysé avec Wireshark le trafic réseau mais je n'en ais rien tiré me permettant de comprendre le phénomène :/

+0 -0

Serveur.js

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
var net = require('net');
var fs = require("fs");
var currentSize = 0;
var stream = fs.createWriteStream("files/fichier_recu.txt", {encoding: 'hex'});

var server = net.createServer(function(socket) {
    socket.on('data', function (data) {
        currentSize += data.length;
        console.log(currentSize);
        fs.writeFile("bug/chunks_serveur/chunk_" + currentSize, data, function(err) {
            if (err) throw err;
        });
        stream.write(data);
    });
});

stream.on('finish', function () {
  console.log('finished writing');
  stream.end();
});

server.listen(3333, '127.0.0.1');

Client.js

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
var net = require('net');
var fs = require("fs");

var client = new net.Socket();
client.connect(3333, '127.0.0.1', function() {
    console.log('Connected');
    var filepath = 'files/fichier_envoye.txt';

    fs.readFile(filepath, {encoding: 'hex'}, function (err, data) {
        if (err) throw err;

        fs.writeFile("bug/data", data, function(err) {
            if (err) throw err;
        }); 

        var position = 0;
        var chunkSize = 65507;
        var currentSize = 0;

        function chunking() {
            var next = position + chunkSize;
            var chunk = data.slice(position, next);

            fs.writeFile("bug/chunks_client/chunk_" + position, chunk, function(err) {
                if (err) throw err;
            }); 

            client.write(chunk);
            currentSize += chunk.length;
            position += chunkSize;
            console.log(currentSize);
        }
        while(data.length != currentSize)
            chunking();
    });
});

client.on('data', function(data) {
    console.log('Received: ' + data);
});

client.on('close', function() {
    console.log('Connection closed');
});
+0 -0

Si tu regarde le fichier d'arrivé, as tu une répétition ? 2 fois les mêmes chunks d'affilés, ou 2 fois d'affilés le contenu complet ? tu peux essayer de remplacer dans le client

1
currentSize += chunk.length;

par

1
currentSize += chunk.length / 2;
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
var net = require('net');
var fs = require("fs");
var currentSize = 0;
var stream = fs.createWriteStream("lele.png");

var server = net.createServer(function(socket) {
    socket.on('data', function (data) {
        currentSize += data.length;
        console.log(currentSize);
        fs.writeFile("bug/chunks_serveur/chunk_" + currentSize, data, function(err) {
            if (err) throw err;
        });
        stream.write(data);
    });
});

stream.on('finish', function () {
  console.log('finished writing');
  stream.end();
});

server.listen(3333, '127.0.0.1');
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
var net = require('net');
var fs = require("fs");

var client = new net.Socket();
client.connect(3333, '127.0.0.1', function() {
    console.log('Connected');
    var filepath = 'lala.png';

    fs.readFile(filepath, function (err, data) {
        if (err) throw err;

        fs.writeFile("bug/data", data, function(err) {
            if (err) throw err;
        });

        var position = 0;
        var chunkSize = 65507;
        var currentSize = 0;

        function chunking() {
            var next = position + chunkSize;
            var chunk = data.slice(position, next);

            fs.writeFile("bug/chunks_client/chunk_" + position, chunk, function(err) {
                if (err) throw err;
            });

            client.write(chunk);
            currentSize += chunk.length;
            position += chunkSize;
            console.log(currentSize);
        }
        while(data.length != currentSize)
            chunking();
    });
});

client.on('data', function(data) {
    console.log('Received: ' + data);
});

client.on('close', function() {
    console.log('Connection closed');
});

J'ai pas trouvé hex comme encoding dans la doc de node. J'ai testé le code ci-dessus (dont le diff est ci-dessous) sur un png de 46Ko. L'image reçu est identique à l'image envoyée :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
~/playground/js/chunks
❯ md5sum *.png
e92bbc6b25d71b867db295bbb8fd80dd  lala.png
e92bbc6b25d71b867db295bbb8fd80dd  lele.png

❯ diff server*
- var stream = fs.createWriteStream("files/fichier_recu.txt");
+ var stream = fs.createWriteStream("files/fichier_recu.txt", {encoding: 'hex'});

❯ diff client*
-     fs.readFile(filepath, function (err, data) {
+     fs.readFile(filepath, {encoding: 'hex'}, function (err, data) {
+0 -0

Salut, merci maintenant ça marche parfaitement, en fait il fallait juste ne pas mettre d'encodage… Cependant j'ai maintenant un autre petit problème: les fichiers reçus, bien qu'identique au fichier envoyé par le client ne peut pas s'ouvrir sur le pc si c'est une image ou encore une vidéo (dans un navigateur par contre aucun problème) car en fait la taille sur le disque du fichier envoyé diffère avec la taille sur le disque du nouveau fichier. C'est pas très grave car j'utilise exclusivement mon outil pour du partage de vidéo en streaming sur un navigateur mais c'est juste par curiosité car je ne sais pas à quoi correspond la taille sur le disque d'un fichier et pourquoi il diffère de sa taille lambda.

image4

+1 -0

Tiens je repensais à cette histoire et j'ai qu'une hypothèse, mais plusieurs moyen de la vérifier.

Le système de fichier ne peut pas stocker 45Mo en un seul bloc, le fichier est réparti sur un tas d'unités d'allocation. Si ton serveur disait à ton OS la taille du fichier à écrire avant de créer le fichier, l'OS serait probablement assez malin pour allouer un espace disque contigu et tes deux fichiers occuperaient probablement le même nombre de blocs.

Là, c'est pas le cas. Ton serveur créé un fichier, puis y ajoute petit à petit des trucs. Si l'OS créé d'autres fichiers durant le transfert, ils seront intercalés sur le disque, donc ton fichier reçu sera fragmenté. Plus tu fragmentes, plus ça prend de la place.

Tu peux faire l'expérience suivante : fais tourner ton code plusieurs fois avec le même fichier et observe la "taille sur disque" de chacun des fichiers reçu. Il y a bien des chances qu'il varie un peu lors de chaque réception. Autre expérience, essaie quelques fois avec des gros chunks (genre 2Mo), quelques fois avec des petits chunks (4Ko, unité d'allocation NTFS), et quelques fois avec des chunks plus petits que 4Ko, genre 1Ko.

Si t'as la pèche, tu peux automatiser ça et benchmarker la taille des résultats sur un truc du genre 3-4 différentes chunk size pour 1000 transferts chacune.

+1 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte