Передача файлов по сети

Наверняка многие сталкивались с задачей пересылки файлов между машинами. В общем случае она решается так:

scp -r . <target_host>:/<target_path>

Но если вы пересылаете много маленьких файлов, эта команда будет работать очень медленно. Конечно, можно заархивировать их с помощью tar или cpio, а потом переслать архив:

tar cvf archive.tar <directory_with_files>
scp archive.tar <target_host>:/<target_path>

Так будет быстрее, однако, на создание архива необходимо время. К тому же, бывают ситуации, когда файлы невозможно поместить в архив — например, недостаточно места на диске.

В этом случае на помощь приходит Unix-утилита netcat, которая работает по следующему принципу:

  1. На принимающей стороне netcat запускается в режиме прослушивания. Все данные, которые она получает на свой порт отправляются в стандартный вывод.
  2. Соответственно, на отправляющей стороне netcat «подхватывает» стандартный ввод и отправляет его на указанный TCP-порт.

Воспользуемся этой возможностью для отправки файлов.

Настройка сервера

С сервером все просто — запускаем netcat в режиме прослушивания и отправляем данные парсеру потока:

netcat -l -p 10000 | gzip -d -c | perl ./recv_file.pl

Обратите внимание, что данные, при необходимости, можно сжать gzip'ом.

Настройка клиента

Клиент отправляет все файлы из текущей директории на машину 192.168.1.100 и порт 10000:

#export ip=127.0.0.1
export ip=192.168.1.100
export port=10000

(find . -type f -print0 ; echo -n "non_existent_file") | xargs -0 -I{} ./send_file.pl {} $ip $port\
    | gzip -c --fast | netcat $ip $port

Команда echo -n "non_existent_file" необходима для того, чтобы определить конец списка файлов и завершить работу netcat.

Формирование потока на клиенте

Поток данных состоит из фрагментов, имеющих следующую структуру:

  • сведения о длине фрагмента (4 байта);
  • содержимое фрагмента.

Фрагмент потока содержит либо имя пересылаемого файла, либо его содержимое.

Сценарий send_file.pl формирует поток по содержимому файла и в конце списка файлов останавливает работу netcat:

#!/usr/bin/perl

sub close_connection {
    my $ip=$ARGV[1];
    my $port=$ARGV[2];
    system("kill `ps ax | grep \"netcat $ip $port\" | grep -v grep | cut -f 1 -d ' '`");
}

my $fname=$ARGV[0];
print STDERR "send '$fname' ";
my $chunk_size=32768;
my $code = open my $pipe, '<', $fname;
if ($code) {

    print pack('l', length($fname));
    print $fname;

    my $data;
    my $bytes;

    my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
               $atime,$mtime,$ctime,$blksize,$blocks)
                      = stat ($fname);
    print pack('l', $size);
    while (($bytes=read $pipe, $data, $chunk_size) != 0) {
        print $data;
    }
    print STDERR "OK\n";
} else {
    close_connection;
    print STDERR "ERR\n";
}
exit 0;

Разбор потока на сервере

На сервере происходит обратная процедура. Вначале принимаем имя файла и создаем для него директорию, если таковая отсутствует. Далее, считываем фрагменты файла, пока не встретим фрагмент нулевой длины:

#!/usr/bin/perl

use Time::HiRes qw(gettimeofday tv_interval);
use File::Basename;
use File::Path;

my $t1    = [gettimeofday];
my $total = 0.0;

while (!eof(STDIN)) {
    my $len_str;
    read STDIN, $len_str, 4;

    my $len=unpack('l', $len_str);

    my $fname;
    read STDIN, $fname, $len;
    print "recv '$fname' ";

    my $dir=dirname($fname);
    mkpath $dir;
    open my $output, '>', $fname;

    read STDIN, $len_str, 4;
    $len=unpack("l", $len_str);
    $total += read STDIN, $chunk, $len;
    print $output $chunk;

    close $output;
    my $t2   = [gettimeofday];
    my $bsec = $total / tv_interval($t1, $t2) / 1024.0 / 1024.0;
    print "OK $bsec Mb/sec \n";
}

Тесты

Тестирование проводилось на массиве в 47 995 файлов общим объемом в 8,8 Гб (копия библиотеки Мошкова). Использовалось следующее оборудование:

Машина 1 (отправляющая сторона)

  • Процессор: Intel E8200.
  • Память: 4 Гб.
  • Винчестер: WD 7500AACS.
  • Сетевой контроллер: Marvel 1Gbit, встроенный.
  • ОС: Mandriva 2008.1 x86_64, kernel 2.6.29.1.

Машина 2 (принимающая сторона)

  • Процессор: Intel E8500.
  • Память: 8 Гб.
  • Винчестер: WD 7500AACS.
  • Сетевой контроллер: RealTek 1Gbit, встроенный.
  • ОС: Windows Vista SP1 x86_64.

На машине с Windows использовались perl, sh, gzip, ssh из Cygwin, netcat-nt (netcat из Cygwin не использовался по причине низкой производительности).

Скорость копирования файлов с помощью scp и netcat (в секундах):

Результаты тестирования скорости копирования файлов с помощью netcat.

Со сжатием получилось даже немного медленнее, но при использовании стамегабитной сети оно будет эффективно.

Оставить комментарий

Для того, чтобы оставить комментарий, нужно заполнить все поля формы.
Ваше имя:
Пожалуйста, введите код, который вы видите на этой картинке: Проверочный код
Проверочный код:
Ваше сообщение: