【问题标题】:Sending and receiving a TMemoryStream using IdTCPClient and IdTCPServer使用 IdTCPClient 和 IdTCPServer 发送和接收 TMemoryStream
【发布时间】:2012-08-28 13:03:04
【问题描述】:

我找到了 Remy Lebeau 的 XE2 中 IdTCP 组件的聊天演示,我想玩一下它。 (可以找到here) 我想使用这些组件发送图片,最好的方法似乎是使用 TMemoryStream。如果我发送字符串,则连接工作正常,字符串传输成功,但是当我将其更改为 Stream 时,它不起作用。代码如下:

服务器

procedure TMainForm.IdTCPServerExecute(AContext: TIdContext);
var rcvdMsg: string;
  ms:TMemoryStream;
begin
// This commented code is working, it receives and sends strings.
//  rcvdMsg:=AContext.Connection.IOHandler.ReadLn;
//  LogMessage('<ServerExec> '+rcvdMsg);
//
//  TResponseSync.SendResponse(AContext, rcvdMsg);
  try
    ms:=TMemoryStream.Create;
    AContext.Connection.IOHandler.ReadStream(ms);

    ms.SaveToFile('c:\networked.bmp');
  except
    LogMessage('Failed to receive',clred);
  end;
end;

客户

procedure TfrmMain.Button1Click(Sender: TObject);
var ms: TMemoryStream;
    bmp: TBitmap;
    pic: TPicture;
    s: string;
begin
  // Again, this code is working for sending strings.
  // s:=edMsg.Text;
  // Client.IOHandler.WriteLn(s);
  ms:=TMemoryStream.Create;

  pic:=TPicture.Create;
  pic.LoadFromFile('c:\Back.png');

  bmp:=TBitmap.Create;
  bmp.Width:=pic.Width;
  bmp.Height:=pic.Height;
  bmp.Canvas.Draw(0,0,pic.Graphic);

  bmp.SaveToStream(ms);
  ms.Position:=0;
  Client.IOHandler.Write(ms);
  ms.Free;
end;

当我尝试从客户端发送流时,没有观察到任何事情发生(OnExecute 中的断点不会触发)。但是,当关闭程序时(发送 MemoryStream 之后),会发生两件事:

  • 如果客户端先关闭,只有这样except部分才会被处理(日志显示'接收失败'错误。但是,即使我在try-except块的第一行放置了一个断点,它以某种方式被跳过,只显示错误)。
  • 如果服务器首先关闭,IDE 不会从 debug 变回,客户端不会将其状态更改为 disconnected(就像在服务器断开连接),并且在客户端也关闭后,会出现来自服务器应用程序的访问冲突错误。我想这意味着服务器的一个线程仍在运行并维护连接。但无论我给它多少时间,它都无法完成接收 MemoryStream 的任务。

注意:服务器使用IdSchedulerOfThreadDefaultIdAntiFreeze,如果这很重要的话。

由于我找不到任何可靠的帮助来源来改进 Indy 10(这一切似乎都适用于旧的 Indy 10,甚至是 Indy 9),我希望你能告诉我哪里出了问题。谢谢


- 答案-

服务器

procedure TMainForm.IdTCPServerExecute(AContext: TIdContext);
var size: integer;
    ms:TMemoryStream;
begin
  try
    ms:=TMemoryStream.Create;
    size:=AContext.Connection.IOHandler.ReadLongInt;
    AContext.Connection.IOHandler.ReadStream(ms, size);

    ms.SaveToFile('c:\networked.bmp');
  except
    LogMessage('Failed to receive',clred);
  end;
end;

客户

procedure TfrmMain.Button1Click(Sender: TObject);
var ms: TMemoryStream;
    bmp: TBitmap;
    pic: TPicture;
begin
  ms:=TMemoryStream.Create;

  pic:=TPicture.Create;
  pic.LoadFromFile('c:\Back.png');

  bmp:=TBitmap.Create;
  bmp.Width:=pic.Width;
  bmp.Height:=pic.Height;
  bmp.Canvas.Draw(0,0,pic.Graphic);

  bmp.SaveToStream(ms);
  ms.Position:=0;
  Client.IOHandler.Write(ms, 0, True);
  ms.Free;
