为什么Socket端会发生自动退出的错误,而文件并没有传送成功?(附源代码)(100分)

  • 主题发起人 主题发起人 solley
  • 开始时间 开始时间
S

solley

Unregistered / Unconfirmed
GUEST, unregistred user!
Server端(在运行这一段时程序退出)
procedure TForm3.ssClientRead(Sender: TObject; Socket: TCustomWinSocket);
var
Df : PDataFlag;
cmd : string;
Buffer : Pointer;
nRetr : Integer;
fs : TFileStream;
Const BufferSize = 1024;
begin
Df := Socket.Data ;
case Df.WorkFlag of
0,3 :
begin
cmd := Trim(Socket.ReceiveText);
if Pos('FILESEND',uppercase(cmd)) > 0 then
begin
Df.FileName := Trim(Copy(cmd,Pos(' ',cmd)+1,Length(cmd)));
Df.FileSize := StrToInt(Copy(Df.FileName,
Pos(' ',Df.FileName)+1,Length(Df.FileName)));
Df.FileName := Trim(Copy(Df.FileName,1,
Pos(' ',Df.FileName)));
Df.WorkFlag := 1;
Socket.Data := Df;
Socket.SendText('文件传送服务准备完毕,可以传送!'#13#10);
RichEdit1.Lines.Add(DateToStr(Date)+' '+
TimeToStr(Time)+'==>>'+'文件名:'+Df.FileName+' 文件大小:'+
IntToStr(Df.FileSize)+#13#10'文件传送服务完毕,可以传送!');
end;
end;
1 :
begin
GetMem(Buffer,BufferSize);
nRetr := Socket.ReceiveBuf(Buffer^,BufferSize);
if not FileExists(Label2.Caption+Df.FileName) then
begin
fs := TFileStream.Create(Label2.Caption+Df.FileName,
fs.seek(0,soFromBeginning));
end
else
begin
fs := TFileStream.Create(Label2.Caption+Df.FileName,
fmOpenWrite or fmShareDenyNone);
fs.Seek(0,soFromEnd);
end;
fs.WriteBuffer(Buffer^,nRetr);
fs.Destroy ;
FreeMem(Buffer);
Df.WorkFlag := 0;
Socket.SendText('文件传送服务准备完毕,可以传送下一个文件!'#13#10)
end;
end;
end;


Client端(发送)
procedure TForm2.N7Click(Sender: TObject);
var
fs : TFileStream;
Buf : Pointer;
begin
fs := TFileStream.Create(Edit1.Text,fmOpenRead or fmShareDenyNone);
GetMem(Buf,fs.size);
fs.Seek(0,soFromBeginning);
fs.ReadBuffer(Buf^,fs.Size);
RichEdit1.Lines.Add(DateToStr(Date)+' '+TimeToStr(Time)+'==>>'+
'共发送:'+IntToStr(cs.Socket.SendBuf(Buf^,fs.Size))+
'字节。' );

end;

 
你的程序在文件很小的时候是可以工作的,但文件大了就不能工作。
因为 TCP 是数据流的概念,所谓流就是说TCP数据并没有包的概念,
也就是说:
你在客户端 SendBuf 一次,服务器上可能会激发多个 OnClientRead 事件
你在客户端 SendBuf 多次,服务器上可能只激发一个 OnClientRead 事件
TCP 帮你保证数据的内容和顺序正确,但不帮你分包。
要实现 请求/应答 工作模式,你必须自己建立一种分包组包机制。
最常见的办法有:
1。加包头,并带上包长信息,根据包长来分段。
2。加包分隔符,判断特定的字符串来分包,比如回车。
一般情况用1比较好,如果是纯文本用2比较简单。
 
但我传一个很小的文件也会出现这种情况。
 
在clientread最前面加上
while socket.receivelength>0 then
beign

//你的代码
//删除这这两句或者放到最后
end;
Df.WorkFlag := 0;
Socket.SendText('文件传送服务准备完毕,可以传送下一个文件!'#13#10)

 
这段代码很奇怪:
1。服务器明明有一个结构,包括文件名称,长度等,客户端却没有按照结构组包发送。
2。服务器上第一句 Df := Socket.Data; 这种写法也是很奇怪,这里的Data从哪发来?
实际跟踪你可以发现,根本就是 nil 的,既然是 nil 的,后面的 df.xxx 就会引用空地址报错
可你确实又知道用 Socket.ReceiveText 函数可以接收数据。
所以这些代码给人一种东拼西凑,想当然的感觉。
先看看 Delphi 里的例子,在 Demo 的基础上修改比较好。
 
写代码的人没有真正理解TCP,很多地方不符合逻辑
 
代码没有大的问题,除了在没有考虑缓存外。轻松虎可能不知道socket.data的用法,它一开始当然是空的,它就是需要被赋值然后以后再提出来判断。从代码上看,它的作用是判断接收信息是文件内容信息还是其它字符串信息
 
没有问题怎么会出错了?计算机可不讲政治上的那些客套话的,哈!
 
你不应该在按钮事件中发送消息或者数据。
正确的方法应该是在ClientSocket的OnClientRead()事件当中发送
procedure TfrmMain.csClientRead(Sender: TObject; Socket: TCustomWinSocket);
var
sRecv,cmd:string;
bufSend:pointer;
iLength:Integer;
begin
sRecv:=Socket.ReceiveText;
cmd:=copy(sRecv,1,5);
if cmd='ACCEPT' then
begin
fsSend:=TFileStream.Create(FileName,fmOpenRead);
bStart:=False;
memo1.Lines.Add('开始发送!');
//BYTEPERSEND是每次发送包的大小。
Socket.SendText(FILEPROPERTY+IntToStr(Trunc(fsSend.Size/BYTEPERSEND)+1));
//创建文件流并发送文件长度。
end
else if cmd=NEXTWILLBEDATA then
begin
Socket.SendText(NEXTWILLBEDATA);
//通知接收端继续传送数据。
end
else if cmd=DATA then //发送数据
begin
if not bStart then
begin
memo1.Lines.Add('发送数据!');
bStart:=True;
end;
if fsSend.Position< fsSend.Size-1 then//数据没有发送完
begin
iLength:=fsSend.Size-1-fsSend.Position;
if iLength>BYTEPERSEND then
iLength:=BYTEPERSEND;
GetMem(bufSend,iLength+1);
try
fsSend.Read(bufSend^,iLength);
Socket.SendBuf(bufSend^,iLength);
finally
FreeMem(bufSend,iLength+1);
end;{of try}
end
else //文件传送结束,没有数据需要发送了。
begin
Socket.SendText(END);
memo1.Lines.Add('结束!');
fsSend.Free;
end;
end
else if cmd=ABORT then //取消传送
begin
memo1.Lines.Add('中止!');
fsSend.Free;
end;
end;
 
后退
顶部