Error to receive file on socket inside a thread

不打扰是莪最后的温柔 提交于 2019-12-30 15:45:17

问题


I'm having trouble to receive a byte array containg a PNG file. When the code is executed in OnClientRead event it works fine, already when transfered for a thread, happens an error of MemoryStream that says:

Out of memory while expanding memory stream.

At this point:

if SD.State = ReadingSize then

I want to know how to solve this specific trouble and also how can I check if I'm receiving a data that contains a file or a simple String?

The code:

type
  TSock_Thread = class(TThread)
  private
    Socket: TCustomWinSocket;
  public
    constructor Create(aSocket: TCustomWinSocket);
    procedure Execute; override;
  end;

type
  TInt32Bytes = record
    case Integer of
      0: (Bytes: array[0..SizeOf(Int32)-1] of Byte);
      1: (Value: Int32);
  end;

  TSocketState = (ReadingSize, ReadingStream);

  TSocketData = class
  public
    Stream: TMemoryStream;
    Png: TPngImage;
    State: TSocketState;
    Size: TInt32Bytes;
    Offset: Integer;
    constructor Create;
    destructor Destroy; override;
  end;

 { ... }

constructor TSock_Thread.Create(aSocket: TCustomWinSocket);
begin
  inherited Create(true);
  Socket := aSocket;
  FreeOnTerminate := true;
end;

procedure TSock_Thread.Execute;
var
  s: String;
  BytesReceived: Integer;
  BufferPtr: PByte;
  SD: TSocketData;
  Item: TListItem;
begin
  inherited;

  while Socket.Connected do
  begin
    if Socket.ReceiveLength > 0 then
    begin
      s := Socket.ReceiveText;

     { SD := TSocketData(Socket.Data);

      if SD.State = ReadingSize then
      begin
        while SD.Offset < SizeOf(Int32) do
        begin
          BytesReceived := Socket.ReceiveBuf(SD.Size.Bytes[SD.Offset],
            SizeOf(Int32) - SD.Offset);
          if BytesReceived <= 0 then
            Exit;
          Inc(SD.Offset, BytesReceived);
        end;
        SD.Size.Value := ntohl(SD.Size.Value);
        SD.State := ReadingStream;
        SD.Offset := 0;
        SD.Stream.Size := SD.Size.Value;
      end;

      if SD.State = ReadingStream then
      begin
        if SD.Offset < SD.Size.Value then
        begin
          BufferPtr := PByte(SD.Stream.Memory);
          Inc(BufferPtr, SD.Offset);
          repeat
            BytesReceived := Socket.ReceiveBuf(BufferPtr^,
              SD.Size.Value - SD.Offset);
            if BytesReceived <= 0 then
              Exit;
            Inc(BufferPtr, BytesReceived);
            Inc(SD.Offset, BytesReceived);
          until SD.Offset = SD.Size.Value;
        end;
        try
          SD.Stream.Position := 0;
          SD.Png.LoadFromStream(SD.Stream);
          SD.Stream.Clear;
        except
          SD.Png.Assign(nil);
        end;
        Item := Form1.ListView1.Selected;
        if (Item <> nil) and (Item.Data = Socket) then
          Form1.img1.Picture.Graphic := SD.Png;
        SD.State := ReadingSize;
        SD.Offset := 0;
      end;  }

    end;
    Sleep(100);
  end;

end;

procedure TForm1.ServerSocket1Accept(Sender: TObject; Socket: TCustomWinSocket);
var
  TST: TSock_Thread;
begin
  TST := TSock_Thread.Create(Socket);
  TST.Resume;
end;

UPDATE:

The code in the answer is not working for me because ServerType=stThreadBlocking blocks all clients connections with the server. And because of this, I'm searching for something like this (ServerType=stNonBlocking, TThread and OnAccept event):

type
  TSock_Thread = class(TThread)
  private
    Png: TPngImage;
    Socket: TCustomWinSocket;
  public
    constructor Create(aSocket: TCustomWinSocket);
    procedure Execute; override;
     procedure PngReceived;
  end;

// ...

// ===============================================================================

constructor TSock_Thread.Create(aSocket: TCustomWinSocket);
begin
  inherited Create(true);
  Socket := aSocket;
  FreeOnTerminate := true;
end;

// ===============================================================================

procedure TSock_Thread.PngReceived;
var
  Item: TListItem;
begin
  Item := Form1.ListView1.Selected;
  if (Item <> nil) and (Item.Data = Socket) then
    Form1.img1.Picture.Graphic := Png;
end;

procedure TSock_Thread.Execute;
var
  Reciving: Boolean;
  DataSize: Integer;
  Data: TMemoryStream;
  s, sl: String;
