TmemoryStream 服务器在接收流时内存不足

TmemoryStream Server Out Of Memory On receiving stream

我想做的就是使用 TSockets 发送一个流,但是我遇到了 "out of memory" 错误。我设法发送文件,而不是图像。在服务器表单的 OnCreate 事件中,我正在创建流。对于客户端,在表单的 OnCreate 我正在创建流,也是一个 bmp。

我试过看它是不是不发送,但是它发送了一些东西,只是我不知道是什么。在服务器端,我测试了向客户端发送命令,我知道它们发送了,我也测试了布尔值,但仍然出现内存错误。

procedure TForm1.ServerSocket1ClientRead(Sender: TObject; Socket: TCustomWinSocket);
var
  BytesReceived: Longint;
  CopyBuffer: Pointer;
  ChunkSize: Integer;
  TempSize: Integer;
  FSize: Integer;
  writing: Boolean;
  bmp: tbitmap;
const
  MaxChunkSize: Longint = 8192;
begin
  If FSize = 0 then
  begin
    If Socket.ReceiveLength > SizeOf(TempSize) then
    begin
      Socket.ReceiveBuf(TempSize, SizeOf(TempSize));
      stream.SetSize(TempSize);
      FSize := TempSize;
    End;
  End;
  If (FSize > 0) and (writing) then            //receiving the image
  begin            
    GetMem(CopyBuffer, MaxChunkSize);
    writing := true;
    While Socket.ReceiveLength > 0 do
    Begin
      ChunkSize := Socket.ReceiveLength;
      If ChunkSize > MaxChunkSize then
        ChunkSize := MaxChunkSize;
      BytesReceived := Socket.ReceiveBuf(CopyBuffer^, ChunkSize);
      stream.Write(CopyBuffer^, BytesReceived);
      Dec(FSize, BytesReceived);
    End;
    If FSize = 0 then
    begin
      bmp.LoadFromStream(stream);
      self.Image1.Picture.Bitmap.LoadFromStream(stream);
      stream.SetSize(0);
      FSize := 0;
    End;                             
    FreeMem(CopyBuffer, MaxChunkSize);
    writing := false;
    stream.Free;
    exit;
  End;
end;

procedure TForm1.ClientSocket1Read(Sender: TObject; Socket: TCustomWinSocket);
var
  size: Integer;
  Data: string;
begin
  try
    CaptureImage(bmp); //i have a procedure for this & know it works
    bmp.SaveToStream(stream);
    size := stream.size;         //sending the tbitmap image
    stream.Position := 0;
    Socket.SendBuf(size, sizeof(size));
    Socket.SendStream(stream);
  except
    stream.Free;
  end;

您在从客户端读取数据时没有考虑 FSize。您正在阅读客户端发送的内容,并且在达到流大小时不会停止。而且您没有考虑到它可能(并且很可能会)需要多个 OnRead 事件来接收整个图像,因此您最终可能会过早地释放您的 stream

此外,TCustomWinSocket.SendStream() 不是很稳定,尤其是在非阻塞模式下使用套接字时。您应该直接在循环中使用 TCustomWinSocket.SendBuf() 并根据需要处理任何套接字错误。

试试像这样的东西:

uses
  ..., System.Math;

procedure TForm1.ServerSocket1ClientConnect(Sender: TObject; Socket: TCustomWinSocket);
begin
  Socket.Data := nil;
end;

procedure TForm1.ServerSocket1ClientDisconnect(Sender: TObject; Socket: TCustomWinSocket);
begin
  if Socket.Data <> nil then
    TMemoryStream(Socket.Data).Free;
end;

procedure TForm1.ServerSocket1ClientRead(Sender: TObject; Socket: TCustomWinSocket);
var
  Stream: TMemoryStream;
  BytesReceived: Integer;
  StreamSize, TempSize: Int32;
  BytesRemaining: Int64;
  P: PByte;
  ChunkSize: Integer;
  bmp: TBitmap;
const
  MaxChunkSize: Int64 = 8192;
