Socket实现多线程文件传输控件的一个怪问题 ( 积分: 200 )

  • 主题发起人 主题发起人 zhao_haili
  • 开始时间 开始时间
Z

zhao_haili

Unregistered / Unconfirmed
GUEST, unregistred user!
前阵子用Socket做了一个多线程文件传输的控件,实现同时响应多个用户多个文件的同时下载(一个文件只用一个线程),存在问题描述:
在XP系统之间,以及2K系统向XP系统传输文件时,控件能正常工作;但在XP向2K系统传输文件时就是出现多传的问题(即传一个5000KB的文件,传输结果会是5290KB,两个数据没有直接联系),情况严重的会导致文件不能用。
我的SendBuffer=1024; RecvBuffer=8192;(个人理解Socket的最大缓存为8K)
如果SendBuffer=32; RecvBuffer=8192;传输50M以内的文件没有问题,大点的文件就会出现“多传”。附发送与接收线程代码:

发送线程代码:
procedure SendTHread.Execute;
var
canDo :TFDSet;
TimeDelay :TimeVal;
Len :Integer;
Sock :TSocket;
Addr :TSockAddrin;
Buffer :Pointer;
Counter :TTimer;
begin
SetFDValue(PSock,canDo);
TimeDelay := GetTimeValue(20,0);
if (select(0, @canDo,nil,nil,@TimeDelay)>0) and (not Terminated) then
begin
Len := sizeOf(Addr);
Sock := accept(PSock,@Addr,@Len);
if Sock<>SOCKET_ERROR then
begin
PFile := TFileStream.Create(PFilename,fmOpenRead or fmShareCompat or fmShareDenyNone);
PFile.Position := 0;
SetDelay(Sock);
SetFDValue(Sock,canDo);
Synchronize(onBegin);
Getmem(Buffer,SendByte);
Counter := TTimer.Create(nil);
Counter.OnTimer := onTime;
try
PTime := Now;
while PFile.Position < PFile.Size do
begin
if (Terminated) or (select(0,nil,@canDo,nil,@TimeDelay)<=0) then
raise Exception.Create('')
else
if FD_ISSET(Sock,canDo) then
begin
Len := PFile.Size-PFile.Position;
if Len > SendByte then
Len := SendByte;
PFile.ReadBuffer(Buffer^,Len);
if send(Sock,Buffer^,Len,0)=-1 then
raise Exception.Create('');
Application.ProcessMessages;
end;
end;
Synchronize(onProgress);
Synchronize(onComplete);
except
Synchronize(onError);
end;
Counter.Free;
Freemem(Buffer);
CloseSocket(Sock);
PFile.Free;
end;
end;
CloseSocket(PSock);
end;

接收线程代码:
procedure RecvTHread.Execute;
var
Len :Integer;
canRead :TFDSet;
Sock :TSocket;
TimeDelay :TimeVal;
Buffer :Pointer;
Counter :TTimer;
begin
if not Terminated then
begin
Sock := CreateClient(PTaskData^.FileData.FileSendIP, PPort);
if Sock <> SOCKET_ERROR then
begin
PFile := TFilestream.Create(PFilename,fmCreate);
SetDelay(Sock);
SetFDValue(Sock,canRead);
TimeDelay := GetTimeValue(20,0);
Synchronize(onBegin);
Getmem(Buffer,RecvByte);
Counter := TTimer.Create(nil);
Counter.OnTimer := onTime;
try
PTime := Now;
repeat
if (Terminated) or (select(0,@canRead,nil,nil,@TimeDelay)<=0) then
raise Exception.Create('')
else
if FD_ISSET(Sock,canRead) then
begin
ioctlsocket(Sock, FIONREAD, Len);
if Recv(Sock,Buffer^,Len,0)=0 then
raise Exception.Create('');
PFile.WriteBuffer(Buffer^,Len);
Application.ProcessMessages;
end;
until PFile.Size = PTaskData.FileData.FileSize;
Synchronize(onProgress);
Synchronize(onComplete);
except
Synchronize(onError);
end;
Counter.Free;
Freemem(Buffer);
PFile.Free;
CloseSocket(Sock);
end;
end;
end;
请各位大侠进来讨论,谢谢!
 
补充一下:如果SendBuffer=1024; RecvBuffer=8192;XP向2K系统中传输文件,不论大小,一概出错;把SendBuffer改成32;50M以内的可以正常传输。
 
if Len > SendByte then
Len := SendByte;
send(Sock,Buffer^,Len,0)=-1
上面这几句想问一下! sendByte是从那里来的
为什么! SendByte := send(Sock,Buffer^,Len,0)不是已经发送的字节数量吗?
然后 PFile.Position := PFile.Position + SendByte;
这个不就得到已经发送的了字节位置吗? 这样做应该就不会发生多发少发字节了
把发送的修改成这样看看;
while PFile.Position < PFile.Size do
begin
if (Terminated) or (select(0,nil,@canDo,nil,@TimeDelay)<=0) then
raise Exception.Create('')
else
if FD_ISSET(Sock,canDo) then
begin
len := PFile.Read(Buffer^,1024);
SendByte := send(Sock,Buffer^,Len,0);
if SendByte = -1 then raise Exception.Create('');
PFile.Position := PFile.Position + SendByte;
Application.ProcessMessages;
end;
end;
 