begin
  inherited;
  while Socket.Connected do
  begin
    if Socket.ReceiveLength > 0 then
    begin
      s := Socket.ReceiveText;
      if not Reciving then
      begin

        SetLength(sl, StrLen(PChar(s)) + 1);
        StrLCopy(@sl[1], PChar(s), Length(sl) - 1);
        DataSize := StrToInt(sl);
        Data := TMemoryStream.Create;
        Png := TPngImage.Create;
        Delete(s, 1, Length(sl));
        Reciving := true;
      end;
      try
        Data.Write(s[1], Length(s));
        if Data.Size = DataSize then
        begin
          Data.Position := 0;
          Png.LoadFromStream(Data);
          Synchronize(PngReceived);
          Data.Free;
          Reciving := false;
        end;
      except
        Png.Assign(nil);
        Png.Free;
        Data.Free;
      end;
    end;
    Sleep(100);
  end;

end;

procedure TForm1.ServerSocket1Accept(Sender: TObject; Socket: TCustomWinSocket);
var
  TST: TSock_Thread;
begin
  TST := TSock_Thread.Create(Socket);
  TST.Resume;
end;

This code has an error of conversion of data at this line: DataSize := StrToInt(sl);

How can I fix this?


回答1:


how solve this specific trouble

You are not using TServerSocket threading the way it is meant to be used.

If you want to use TServerSocket in stThreadBlocking mode (see my other answer for how to use TServerSocket in stNonBlocking mode), the correct way is to:

  • derive a thread class from TServerClientThread
  • override its virtual ClientExecute() method to do your I/O work (via TWinSocketStream)
  • use the TServerSocket.OnGetThread event to instantiate the thread.

If you don't do this, TServerSocket will create its own default threads (to fire the OnClient(Read|Write) events in the main thread), which will interfere with your manual threads.

Also, you don't need the state machine that I showed you in my answer to your other question. That was for event-driven code. Threaded I/O code can be written linearly instead.

Try something more like this:

type
  TSock_Thread = class(TServerClientThread)
  private
    Png: TPngImage;
    procedure PngReceived;
  protected
    procedure ClientExecute; override;
  end;

type
  TInt32Bytes = record
    case Integer of
      0: (Bytes: array[0..SizeOf(Int32)-1] of Byte);
      1: (Value: Int32);
  end;

procedure TSock_Thread.ClientExecute;
var
  SocketStrm: TWinSocketStream;
  Buffer: TMemoryStream;
  Size: TInt32Bytes;
  Offset: Integer;
  BytesReceived: Integer;
  BufferPtr: PByte;
begin
  SocketStrm := TWinSocketStream.Create(ClientSocket, 5000);
  try
    Buffer := TMemoryStream.Create;
    try
      Png := TPngImage.Create;
      try
        while ClientSocket.Connected do
        begin
          if not SocketStrm.WaitForData(100) then Continue;

          Offset := 0;
          while Offset < SizeOf(Int32) do
          begin
            BytesReceived := SocketStrm.Read(Size.Bytes[Offset], SizeOf(Int32) - Offset);
            if BytesReceived <= 0 then Exit;
            Inc(Offset, BytesReceived);
          end;
          Size.Value := ntohl(Size.Value);
          Buffer.Size := Size.Value;
          BufferPtr := PByte(Buffer.Memory);

          Offset := 0;
          while Offset < Size.Value do
          begin
            BytesReceived := SocketStrm.Read(BufferPtr^, Size.Value - Offset);
            if BytesReceived <= 0 then Exit;
            Inc(BufferPtr, BytesReceived);
            Inc(Offset, BytesReceived);
          end;

          Buffer.Position := 0;
          try
            Png.LoadFromStream(Buffer);
          except
            Png.Assign(nil);
          end;

          Synchronize(PngReceived);
        end;
      finally
        Png.Free;
      end;
    finally
      Buffer.Free;
    end;
  finally
    SocketStrm.Free;
  end;
end;

procedure TSock_Thread.PngReceived;
var
  Item: TListItem;
begin
  Item := Form1.ListView1.Selected;
  if (Item <> nil) and (Item.Data = ClientSocket) then
    Form1.img1.Picture.Graphic := Png;
end;

procedure TForm1.ServerSocket1GetThread(Sender: TObject; ClientSocket: TServerClientWinSocket; var SocketThread: TServerClientThread);
begin
  SocketThread := TSock_Thread.Create(False, ClientSocket);
end;

how i can check if i'm receiving a data that contains a file or a simple String?

The client needs to send that information to your server. You are already sending a value to specify the data size before sending the actual data. You should also preceed the data with a value to specify the data's type. Then you can handle the data according to its type as needed.



来源:https://stackoverflow.com/questions/48030774/error-to-receive-file-on-socket-inside-a-thread

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!