begin
  Stream := TMemoryStream(Socket.Data);

  // receiving the image size
  if Stream = nil then
  begin
    if Socket.ReceiveLength < SizeOf(TempSize) then Exit;
    BytesReceived := Socket.ReceiveBuf(TempSize, SizeOf(TempSize));
    if BytesReceived <= 0 then Exit; 
    StreamSize := ntohl(TempSize);
    Stream := TMemoryStream.Create;
    Socket.Data := Stream;
    Stream.Size := StreamSize;
    BytesRemaining := StreamSize;
  end else
    BytesRemaining := Stream.Size - Stream.Position;

  // receiving the image
  if BytesRemaining > 0 then
  begin
    P := PByte(Stream.Memory);
    if Stream.Position > 0 then
      Inc(P, Stream.Position);
    repeat
      ChunkSize := Integer(Math.Min(BytesRemaining, MaxChunkSize));
      BytesReceived := Socket.ReceiveBuf(P^, ChunkSize);
      if BytesReceived <= 0 then Exit;
      Inc(P, BytesReceived);
      Dec(BytesRemaining, BytesReceived);
      Stream.Seek(soCurrent, BytesReceived);
    until BytesRemaining = 0;
  end;

  // loading the image
  try
    bmp := TBitmap.Create;
    try
      Stream.Position := 0;
      bmp.LoadFromStream(Stream);
      Image1.Picture.Bitmap.Assign(bmp);
    finally
      bmp.Free;
    end;
  finally
    Socket.Data := nil;
    Stream.Free;
  end;
end;

uses
  ..., System.Math, Winapi.WinSock;

function SendRaw(Sckt: TSocket; const Data; Size: Integer);
var
  P: PByte;
  BytesSent: Integer;
begin
  Result := 0;
  P := PByte(@Data);
  while Size > 0 do
  begin
    BytesSent := send(Sckt, P^, Size, 0);
    if BytesSent = -1 then Exit;
    Inc(P, BytesSent);
    Dec(Size, BytesSent);
    Inc(Result, BytesSent);
  end;
end;

procedure WriteToSocket(Socket: TCustomWinSocket; const Data; Size: Integer);
var
  Stream: TMemoryStream;
  P: PByte;
  BytesSent: Integer;
begin
  if Size <= 0 then Exit;

  Stream := TMemoryStream(Socket.Data);
  P := PByte(@Data);

  if not ((Stream <> nil) and (Stream.Size > 0)) then
  begin
    BytesSent := SendRaw(Socket.SocketHandle, P^, Size);
    if BytesSent > 0 then
    begin
      Dec(Size, BytesSent);
      if Size = 0 then Exit;
      Inc(P, BytesSent);
    end;
  end;

  if Stream = nil then
  begin
    Stream := TMemoryStream.Create;
    Socket.Data := Stream;
  end else
    Stream.Seek(soEnd, 0);

  Stream.WriteBuffer(P^, Size);
end;

procedure TForm1.ClientSocket1Connect(Sender: TObject; Socket: TCustomWinSocket);
begin
  Socket.Data := nil;
end;

procedure TForm1.ClientSocket1Disconnect(Sender: TObject; Socket: TCustomWinSocket);
begin
  if Socket.Data <> nil then
    TMemoryStream(Socket.Data).Free;
end;

procedure TForm1.ClientSocket1Write(Sender: TObject; Socket: TCustomWinSocket);
var
  Stream: TMemoryStream;
  BytesRemaining: Int64;
  ChunkSize: Integer;
  P: PByte;
begin
  Stream := TMemoryStream(Socket.Data);
  if Stream = nil then Exit;

  BytesRemaining := Stream.Size;
  if BytesRemaining = 0 then Exit;

  P := PByte(Stream.Memory);
  repeat
    ChunkSize := Integer(Math.Min(BytesRemaining, MaxInt));
    BytesSent := SendRaw(Socket.SocketHandle, P^, ChunkSize);
    if BytesSent > 0 then
    begin
      Inc(P, BytesSent);
      Dec(BytesRemaining, BytesSent);
    end;
  until (BytesSent < ChunkSize) or (BytesRemaining = 0);

  if BytesRemaining = 0 then
    Stream.Clear
  else if P > Stream.Memory then
  begin
    MoveMemory(Stream.Memory, P, BytesRemaining);
    Stream.Size := BytesRemaining;
  end;
end;

procedure TForm1.ClientSocket1Read(Sender: TObject; Socket: TCustomWinSocket);
var
  Stream: TMemoryStream;
  bmp: TBitmap;
  StreamSize, TempSize: Int32;
begin
  ...
  Stream := TMemoryStream.Create;
  try
    // saving the bitmap image
    bmp := TBitmap.Create;
    try
      CaptureImage(bmp);
      bmp.SaveToStream(Stream);
    finally
      bmp.Free;
    end;

    // sending the TBitmap image
    StreamSize := Stream.Size;
    TempSize := htonl(StreamSize);
    WriteToSocket(Socket, TempSize, sizeof(TempSize));
    WriteToSocket(Socket, Stream.Memory^, StreamSize);
  finally
    Stream.Free;
  end;
end;