为什么TCusomWinSocket.ReceiveBuf没有返回0?

时间:2012-01-05 19:46:37

标签: delphi sockets

说到套接字,TClientSocket和TServerSockets是我最喜欢的,因为它们使用简单。

我的任务很简单。我需要通过这两个组件发送一个文件(RAW),所以我有两个例程,如下所示:

procedure csRead(Sender: TObject; Socket: TCustomWinSocket);
var
   MSCli : TMemoryStream;
   cnt   : Integer;
   buf   : array [0..1023] of byte;
begin
    MSCli := TMemoryStream.Create;
    try
    repeat
      cnt := Socket.ReceiveBuf(buf[0], 1024); //This loop repeats endlesly
      MSCli.Write(buf[0], cnt)
    until cnt = 0;
    finally
    MSCli.SaveToFile('somefile.dmp');
    MSCli.Free;
    end;
end;

当然是发件人:

  //...some code
    MSSErv.LoadFromFile('some file');
    MSServ.Position := 0;
    Socket.SendStream(MSServ);
  end;

读者的循环在不知不觉中重复,我不知道为什么。可能是问题的根源是什么?

1 个答案:

答案 0 :(得分:5)


SendStream()不是一个特别好的选择 - 永远。它旨在发送整个TStream,然后在完成后释放它。但是,如果套接字设置为非阻塞模式且套接字在发送期间阻塞,SendStream()会立即退出,并且不会释放TStream。您必须再次致电SendStream()以继续从TStream停止的位置发送SendStream()。但是还有其他条件会导致SendStream()退出并释放TStream,而您实际上并不知道它何时释放或未释放TStream,因此它变得非常危险尝试使用相同的SendStream()对象再次呼叫TStream。一种更安全的方法是不惜一切代价避免SendStream(),而是直接在您自己的循环中调用SendBuf()

话虽如此,SendStream()不会通知接收方将发送多少字节,因此接收方不知道何时停止读取(除非您在发送TStream后关闭连接)。更好的选择是在发送TStream数据之前发送预期的字节数。这样,接收器可以先读取字节数,然后在收到指定的字节数时停止读取。例如:

procedure ReadRawFromSocket(Socket: TCustomWinSocket; Buffer: Pointer; BufSize: Integer);
var 
  buf: PByte; 
  cnt: Integer; 
begin 
  buf := PByte(Buffer); 
  while BufSize > 0 do
  begin 
    cnt := Socket.ReceiveBuf(buf^, BufSize);
    if cnt < 1 then begin
      if (cnt = -1) and (WSAGetLastError() = WSAEWOULDBLOCK) then
      begin
        Application.ProcessMessages;
        Continue;
      end;
      Abort;
    end;
    Inc(buf, cnt);
    Dec(BufSize, cnt);
  end; 
end;

function ReadInt64FromSocket(Socket: TCustomWinSocket): Int64;
begin
  ReadRawFromSocket(Socket, @Result, SizeOf(Int64));
end;

procedure ReadMemStreamFromSocket(Socket: TCustomWinSocket: Stream: TMemoryStream);
var
  cnt: Int64; 
begin
  cnt := ReadInt64FromSocket(Socket);
  if cnt > 0 then
  begin
    Stream.Size := cnt; 
    ReadRawFromSocket(Socket, Stream.Memory, cnt);
  end; 
end;

procedure csRead(Sender: TObject; Socket: TCustomWinSocket); 
var 
  MSCli : TMemoryStream; 
begin 
  MSCli := TMemoryStream.Create; 
  try 
    ReadMemStreamFromSocket(Socket, MSCli);
    MSCli.SaveToFile('somefile.dmp'); 
  finally
    MSCli.Free; 
  end; 
end; 

procedure SendRawToSocket(Socket: TCustomWinSocket; Buffer: Pointer; BufSize: Integer);
var 
  buf: PByte; 
  cnt: Integer; 
begin 
  buf := PByte(Buffer); 
  while BufSize > 0 do
  begin 
    cnt := Socket.SendBuf(buf^, BufSize);
    if cnt < 1 then begin
      if (cnt = -1) and (WSAGetLastError() = WSAEWOULDBLOCK) then
      begin
        Application.ProcessMessages;
        Continue;
      end;
      Abort;
    end;
    Inc(buf, cnt);
    Dec(BufSize, cnt);
  end; 
end;

procedure SendInt64ToSocket(Socket: TCustomWinSocket; Value: Int64);
begin
  SendRawToSocket(Socket, @Value, SizeOf(Int64));
end;

procedure SendMemStreamToSocket(Socket: TCustomWinSocket: Stream: TMemoryStream);
begin
  SendInt64FromSocket(Socket, Stream.Size);
  SendRawToSocket(Socket, Stream.Memory, Stream.Size);
end;

begin
  ...
  MSSErv.LoadFromFile('some file'); 
  MSServ.Position := 0; 
  SendMemStreamToSocket(Socket, MSServ);
  ...
end;