[轉] PERL模擬飛鴿傳書檔案傳輸總結

bulletming發表於2019-04-01
我也在用perl的IO::Socket去傳輸XML檔案,但是我並沒有深入研究,記下來有空看看下邊的文章,另外前一段時間學習SOAP,感覺對於資料交換,感覺也是挺好的。

轉自:


   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;
                                }

}
}

while (1){
        #####################   #####################   #####################
####################    #####################
                    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";};

        close(FILE);
        shutdown $sock,2;

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/82392/viewspace-442694/,如需轉載,請註明出處,否則將追究法律責任。

相關文章