通过套接字发送文件:SendText() 和 SendStream() 未正确发送数据

Send file over socket: SendText() and SendStream() not sending data correctly

我尝试将 .jpg 文件从 ClientSocket 发送到 ServerSocket,但显然在 SendText 和 [=32 附近遇到了问题=]SendStream 起作用,因为执行 SendText 后得到的结果总是 0 .但是存在其他奇怪的事情,当我在发送文件大小之前放置 ShowMessage() 时,SendText 工作(并且收到大小)但是 SendStream 失败,结果为 -1

如何解决?

这是我最后一次尝试>

发件人:

uses
  System.Win.ScktComp, Vcl.Imaging.jpeg;

type
  TForm1 = class(TForm)
    ClientSocket1: TClientSocket;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
  P: TPicture;
  J: TJpegImage;
  MS: TMemoryStream;
  Sent: Boolean;
begin
  ClientSocket1.Host := '192.168.0.10';
  ClientSocket1.Port := 1234;
  ClientSocket1.Active := True;

  try
    MS := TMemoryStream.Create;
    MS.Position := 0;
    P := TPicture.Create;
    P.Bitmap.LoadFromFile('sent.bmp');
    J := TJpegImage.Create;
    J.Assign(P.Bitmap);
    J.CompressionQuality := 100;
    J.SaveToStream(MS);
    ShowMessage(IntToStr(Round(MS.Size / 1024)));
    ClientSocket1.Socket.SendText(IntToStr(MS.Size) + #0);
    Sent := ClientSocket1.Socket.SendStream(MS);
    ShowMessage(BoolToStr(Sent));
  finally
    MS.Free;
    P.Free;
    J.Free;
  end;
end;

end.

接收方:

uses
  System.Win.ScktComp, Vcl.Imaging.jpeg;

type
  TForm1 = class(TForm)
    Button1: TButton;
    ServerSocket1: TServerSocket;
    procedure ServerSocket1ClientRead(Sender: TObject;
      Socket: TCustomWinSocket);
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
ServerSocket1.Port := 1234;
ServerSocket1.Active := True;
end;

procedure TForm1.ServerSocket1ClientRead(Sender: TObject;
  Socket: TCustomWinSocket);
var
  s: string;
  Stream: TMemoryStream;
  Receiving: Boolean;
  stSize: Integer;
  jpg: TJpegImage;
begin
    if Socket.ReceiveLength > 0 then
    begin
      s := Socket.ReceiveText;

      if not Receiving then
      begin
        if Pos(#0, s) > 0 then
        begin
          stSize := strToInt(Copy(s, 1, Pos(#0, s) - 1));
          ShowMessage(IntToStr(Round(stSize / 1024)));
        end
        else
          ;
        Stream := TMemoryStream.Create;
        Receiving := true;
        Delete(s, 1, Pos(#0, s));
      end;
      try
        Stream.Write(AnsiString(s)[1], length(s));
        if Stream.Size = stSize then
        begin
          Stream.Position := 0;
          Receiving := false;
          jpg := TJpegImage.Create;
          jpg.LoadFromStream(Stream);
          jpg.SaveToFile('received.jpg');
          Stream.Free;
        end;
      except
        Stream.Free;
      end;
    end;
  end;

end.

您的发件人代码未处理 SendText()SendStream() 发送部分数据的可能性,尤其是在非阻塞模式下。 SendStream() 可能会或可能不会在退出前释放 TStream,您无法知道其中一种方式。 SendText() 在 D2009+ 中无法正确处理 Unicode 字符串。

您的接收器代码没有考虑到 ReceiveText() 可能不会并且很可能不会在一次读取中接收所有数据。它可以并且很可能将需要多个 OnClientRead 事件来接收所有数据。或者 ReceiveText() 可能会收到部分图像数据并错误地尝试将这些字节转换为字符串字符。此外,如果单次读取中的数据不完整,您不会在 OnClientRead 事件之间缓存未处理的字节。

所以,根本不要使用 SendText()/SendStream()ReceiveText()!您没有正确使用它们,尤其是在非阻塞模式下。始终使用 SendBuf()ReceiveBuf(),并注意它们的 return 值,以便您知道何时需要再次调用它们来处理更多数据。

试试像这样的东西:

unit Unit1;

interface

uses
  ..., System.Win.ScktComp;

type
  TForm1 = class(TForm)
    ClientSocket1: TClientSocket;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure ClientSocket1Connect(Sender: TObject; Socket: TCustomWinSocket);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

uses
  Vcl.Imaging.jpeg, System.Math;

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
  ClientSocket1.Host := '192.168.0.10';
  ClientSocket1.Port := 1234;
  ClientSocket1.Active := True;
end;

procedure TForm1.ClientSocket1Connect(Sender: TObject; Socket: TCustomWinSocket);
var
  B: TBitmap;
  J: TJpegImage;
  MS: TMemoryStream;
  Sent: Boolean;

  function htonll(Value: UInt64): UInt64;
  var
    UL: Windows.ULARGE_INTEGER;
    L: UInt32;
  begin
    UL.QuadPart := Value;
    L := htonl(UL.HighPart);
    LParts.HighPart := htonl(UL.LowPart);
    LParts.LowPart := L;
    Result := UL.QuadPart;
  end;

  function DoSend(Buf: Pointer; BufLen: Integer): Boolean;
  var
    P: PByte;
    BytesSent: Integer;
  begin
    Result := False;
    P := PByte(Buf);
    while BufLen > 0 do
    begin
      BytesSent := Socket.SendBuf(P^, BufLen);
      if BytesSent = -1 then
      begin
        if WSAGetLastError = WSAEWOULDBLOCK then
        begin
          // TODO: use Winsock.select() or TClientSocket.OnWrite to detect when
          // the socket can accept more bytes again...
          Continue;
        end;
        Exit;
      end;
      Inc(P, BytesSent);
      Dec(BufLen, BytesSent);
    end;
    Result := True;
  end;

  function DoSendStream(Stream: TStream): Boolean;
  const
    MaxChunkSize: UInt64 = 1024;
  var
    Size, TempSize: UInt64;
    Buf: array[0..1023] of Byte;
    ChunkSize: Integer;
  begin
    Result := False;
    Size := Strm.Size - Strm.Position;
    TempSize := htonll(Size);
    if not DoSend(@TempSize, SizeOf(TempSize)) then Exit;
    while Size > 0 do
    begin
      ChunkSize := Integer(Min(Size, MaxChunkSize));
      Stream.ReadBuffer(buf[0], ChunkSize);
      if not DoSend(@buf[0], ChunkSize) then Exit;
      Dec(Size, ChunkSize);
    end;
    Result := True;
  end;

begin
  // NOTE: the DoSend...() functions above are written to operate in a blocking
  // manner, even if the socket is set to non-blocking mode!  If you truly want
  // to operate in a non-blocking manner, you need to handle the case where
  // SendBuf() reports a WSAEWOULDBLOCK error by stopping the sending immediately,
  // cache any unsent bytes, exit and let code flow return to the main message loop,
  // and wait for the TClientSocket.OnWrite event to fire before sending the cached
  // and subsequent bytes.  Repeat every time WSAEWOULDBLOCK is reported...

  try
    MS := TMemoryStream.Create;
    try
      J := TJpegImage.Create;
      try
        B := TBitmap.Create;
        try
          B.LoadFromFile('sent.bmp');
          J.Assign(B);
        finally
          B.Free;
        end;
        J.CompressionQuality := 100;
        J.SaveToStream(MS);
      finally
        J.Free;
      end;
      MS.Position := 0;
      //ShowMessage(IntToStr(Round(MS.Size / 1024)));
      Sent := DoSendStream(MS);
    finally
      MS.Free;
    end;
  finally
    Socket.Close;
  end;
  ShowMessage(BoolToStr(Sent));
end;

end.
unit Unit1;

interface

uses
  ... System.Win.ScktComp;

type
  TForm1 = class(TForm)
    Button1: TButton;
    ServerSocket1: TServerSocket;
    procedure ServerSocket1ClientConnect(Sender: TObject; Socket: TCustomWinSocket);
    procedure ServerSocket1ClientDisconnect(Sender: TObject; Socket: TCustomWinSocket);
    procedure ServerSocket1ClientRead(Sender: TObject; Socket: TCustomWinSocket);
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

uses
  Vcl.Imaging.jpeg, System.Math;

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
  ServerSocket1.Port := 1234;
  ServerSocket1.Active := True;
end;

type
  SocketState = (ReadingSize, ReadingData);
  TSocketHelper = class
  public
    Buffer: array[0..1023] of Byte;
    BufSize: Integer;
    ExpectedSize: UInt64;
    Stream: TMemoryStream;
    State: SocketState;
    constructor Create;
    destructor Destroy; override;
  end;

constructor TSocketHelper.Create;
begin
  BufSize := 0;
  ExpectedSize := 0;
  Stream := TMemoryStream.Create;
  State := ReadingSize;
end;

destructor TSocketHelper.Destroy;
begin
  Stream.Free;
  inherited;
end;

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

procedure TForm1.ServerSocket1ClientDisconnect(Sender: TObject; Socket: TCustomWinSocket);
begin
  TSocketHelper(Socket.Data).Free;
end;

procedure TForm1.ServerSocket1ClientRead(Sender: TObject; Socket: TCustomWinSocket);
var
  SH: TSocketHelper;
  jpg: TJpegImage;

  function ntohll(Value: UInt64): UInt64;
  var
    UL: Windows.ULARGE_INTEGER;
    L: UInt32;
  begin
    UL.QuadPart := Value;
    L := ntohl(UL.HighPart);
    LParts.HighPart := ntohl(UL.LowPart);
    LParts.LowPart := L;
    Result := UL.QuadPart;
  end;

begin
  SH := TSocketHelper(Socket.Data);
  repeat
    case SH.State of
      ReadingSize: begin
        while SH.BufSize < SizeOf(UInt64) do
        begin
          BytesReceived := Socket.ReceiveBuf(SH.Buffer[SH.BufSize], SizeOf(UInt64) - SH.BufSize);
          if BytesReceived <= 0 then Exit;
          Inc(SH.BufSize, BytesReceived);
        end;
        SH.ExpectedSize := ntohll(PUInt64(@SH.Buffer)^);
        SH.Data.Clear;
        SH.State := ReadingData;
        //ShowMessage(IntToStr(Round(SH.ExpectedSize / 1024)));
        Continue;
      end;

      ReadingData: begin
        while SH.ExpectedSize > 0 do
        begin
          BytesReceived := Socket.ReceiveBuf(SH.Buffer[0], Integer(Min(SH.ExpectedSize, SizeOf(SH.Buffer))));
          if BytesReceived <= 0 then Exit;
          Dec(SH.ExpectedSize, BytesReceived);
          SH.Data.WriteBuffer(SH.Buffer[0], BytesReceived);
        end;
        try
          jpg := TJpegImage.Create;
          try
            SH.Data.Position := 0;
            jpg.LoadFromStream(SH.Data);
            jpg.SaveToFile('received.jpg');
          finally
            jpg.Free;
          end;
        finally
          SH.Data.Clear;
          SH.BufSize := 0;
          SH.State := ReadingSize;
        end;
        Continue;
      end;
    end;
  until False;
end;

end.