Tuesday, 24 April 2018

socket 传输文件编程 传送大文件的算法

http://www.greensoftcode.net/techntxt/201141310049498110245

我采用的开发语言是delphi 当然采用其它开发语言大同小异。
在写代码之前先说下网络传送协议:常用的网络文件传输协议为:TCP 和UDP,两者的区别是TCP是面向连接的 UDP是非面向连接的。TCP所谓的面向连接的及三次握手协议,报文头加入验证字段等很复杂
UDP相对TCP报头帧相对简单无验证帧。

一.udp 传送文件的特点:
 1.速度快
 2.传送质量差可能丢包。
 3.1m小文件最佳
二、tcp 传送文件的特点:
  1.传送文件速度相对UDP较慢。
  2.传送质量较高丢包情况较少
  3.大文件传送佳。
三、udp传送代码:
采用delphi IdUDPClient、IDUDPSERVER的的控件
//发送端
function udpsenchangefile(filename:string ;ip:string;port:integer; bar:TProgressBar;mainfrm:TForm;iconarr:array of TIcon;label1:TLabel;label2:TLabel):boolean; //发送文件
var
receivedString:string;
stream:TFileStream;
posi,len:integer;
p:array[0..1023]  of byte;
sendresult:boolean;  //发送结果
sendedsize :integer;   //已发送字节数
udpclient:TIdUDPClient ;
begin
sendedsize:=0;
sendresult:=false;
udpclient:= TIdUDPClient.Create(Application);
udpclient.Host:=ip;
udpclient.Port:=port;
if  not udpclient.Active then udpclient.Active:=true;
posi:=0;
stream:=nil;
try
stream:=tfileStream.Create(filename,fmOpenRead);
if stream.Size>0 then
udpclient.Send('token'+'|'+filename+'|'+IntToStr(stream.Size));
receivedString:=udpclient.ReceiveString();
if uppercase(receivedString)=upperCase('agree-accept')then
begin
udpclient.Active:=false;
udpclient.Free ;
while posi<stream.Size do
begin
udpclient:= TIdUDPClient.Create(Application); //每发送一个文件建立一个对象 因为 我用一个控件时发送大于1M文件就程序就死掉啦!
udpclient.Host:=ip;
udpclient.Port:=port;
udpclient.ReceiveTimeout:=-2;
udpclient.BufferSize :=1024000;
udpclient.BroadcastEnabled:=false;
if  not udpclient.Active then udpclient.Active:=true;
len:= sendbytesize;                 //只能发  sendbytesize
if stream.Size< len then          //如果长度不到 sendbytesize
len:=stream.Size;
stream.Read(p,len);
udpclient.SendBuffer(p,len);
sendedsize:=sendedsize+1;
inc(posi,len);
label1.Caption:=inttostr(stream.Size);
bar.Position:=round(posi/stream.size*100);
//softtitle:=softtitle+inttostr(sendedsize)+'字节';
receivedString:=udpclient.ReceiveString();
//if ( sendedsize>0 )and (sendedsize mod 1000 =0) then Delay(5000);
label2.Caption:=inttostr( sendedsize);
if upperCase(ReceivedString)<>upperCase('receivedok') then    break;
application.ProcessMessages;
 changeico(mainfrm,softtitle, iconarr) ;
udpclient.Active:=false;
udpclient.Free ;
end;
udpclient.Send('tokenfilal');
if udpclient.ReceiveString()='receivefilal' then
sendresult:=true;
end
else
sendresult:=false;
finally
stream.Free;
end;
Result:=sendresult;
end;
接受端:
procedure Tmainfrm.udpserverUDPRead(Sender: TObject; AData: TStream;
  ABinding: TIdSocketHandle);
var
str ,receiveval:string;
temp:TStringList;
begin
aData.Seek(0,0);
setLength(receiveval,aData.size);
aData.Read(receiveval[1],aData.Size);
temp:= SplitString(receiveval,'|');
case protocol.IndexOf(temp[0])  of
 0: begin
    receiveFileName:=temp[1];
    receiveFileSize:=strtoint(temp[2]);
    filename:=createreceivedir(receiveFileName,receivedir);
     if not fileExists(filename) then
    stream:=TFileStream.Create(FileName,sysutils.fmOpenReadWrite or fmCreate)
    else
    stream:=TFileStream.Create(FileName,fmopenReadWrite);
    str:='agree-accept';
    abinding.SendTo(aBinding.PeerIP ,aBindIng.PeerPort  ,str[1],length(str));
    end   ;
  1:
   if stream<>nil then
    begin
    successfilesynchcount:=successfilesynchcount+1;
    stream.Free;
    stream:=nil;
    str:='receivefilal';
    aBinding.SendTo(aBinding.PeerIP,aBinding.PeerPort,str[1],length(str));
    filename:='';
    receiveFileName:='';
    receiveFileSize:=0;
    receivsize:=0;
    softtitle:='目前成功接受'+inttostr(successfilesynchcount)+'个文件!';
    end;
 else
   if stream<>nil then
   begin
   receivsize:=receivsize+1;
   softtitle:='正在接受'+ filename  ;
   stream.Seek(0,2);
   aData.Seek(0,0);
   stream.CopyFrom(aData,aData.Size);
   label1.Caption:=inttostr(  receivsize)   ;
   Bar.Position:=round(stream.size/receiveFileSize*100);
   str:='receivedok';
   abinding.SendTo(aBinding.PeerIP,aBinding.PeerPort,str[1],length(str));
   label2.Caption:=inttostr(  receivsize)   ;
   application.ProcessMessages;
   changeico(mainfrm,softtitle,   notifyicon) ;
   end;