To kk2000:
1、SendBuffer与RecvBuffer我写错了,是
Const SendByte=1024;
RecvByte=8192;
2、执行PFile.ReadBuffer(Buffer^,Len);后,PFile.Position便自行跳到下一个Len位置了,PFile.Position := PFile.Position + SendByte;这一句是不用加的。
3、这个控件我在XP系统中测试过,传输文件是正常的。
 
呵呵! 估计你没有理解套节发送的时候! 它并不一定发送完你的Len的字节,而是通过
SendByte := send(Sock,Buffer^,Len,0); 这个返回发送实际的的字节数量!
而不是你想让他发送多少就多少啊
 
To kk2000:
1、在什么情况会出现不能完全发送Len长度的数据?比如,会不会在对方接收缓存满了时不能完全发送?
2、如果发送的速度比接收的速度快,即所发送的数据已经占满了接收方的缓存了,这时如果继续发送会出现什么情况?(家里电脑没有装delphi,所以先弄懂理论,现在不能试)
 
呵呵:套接字分为发送缓冲和接收缓冲,可能出现不能发送完的原因是你的套接发送缓冲满了!
 
To kk2000:
1、发送缓冲什么情况会满?
2、如果发送缓冲满了,我当前的发送线程代码,就不能完全把数据发送过去,但接收线程为什么会出现接收我数据比发送的数据还多呢?
3、有发送和接收缓冲的存在,那还会不会出现“发送到接收缓冲的数据因为发送过快,而在接收缓存中造成数据丢失?”
呵呵,我对这个套接字一知半解的。
 
看来你的问题还挺多的啊!推荐你看看<<windows网络编程>>这本书八第二版的里面讲得比较清楚
 
请看我的接收过程