end;

【问题讨论】:

  • 首先发送流的大小,以便服务器确切知道它需要读取多少字节。

标签: delphi tcp indy


【解决方案1】:

只要使用正确的参数:

.IOHandler.Write(ms, 0, true); //true ... indicates WriteByteCount

.IOHandler.ReadStream(ms, -1); //-1 ... use ByteCount

【讨论】:

  • 甚至比我的解决方案更好:)
  • @whosrdaddy:实际上是一样的,只是已经包含了这个功能。
  • 太棒了,它现在甚至无需断开和重新连接客户端即可工作。为了完整性,我会更新我的问题。因为你们两个的答案几乎一样,我不知道该给哪个接受的答案..我会给出较短的版本,但仍然感谢,谁srdaddy
  • 仅供参考,AByteCountAReadUntilDisconnect 参数的组合决定了 ReadStream() 是否需要前面的字节数。它本身不是AByteCount 参数。只有当AByteCount=-1 AReadUntilDisconnect=FalseReadStream() 期望前面的字节数。这些是两个参数的默认值。
【解决方案2】:

预先发送流的大小,以便服务器知道它必须读取多少字节。

服务器:

procedure TMainForm.IdTCPServerExecute(AContext: TIdContext);
var rcvdMsg: string;
  ms:TMemoryStream;
  size : integer;
begin
// This commented code is working, it receives and sends strings.
//  rcvdMsg:=AContext.Connection.IOHandler.ReadLn;
//  LogMessage('<ServerExec> '+rcvdMsg);
//
//  TResponseSync.SendResponse(AContext, rcvdMsg);
  try
    ms:=TMemoryStream.Create;
    try 
     size := AContext.Connection.IOHandler.ReadLongint; 
     AContext.Connection.IOHandler.ReadStream(ms, size, false);
     ms.SaveToFile('c:\networked.bmp');
    finally
     ms.Free;
    end;        
  except
    LogMessage('Failed to receive',clred);
  end;
end;

客户端(固定资源处理)

procedure TfrmMain.Button1Click(Sender: TObject);
var ms: TMemoryStream;
    bmp: TBitmap;
    pic: TPicture;
    s: string;
begin
  // Again, this code is working for sending strings.
  // s:=edMsg.Text;
  // Client.IOHandler.WriteLn(s);
  ms:=TMemoryStream.Create;
  pic:=TPicture.Create;
  bmp:=TBitmap.Create;
  try
   pic.LoadFromFile('c:\Back.png');   
   bmp.Width:=pic.Width;
   bmp.Height:=pic.Height;
   bmp.Canvas.Draw(0,0,pic.Graphic);
   bmp.SaveToStream(ms);
   ms.Position:=0;
   Client.IOHandler.Write(ms.Size);
   Client.IOHandler.Write(ms);
  finally
   ms.Free;
   bmp.Free;
   pic.Free;
  end;
end;

【讨论】:

  • 谢谢,它在某种程度上有效。两个应用程序仍然“卡住”,只有在退出客户端后服务器才会写入“接收失败”错误。但是尽管出现了这个错误,但图片 正确保存在硬盘上。所以看起来传输完成得很好,但由于某种原因没有结束
  • 服务器尝试在执行循环中接收下一张图片。发送图片后只需断开客户端即可解决问题,因为服务器会注意到关闭的套接字并停止。
  • 这真的是正确的方法吗?我的意思是,我可能会发送更多图片,只是根据客户的需求,所以一直连接和断开连接对我来说似乎有点奇怪。无论如何,OnExecute 应该只在数据到达服务器时触发,它不是循环,对吧?
  • @MartinMelka 不会在 Execute 处理程序中吞下异常,因为 Indy 使用异常来检测断开连接。这解释了为什么服务器会卡住。顺便说一句,在不断开连接的情况下循环发送图片可以很好地使用上面的代码。
  • 是的,我注意到一行错误的代码,它现在可以工作了。这是否意味着在使用 Indy 时,我不应该使用 try-except 块?
猜你喜欢
  • 2020-03-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-02-21
  • 1970-01-01
  • 2013-11-13
  • 2020-04-14
相关资源
最近更新 更多