end;
end;
通过这个例子udp 发送大文件时我发现确实存在不可靠事件,及文件可能没发送完!
要想准确发送成功!可能还要改进算法
tcp 发送例子
发送端:
function tcpsenchangefile(filename:string ;ip:string;port:integer; bar:TProgressBar;mainfrm:TForm;iconarr:array of TIcon):boolean; //发送文件
var
receivedString:string;
stream:TFileStream;
ReadCount : Integer;
Buf:array[0..1023]  of byte;
sendresult:boolean;  //发送结果
tcpclient:TIdTCPClient ;
begin
sendresult:=false;
tcpclient:=TIdTCPClient.Create(Application);
tcpclient.Host:=ip;
tcpclient.Port:=port;
if  not tcpclient.Connected then tcpclient.Connect(5000);
stream:=nil;
try
stream:=tfileStream.Create(filename,fmOpenRead);
if stream.Size>0 then tcpclient.WriteLn('token'+'|'+filename+'|'+IntToStr(stream.Size));
receivedString:=tcpclient.ReadLn(#13#10, 1000);
if uppercase(receivedString)=upperCase('agree-accept')then
begin
while stream.Position < stream.Size do
begin
if stream.Size - stream.Position >= SizeOf(Buf) then
ReadCount := sizeOf(Buf)
else ReadCount := stream.Size - stream.Position;
stream.ReadBuffer(Buf, ReadCount);
tcpclient.WriteBuffer(Buf, ReadCount);
//receivedString:=tcpclient.ReadLn(#13#10, 1000);
//if upperCase(ReceivedString)<>upperCase('receivedok') then    break;
changeico(mainfrm,softtitle, iconarr) ;
end;
tcpclient.WriteLn('tokenfilal');
receivedString:=tcpclient.ReadLn(#13#10, 1000);
if receivedString='receivefilal' then  sendresult:=true;
tcpclient.Disconnect;
end;
except
sendresult:=false;
end;
tcpclient.Disconnect;
if stream<>nil then FreeAndNil(stream);
if tcpclient<>nil then FreeAndNil(tcpclient);
Result:=sendresult;
end;

接受端
 procedure Tmainfrm.tcpserverExecute(AThread: TIdPeerThread);
 var
str ,receiveval:string;
temp:TStringList;
Buff : array[0..1023] of Byte; // Buff 缓存区大小设置,byte型
ReadCount : Integer; //实际每次读取文件块的大小,整型
begin
if not AThread.Terminated and AThread.Connection.Connected then
begin
if State= dstNone then
  receiveval := AThread.Connection.ReadLn(#13#10, 1000);
   if receiveval='' then Exit;
   temp:= SplitString(receiveval,'|');
   if protocol.IndexOf(temp[0])=0 then
    begin
    receiveFileName:=temp[1];
    receiveFileSize:=strtoint(temp[2]);
    filename:=createreceivedir(receiveFileName,receivedir);
    if not fileExists(filename) then
    stream:=TFileStream.Create(FileName,sysutils.fmOpenReadWrite or fmCreate)
    else
    stream:=TFileStream.Create(FileName,fmopenReadWrite);
    str:='agree-accept';
    AThread.Connection.WriteLn(str);
    State := dstReceiving;
    end
  else
  begin
    successfilesynchcount:=successfilesynchcount+1;
    str:='receivefilal';
    AThread.Connection.WriteLn(str);
    filename:='';
    receiveFileName:='';
    receiveFileSize:=0;
    receivsize:=0;
    softtitle:='目前成功接受'+inttostr(successfilesynchcount)+'个文件!';
  end;
 end;
   if stream<>nil then
   begin
    repeat
    if   receiveFileSize - Stream.Size > SizeOf(Buff) then
    ReadCount := SizeOf(Buff)
    else
    ReadCount := receiveFileSize - Stream.Size;
    AThread.Connection.ReadBuffer(Buff, ReadCount); //从连接中读取 ReadCount长度的文件块放到缓冲区Buff,中
    stream.WriteBuffer(Buff, ReadCount); //将缓冲区中的内容写进文件流中,这是就是写到文件aFileName中啦
    Bar.Position:=round(stream.size/receiveFileSize*100);
    Application.ProcessMessages; //这句作用是让消息传递动态显示起来,如果没有这句上面的caption是不会显示跳动的
    changeico(mainfrm,softtitle,   notifyicon) ;
   // str:='receivedok';
   // AThread.Connection.WriteLn(str);
    until Stream.Size >= receiveFileSize; //直到文件大小和原文件大小一致结束循环
    State := dstNone;
    stream.Free;
    stream:=nil;
   end;
end;
Tcp 发送大小文件测试过程中全部成功!这说明TCP发送文件可靠性比较强!算法要求低。
上面的算法有些变量没有写全 但核心代码没有问题 测试通过!

1 comment:

zomok E-commerce system plan. Choose your online ordering system. No-risk 30 day free trial. Then USD 9/month. No credit card required.

zomok E-commerce system plan. Choose your online ordering system. No-risk 30 day free trial. Then USD 9/month. No credit card required. h...