procedure TFileSvr.DoUpFileData(ClientThread: TDXClientThread);
var
i: Integer;
lUserbuff: TUserSubTread;
Lbuff: RUpFileData;
Lidx: Integer;
LOpter: TFileItem;
LRecSize, LCurrRec, LTrueSize: Integer;
LRecBuff: Pointer;
begin
LRecBuff := nil;
lUserbuff := TUserSubTread.Create;
lUserbuff.Conn := ClientThread;
ClientThread.fpSessionData := lUserbuff;
ClientThread.Socket.ReceiveBuf(Lbuff, Sizeof(Lbuff));
{用户线程开始上传}
lUserbuff.WriteRd := Lbuff;
Lidx := FileUoDMM.GetItms((lbuff.FileID));
if Lidx < 0 then begin
Dec(tuser(ClientThread.fpSessionData).SubThreadCount);
Shower.AddShow('严重错误,未发现此文件任务 文件ID%D', [lbuff.FileID]);
Exit;
end
else begin
{加入到FileItem的线程列表内}
TFileItem(FileUoDMM.PeekItem(Lidx)).FileOptList.AddObject(Format('%d_%d', [Lbuff.StartPos, Lbuff.EndPos]), lUserbuff);
end;
LOpter := TFileItem(FileUoDMM.PeekItem(Lidx));
LOpter.CheckTime := GetTickCount;
Shower.AddShow('开始发送数据 文件ID:%D 起始:%d 到:%d', [Lbuff.FileID, Lbuff.StartPos, Lbuff.EndPos]);
if InRange(Lbuff.StartPos, 0, LOpter.fileSize) = False then begin
Shower.AddShow('上传的标示不在范围内', []);
{如果上传的标示不在范围内}
Dec(Tuser(ClientThread.fpSessionData).SubThreadCount);
ClientThread.fpSessionData := nil;
ClientThread.Socket.Disconnect;
end;
try
try
lUserbuff.ConnToFile(LOpter.FileName);
LRecBuff := GetMemory(1024 * 8);
LRecSize := Lbuff.EndPos - Lbuff.StartPos; {总共要读取的大小}
LCurrRec := 0; {已经读取的大小}
LTrueSize := Min(1024 * 8, LRecSize - LCurrRec);
SetBuff(lUserbuff.PCurrRecvBUff, CMissonRecBuff + 1024 * 8);
while (LCurrRec < LRecSize) and (ClientThread.Socket.Connected) do begin
LTrueSize := ClientThread.Socket.ReceiveBuf(LRecBuff^, LTrueSize);
lUserbuff.PCurrRecvBUff^.Buff.WriteBuffer(LRecBuff^, LTrueSize);
inc(LCurrRec, LTrueSize);
inc(lUserbuff.CurrRecv, LTrueSize); {为了能让服务端检测到现在已经接收到的数据}
LTrueSize := Min(1024 * 8, LRecSize - LCurrRec);
{接满一个缓存后 使用下一个 如果下一个还在写 则等待写完}
while lUserbuff.PCurrRecvBUff^.Buff.Position >= CMissonRecBuff do begin
{把写满的缓存标记为可写入 让写入线程来写到文件内}
lUserbuff.PCurrRecvBUff^.UseSize := lUserbuff.PCurrRecvBUff^.Buff.Position;
lUserbuff.PCurrRecvBUff^.IsCanWrite := 2;
{如果写入指针为空 则复值给它 }
if lUserbuff.PCurrWriteBuff = nil then
lUserbuff.PCurrWriteBuff := lUserbuff.PCurrRecvBUff;
{判断是否有可写的buff 有则继续接收 否则就检查等待}
for i := low(lUserbuff.RecBuff) to High(lUserbuff.RecBuff) do begin // Iterate
if lUserbuff.RecBuff.IsCanWrite = 1 then begin
lUserbuff.PCurrRecvBUff := @lUserbuff.RecBuff;
Break;
end;
end; //for
{如果没有设置过缓冲则设置}
if lUserbuff.PCurrRecvBUff^.Buff.Size <> CMissonRecBuff + 1024 * 8 then
SetBuff(lUserbuff.PCurrRecvBUff, CMissonRecBuff + 1024 * 8);
{如果磁盘还在写入则等待}
if lUserbuff.PCurrRecvBUff^.Buff.Position >= CMissonRecBuff then
sleep(10);
end;
end; // while
Shower.AddShow('LCurrRec : %d LRecSize:%d ', [LCurrRec, LRecSize, BoolToStr(ClientThread.Socket.Connected)]);
if LCurrRec = LRecSize then begin
{正常结束的时候 要把正在接收的BUFF标记为可写入磁盘}
lUserbuff.PCurrRecvBUff^.UseSize := lUserbuff.PCurrRecvBUff^.Buff.Position;
lUserbuff.PCurrRecvBUff^.IsCanWrite := 2;
LOpter.FinishAnThread(Lbuff);
end
else
LOpter.State := Excepts;
except
on e: Exception do
Shower.AddShow('接收数据时异常原因 :%s 将结束此用户上传的所有相关线程', [e.Message]);
end;
finally
Dec(Tuser(ClientThread.fpSessionData).SubThreadCount);
ClientThread.fpSessionData := nil;
FreeMem(LRecBuff);
Shower.AddShow('线程接收数据完毕 文件ID%D 起始:%d 到:%d ', [Lbuff.FileID, Lbuff.StartPos, Lbuff.EndPos]);
lUserbuff.IsFinish := True;
end;
end;
-------------------------------------------------------------------------------
请注意
LTrueSize := ClientThread.Socket.ReceiveBuf(LRecBuff^, LTrueSize);
lUserbuff.PCurrRecvBUff^.Buff.WriteBuffer(LRecBuff^, LTrueSize);
inc(LCurrRec, LTrueSize);
inc(lUserbuff.CurrRecv, LTrueSize); {为了能让服务端检测到现在已经接收到的数据}
LTrueSize := Min(1024 * 8, LRecSize - LCurrRec);

接收到的数据并不会每次都如预期的那么多,所以需要这样写。发送时也一样。你的这个问题我写文件传输模块的时候也遇到了。
 
To mmzmagic:
你的程序写的严谨、漂亮!呵呵。我反复看了下你的程序和我的程序,感觉我的程序问题应该在发送线程,因为读取的文件数据并不一定能全部发送出去,这样接收到的数据量就不会等于文件实际大小,
repeat
if (Terminated) or (select(0,@canRead,nil,nil,@TimeDelay)<=0) then
raise Exception.Create('')
else
if FD_ISSET(Sock,canRead) then
begin
ioctlsocket(Sock, FIONREAD, Len);
if Recv(Sock,Buffer^,Len,0)=0 then
raise Exception.Create('');
PFile.WriteBuffer(Buffer^,Len);
Application.ProcessMessages;
end;
until PFile.Size = PTaskData.FileData.FileSize;
Until条件就不能满足,因为套接原理我还不太了解,所以,为什么最终得到的文件会比源文件要大,这一点还请指点......
 
<<windows网络编程>>第二版,谁有下载地址?
 
我操,这个世界没有天理了~~~~~ MD,老虎不发威,当我是病猫, 先骂人的倒有理了 我可是从来不主动得罪人的 谁要是欺负人,那就不客气了 明天把这个工具开个源,叫大家都来用用 请问你们有什么拿的出手的?自己写的东西? 说出来叫大爷我长长见识? 别不会是一群耍嘴皮子的废物吧? 需要的请关注我的 blog http://hi.baidu.com/earthsearch
 

Similar threads

I
回复
0
查看
552
import
I
S
回复
0
查看
3K
SUNSTONE的Delphi笔记
S
S
回复
0
查看
2K
SUNSTONE的Delphi笔记
S
后退
顶部