转自:
http://groups.google.com/group/opener-com-work-team/browse_thread/thread/d2ce1155f9721dfd
PERL模拟飞鸽
传书文件传输总结
经过半个月的学习实践,对于网络SOCKET连接、文件传输的实现原理与具体实现的重点难点已经有了一定的了解。
文件传输需要建立一个文件发送端,一个文件接收端,并通过自己指定的传输协议,对文件数据进行传输。在文件传输过程中,我们由于许多原因,要对数据进行
处理,这样,就需要文件发送与接收双方进行通信,对传输文件的一些特性进行互相通告,以方便对传输数据的处理。文件传输的要求在于文件数据的传输速
度,CPU的占用率,以及网络资源的使用率。下面对这三方面进行分析:
1. 文件数据的传输速率
纯正意义上的文件传输速度是指文件的实际内容开始发送到文件内容全部传输完毕所用的时间来除文件的大小得出的值。这个值由发送数据包的大小决定,这个大
小值要保证一次传输数据要尽可能的多的同时发送数据包的速度也要快,也就是说网络占用率与传输速度相互关联。但在程序实际传输文件中,发送方读文件内容
到内存、接收方从内存向文件写数据、发送文件前的通信以及传输数据的压缩与解压缩都会消耗时间,所以我们算速率的时候通常把这些时间也算进去了,因为要
评估程序的性能,就要把实际消耗的时间都算上。那么影响传输速率的其他因素还有:
发送端读文件时间;
接收端写文件的时间;
数据的压缩、解压缩时间;
传输通讯时间。
我们需要解决的是,一次发送接收多少数据,如何发送,数据一次压缩或解压缩多少,还有就是通信的方式,如何发送接收发送前的通信,才能使其耗时最短。
2. CPU的占用率
CPU的占用率就是指程序运行后,文件传输时CPU的使用情况。CPU主要工作在文件数据的读写,文件数据的压缩与解压缩。文件数据连续的进行写入内
存,或者从内存写入文件都会占用很多的CPU使用,压缩解压缩数据时,对于很大的数据进行压缩或解压缩也会占用很大的CPU使用。
想要降低CPU占用率,就要使文件发送与接收有层次,将大的数据分开成若干的较大的数据包,然后再将这些数据包分别按小包发送。如何控制分割的大数据包
的大小,和每次发送数据包的大小,与接收端接收了多少数据再写入文件都是关键,;如果要对传输的数据进行压缩,何时进行压缩和解压缩,一次压缩和解压多
少数据,压缩次数是关键,由于逻辑结构的因素,何时进行压缩就制约了数据压缩大小,在文件分割前压缩,压缩的数据就会很大,如果把文件分割成大的数据包
后进行压缩,这个数据包的大小就决定压缩的效率,如果在每次SOCKET发送数据包的时候进行压缩,就会使压缩的次数巨增,也制约了压缩效率。
3. 网络资源的使用率
网络资源的利用率是指在文件传输过程中,对网络资源的利用情况,SOCKET在传输数据包的时候有一个最大传输字节,可以用
SND_BUF、RCV_BUF分别取出发送端与接收端的最大值。每次发送数据包时候最大利用了SND_BUF、RCV_BUF的大小,就可以使传输效
率大大提高,网络利用率也就很高。网络资源的使用率也影响文件数据的传输速率。为了保证每次发包与收包的数据大小的一致性,我们就要保证
SND_BUF、RCV_BUF的一致,所以接收端的RCV_BUF要与发送端的SND_BUF相等,这就要求我们在发送文件之前的通信中要把发送端的
SND_BUF告诉接收端,接收端对自己的RCV_BUF进行处理。
文件传输过程中还有一些问题:
1.文件传输结束标志,文件什么时候传输完毕,我们退出SOCKET,不再进行数据的收发,我们可以在传输前通信前的通信中把文件大小告诉接收方,当接
收方写入文件数据到了该大小后传输结束。
2.文件传输过程中出现发送方与接收方单方中断传输时候,要通知对方结束程序,并对没有写完的文件进行删除,着块要根据传输方式跟逻辑结构的不同采取不
同的方法实现。
3.文件内容的压缩与不压缩一定程度上影响了文件传输的逻辑结构,采取不同的压缩方式,在不同的阶段进行压缩,文件传输的逻辑结构应该不同,这样才能保
证传输数据的正确性和传输的高效性。
4.在文件传输过程中是否会有丢包的现象呢?采取TCP方式,由于它是可靠的连接,三次握手机制保证了传输数据的正确性。而采取UDP方式就不行了。另
外接收端在采用sysread从SOCKET中读数据时候,如果是外网文件传输,sysread会出现读到的数据小于我们指定的长度,这样就要采取逻辑
上的补救,限制读够了该长度后才读下一数据。
5.文件接收端在往缓存变量写数据时候,如果一次写入的数据比较大,那么,第一次写数据时候将消耗大量的时间,这里要采用先把缓存扩大到数据长度的空间
大小,然后清空缓存变量,再进行数据的写入。
6.数据压缩后发送给接收端,接收端如何知道何时解压缩得到的数据是原始正确的数据呢,压缩后的数据分开后或者取出部分后解压缩后得到的值肯定不等于原
始数据,接收端依次读取SOCKET数据包,累加写入变量,当变量的值等于发送端发送的压缩数据时,对其解压缩得到的值才是原始数据。判断这个变量等于
压缩的数据就要用到压缩数据的大小,发送端压缩后先与接收端进行通信,将压缩数据大小告诉接收端,接收端反馈一个收到的信息后,发送端开始发送压缩数
据,接收端根据收到的压缩数据的大小来判断接收数据是否到达了压缩数据包的大小,如果大小一样了,就说明压缩包全部接收到了,现在就可以进行解压缩处理
了。
总结,一个基本的文件传输结构已经出来了,再加上简单的处理,比如,对发送文件的选择,接收文件的重命名,传输方的IP选择等等,文件传输程序就基本实
现了,程序还有很多不太合理的地方,CPU,网络,跟传输速率还有提升的空间,希望大家批评指正。
附:程序雏形的代码:
接收端代码:
use strict;
use IO::Socket;
use IO::Select;
use Socket;
use Compress::Zlib ;
my $port=2008;
my ($input,$no,$temp,$num,$n,$head,$input1,$input2,$i,$u,$z,$length,
$head,$r);
$z=0;
$num=10000;
#my $length=1400;
$input1='a'x90000000;
$input1='';
$temp='a'x90000000;
$temp='';
my $file="34567.exe";
open(FILE,">$file");
binmode FILE;
my $sock = IO::Socket::INET->new( Listen => 20,
LocalPort => $port,
Proto =>'TCP',
Reuse => 1)
or die "Can't create listening socket: $!\n";
print "recv sever is connect now!\n"; #创建一个服务器进行监听。
$no=1;
#确定保存文件全路径名并打开文件
my $session = $sock->accept();
my $select=IO::Select->new($session);
my $rr;
print "文件接收中...\n";
###########接收文件前的通信:依次按读出的长度读出 文件名 文件长度 SNDBUFF ####################
my ($l0,$l1,$l2,$l3,$cmd,$filename,$u,$buf,$info);
while(1){
if ($select->can_read()) {
if($session->recv($info,10000,0))
{
$l0=substr($info,0,1);
$l0=unpack("c",$l0);
#print $l0."\n";
$cmd=substr($info,1,$l0);
$l1=substr($info,$l0+1,1);
$l1=unpack("c",$l1);
#print $l1."\n";
$filename=substr($info,$l0+2,$l1);
#print $filename."\n";
$l2=substr($info,$l0+$l1+2,1);
$l2=unpack("c",$l2);
#print $l2."\n";
$u=substr($info,$l0+$l1+3,$l2);
#print $u."\n";
$l3=substr($info,$l0+$l1+$l2+3,1);
$l3=unpack("c",$l3);
#print $l3."\n";
$buf=substr($info,$l0+$l1+$l2+4,$l3);
#print $buf."\n";
$buf=pack("I", $buf);
setsockopt($sock, SOL_SOCKET, SO_RCVBUF, $buf);
$length=getsockopt($sock,SOL_SOCKET,SO_RCVBUF);
#print $length,"\n";
$length=unpack("I", $length);
$session->send("BEGIN");last;
}
}
##################### ##################### #####################
#################### #####################
if ($select->can_read()) {
unless($filename=~/\.exe$/i or $filename=~/\.zip$/i or
$filename=~/\.rar$/i )
{
while(1) {
if( $session->recv($head,$length,0))
{ #print "$head\n";
$head=hex(substr(unpack("H*",$head),0,8));
#print "$head\n";
$session->send("START");last;}
}
}
RE: $r=sysread $session,$input,$length;
#print "$n\n";
$input1.=$input;
$i=length $input1;
#print $i." ".$head."\n";
unless($filename=~/\.exe$/i or $filename=~/\.zip$/i or
$filename=~/\.rar$/i )
{unless($i eq $head){goto RE;}}
unless($filename=~/\.exe$/i or $filename=~/\.zip$/
i or $filename=~/\.rar$/i )
{$input1=uncompress($input1);}
$z+=$i;
#print $z."\n";
$temp.=$input1;
unless($filename=~/\.exe$/i or $filename=~/\.zip$/i or
$filename=~/\.rar$/i )
{ }
else
{ if($no%$num==0)
{
print FILE $temp;
$temp="";
}
}
if ($z eq $u) {
if($filename=~/\.exe$/i or $filename=~/\.zip$/i or $filename=~/
\.rar$/i )
{print FILE $temp; }
close(FILE);
shutdown $session,2;
print "接收完毕\n";
exit(0);
}
$no++;
$input="";$input1="";
unless($filename=~/\.exe$/i or $filename=~/\.zip$/i or
$filename=~/\.rar$/i )
{$temp="";}
##################### ##################### #####################
##################### #####################
}
use strict;
use IO::Socket;
use IO::Select;
use Socket;
use Compress::Zlib ;
my $ss; my $mm; my $hh;
my $n;my $t;my $w;
my ($host,$port,$line,$f,$sock,$head,$headpack,$sum1);
print "####飞鸽传书模拟版,文件发送端###\n";
$host="192.168.1.100";#124.128.127.15 192.168.1.101
$port=2008;
#确定主机地址
my $path="123.exe";
#确定传送文件
&start_sock;
sub start_sock
{$sock=new IO::Socket::INET(PeerAddr=>$host,
PeerPort=>$port,
Proto =>'TCP'
) or die "connect err";
my $select=IO::Select->new($sock);
my $l =getsockopt($sock,SOL_SOCKET,SO_SNDBUF);
$l=unpack("I", $l);
my $length=$l*1000;#可以改变大小(每一大块数据的大小)
print "sock creat ok!\n";
my $time= scalar(localtime);
print "开始发送时间:".$time."\n";
$time=substr($time,11,8);
my @time=split/:/,$time;
my $h=$time[0];
my $m=$time[1];
my $s=$time[2]; #取时间
print "开始发送...\n";
my $session;
my $input;
my $f_content;
open(FILE,$path) or die "open file err";
my $no=1;
my $sum;
binmode FILE;
my $size = (stat($path))[7];
#print $size."\n";
if ($size%$length==0) {
$sum=$size/$length;
}
else {
$sum=int($size/$length+1);
} # 取文件大小,确定发送数据包的个数
###########接收文件前的通信:依次发送: 命令长度, 命令行(以后扩展用), 文件名长度, 文件名, 文件大小长度, 文件大小,
SENDBUFFER长度, SENDBUFFER.####################
my $cmd="0000";#定义命令 以后扩展
my $length0=length $cmd;#命令长度 以后扩展
$length0=pack("c",$length0);
my $length1=length $path;
$length1=pack("c",$length1);
my $length2=length $size;
$length2=pack("c",$length2);
my $length3=length $l;
my $length3=pack("c",$length3);
print $sock $length0.$cmd.$length1.$path.$length2.$size.$length3.$l;
AA:if ($select->can_read()) {
sysread $sock,$input,5;
if($input eq "BEGIN"){goto CC;}
else{ goto AA;}
}
else{ goto AA;}
#####################################组织发送数据:发送数据包的格式: 压缩数据大小
###################################################
########压缩数据方式:把原始数据分成很多较大的数据包,每一个大的数据包进行压缩
CC: foreach (1..$sum){
sysread FILE,$f_content,$length;
$w=0;
unless($path=~/\.exe$/i or $path=~/\.rar$/i or $path=~/\.zip$/i)
{$f_content=compress($f_content);
$head=length $f_content;
#print "$head\n";
$headpack=pack("N2",$head);
print $sock $headpack;
BB:if ($select->can_read()) {
sysread $sock,$input,5;
if($input eq "START"){goto DD;}
else{ goto BB;}
}
else{ goto BB;}
DD: if ($head%$l==0) {
$sum1=$head/$l;
}
else {
$sum1=int($head/$l+1);
}
}
else{$sum1=($length/$l);}
foreach(1..$sum1){
my $no1= pack("N2",$no);
if(!(substr($f_content,(0+$w*$l),$l))) {last;}
print $sock substr($f_content,(0+$w*$l),$l);#发送数据
#print "send : ".substr($f_content,0+$w*$l,1).substr($f_content,0+
$w*$l+1300,1)."\n";
$w++;
$no++;
}
#print "发送完毕\n";
}
##################################################################################################
#print "传送完毕\n";
my $time1= scalar(localtime);
print "发送完毕时间:".$time1."\n";
$time1=substr($time1,11,8);
my @time1=split/:/,$time1;
my $h1=$time1[0];
my $m1=$time1[1];
my $s1=$time1[2];
#取发送完毕的时间
if($s1 >= $s)
{$ss=$s1-$s;}
else
{$s1+=60;$ss=$s1-$s;$m1--;}
if($m1 >= $m)
{$mm=$m1-$m;}
else
{$m1+=60;$mm=$m1-$m;$h1--;}
if($h1 >= $h)
{$hh=$h1-$h;}
else
{$h1+=24;$hh=$h1-$h;}
print "发送消耗时间:$hh小时$mm分$ss秒\n";
#计算消耗时间
my $ts=$mm*60+$ss;
eval{print "平均".($size/$ts)/(1024*1024)."M/s\n";};
shutdown $sock,2;