-1

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?

Community
  • 1
  • 1

1 Answers1

3

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:

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.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • `Then you can handle the data according to its type as needed.` then basically i have that send a `String` command to server and check for example: `if Pos('{file}', Socket.ReceiveText) > 0 then begin // handle data end; if Pos('{string}', Socket.ReceiveText) > 0 then begin // handle data end;` –  Dec 30 '17 at 10:02
  • I thought that `Socket` had some property to check if is receiving a file or a string :-). –  Dec 30 '17 at 10:05
  • And also this not worked to me `use the TServerSocket.OnGetThread event to instantiate the thread.` thread not is executed. When some client connect, nothing happens like on `OnAccept`. –  Dec 30 '17 at 10:56
  • 1
    @JeffersonFarias Do not use `ReceiveText()`. It has no concept of message boundaries. Since the protocol being used is binary in nature, just send an extra byte containing the type, eg: `<1 byte type><4 byte size>`. If `type` is string, `data` is byte encoded characters, like UTF-8. Otherwise `data` is a file/PNG. Etc. And `OnGetThread` works fine for me when `ServerType=stThreadBlocking` – Remy Lebeau Dec 30 '17 at 14:25
  • `ServerType=stThreadBlocking` here, not receives client connection :-( –  Dec 30 '17 at 15:00
  • I think that must exist some other alternative beyond `OnGetThread` for initialize this thread. –  Dec 30 '17 at 15:18
  • Probably continue using `OnAccept` and `TThread` (not `TServerClientThread`). With this probably we can remove: `SocketStrm := TWinSocketStream.Create(ClientSocket, 5000);` **why this direct connection here**? –  Dec 30 '17 at 15:33
  • @JeffersonFarias no, you MUST use `OnGetThread`, `TServerClientThread`, and `TWinSocketStream`, for the reason I already stated in my answer. That is how `TServerSocket` is *designed* to be used. If it is not working for you, you are not using it correctly. It always worked fine for me. But then, I stopped using it years ago, I use `TIdTCPServer` now – Remy Lebeau Dec 30 '17 at 20:39
  • `If you want to use TServerSocket in stThreadBlocking mode, the correct way is to:` and if i no want use this way, then how stays? –  Dec 30 '17 at 20:56
  • @JeffersonFarias you CANNOT use your own `TThread` class with `TServerSocket`. Period. *IT IS NOT DESIGNED FOR THAT*. `TServerSocket` supports only two modes: `stNonBlocking` and `stThreadBlocking`. You need to use `TServerClientThread` in `stThreadBlocking` mode. [My other answer](https://stackoverflow.com/a/48013508/65863) showed you how to use `TServerSocket` in `stNonBlocking` mode – Remy Lebeau Dec 30 '17 at 23:07
  • ok but i already saw [some examples](https://github.com/senjaxus/Delphi_Remote_Access_PC) on web that use `stNonBlocking` with `TThread`. The trouble with these example is because probably not works to my case here. Then based in this linked example, could show in your answer how stays the thread activation please? –  Dec 30 '17 at 23:38
  • 3
    @JeffersonFarias I've been studying the inner workings of the VCL for almost 20 years now. I have used `TServerSocket` and `TClientSocket` in many projects, in both modes. You think you know better than me, then have fun figuring it out for yourself. I've given you two complete solutions in both modes. You clearly don't know what you are doing, and have chosen to ignore me. So I'm done. – Remy Lebeau Dec 31 '17 at 03:43
  • based in [this answer](https://pastebin.com/nauBhDw1) how implement this separate thread to receive connections? [Refrence](https://groups.google.com/forum/#!msg/borland.public.cppbuilder.internet.socket/T3YHBxuahTc/HdSsONSOT7sJ). Could edit your answer inserting this? –  Dec 31 '17 at 23:08
  • 2
    @JeffersonFarias read my answer and previous comments more carefully. I already addressed that. YOU CAN'T USE YOUR OWN `TThread` class! PERIOD! STOP ASKING FOR IT. In non-blocking mode, `TServerSocket` doesn't use any threads of its own, and you are not meant to use your own threads, so don't even try. In blocking mode, `TServerSocket` uses its own thread for accepting connections, and then you must use `TServerClientThread` for your I/O logic once a client is connected. Anything else is beyond the scope of `TServerSocket`s design. Now please, pay attention to what is told to you. – Remy Lebeau Jan 01 '18 at 04:10
  • for example [**here**](https://stackoverflow.com/a/17440911/9120317) how i could receive **"SomeString"** on `ClientExecute()` method of a way that the code referent to receiving screenshot not can indentify a data that not is really a file type? could add code example in your answer? –  Jan 05 '18 at 01:51
  • When you back of vacation! ok? :-) –  Jan 05 '18 at 02:01
  • @JeffersonFarias: I have already explained to you how to accomplish that, and given you relevant code snippets that you just need to adapt to your situation. And no, I'm not back from vacation yet, I just happened to have time on the computer today. – Remy Lebeau Jan 05 '18 at 04:26
  • Could edit this second part of answer inserting a code example to better understanding? –  Jan 08 '18 at 15:14