Наверняка многие сталкивались с задачей пересылки файлов между машинами. В общем случае она решается так:
scp -r . <target_host>:/<target_path>
Но если вы пересылаете много маленьких файлов, эта команда будет работать очень медленно. Конечно, можно заархивировать данные с помощью tar или cpio, а потом переслать архив:
tar cvf archive.tar <directory_with_files> scp archive.tar <target_host>:/<target_path>
Так будет быстрее, однако, на создание архива также необходимо время. Кроме того, бывают ситуации, когда файлы заархивировать невозможно — например, недостаточно места на диске.
В этом случае на помощь приходит Unix-утилита netcat, которая работает по следующему принципу:
Воспользуемся этой возможностью для отправки файлов.
На сервере все просто — запускаем 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.
Поток данных состоит из фрагментов, имеющих следующую структуру:
Фрагмент потока содержит либо название пересылаемого файла, либо его содержимое.
Сценарий 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 (отправляющая сторона)
Машина 2 (принимающая сторона)
На машине с Windows использовались perl, sh, gzip, ssh из Cygwin, netcat-nt (netcat из Cygwin не использовался по причине низкой производительности).
Скорость копирования файлов с помощью scp и netcat:
Со сжатием получилось даже немного медленнее, но при использовании стамегабитной сети оно будет эффективно.