Сайт Алексея Озерицкого

Искусственный интеллект, суперкомпьютерные системы, параллельные вычисления, численные методы, алгоритмы

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

  1. Настройка сервера
  2. Настройка клиента
  3. Формирование потока на клиенте
  4. Разбор потока на сервере
  5. Тесты

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

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.

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