Передача файлов по сети
Содержание
Наверняка многие сталкивались с задачей пересылки файлов между машинами. В общем случае она решается так:
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«подхватывает» стандартный ввод и отправляет его на указанный 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 (в секундах):
Со сжатием получилось даже немного медленнее, но при использовании стамегабитной сети оно будет эффективно.