在线程内的套接字上接收文件时出错 [英] Error to receive file on socket inside a thread

查看:83
本文介绍了在线程内的套接字上接收文件时出错的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我无法接收包含PNG文件的字节数组. 在OnClientRead事件中执行代码后,它可以正常工作,在为线程传输时已经发生了MemoryStream错误,内容为:

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.

此时:

if SD.State = ReadingSize then

if SD.State = ReadingSize then

我想知道如何解决此特定问题,还想知道如何接收包含文件或简单String的数据?

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?

代码:

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;


更新:

答案中的代码对我不起作用,因为ServerType=stThreadBlocking阻止了所有客户端与服务器的连接.因此,我正在搜索类似的内容(ServerType=stNonBlockingTThreadOnAccept事件):

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;

此代码在此行存在数据转换错误: DataSize := StrToInt(sl);

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

我该如何解决?

推荐答案

如何解决这个特定的问题

how solve this specific trouble

您没有按照本意使用TServerSocket线程.

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

如果您想在stThreadBlocking模式下使用TServerSocket(请参阅我的其他答案有关如何使用TServerSocketstNonBlocking模式下),正确的方法是:

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:

如果不这样做,TServerSocket将创建自己的默认线程(在主线程中触发OnClient(Read|Write)事件),这会干扰您的手动线程.

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.

尝试更多类似的方法:

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.

这篇关于在线程内的套接字上接收文件时出错的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