Indy写入缓冲/高效TCP通信

时间:2009-03-03 08:40:12

标签: delphi tcp performance client-server indy

我知道,我问了很多问题......但作为一名新的delphi开发人员,我不断讨论所有这些问题:)

这个使用indy 10处理TCP通信。为了使通信高效,我将客户端操作请求编码为单个字节(在大多数情况下,当然是其他数据字节,但在这种情况下只有一个字节)。问题是那个

var Bytes : TBytes;
...
SetLength (Bytes, 1);
Bytes [0] := OpCode;
FConnection.IOHandler.Write (Bytes, 1);
ErrorCode := Connection.IOHandler.ReadByte;

不立即发送该字节(至少不调用服务器执行处理程序)。如果我将'1'更改为'9',例如一切正常。我假设Indy缓冲了输出字节并试图用

禁用写缓冲
FConnection.IOHandler.WriteBufferClose;

但它没有帮助。如何发送单个字节并确保立即发送?并且 - 我在这里添加另一个小问题 - 使用indy发送整数的最佳方法是什么?不幸的是我在TIdTCPServer的IOHandler中找不到像WriteInteger这样的函数......而且

WriteLn (IntToStr (SomeIntVal))

对我来说似乎不是很有效率。我是否在一行中使用多个写命令或在字节数组中将事物打包在一起并发送一次会有所不同吗?

感谢您的回答!

编辑:我添加了一个提示,我正在使用Indy 10,因为在读写过程中似乎有重大变化。

3 个答案:

答案 0 :(得分:6)

默认情况下禁用写缓冲。您可以通过测试fConnection.IOHandler.WriteBufferingActive属性来检查写缓冲以查看它是否在您的代码中处于活动状态。

就发送整数的最佳方式而言......这取决于您的协议和总体目标。具体来说,使用FConnection.IOHandler.Write(),因为有重载方法可以写入任何类型的数据,包括整数。

取自IdIOHandler:

// Optimal Extra Methods
//
// These methods are based on the core methods. While they can be
// overridden, they are so simple that it is rare a more optimal method can
// be implemented. Because of this they are not overrideable.
//
//
// Write Methods
//
// Only the ones that have a hope of being better optimized in descendants
// have been marked virtual
procedure Write(const AOut: string; const AEncoding: TIdEncoding = enDefault); overload; virtual;
procedure WriteLn(const AEncoding: TIdEncoding = enDefault); overload;
procedure WriteLn(const AOut: string; const AEncoding: TIdEncoding = enDefault); overload; virtual;
procedure WriteLnRFC(const AOut: string = ''; const AEncoding: TIdEncoding = enDefault); virtual;
procedure Write(AValue: TStrings; AWriteLinesCount: Boolean = False; const AEncoding: TIdEncoding = enDefault); overload; virtual;
procedure Write(AValue: Byte); overload;
procedure Write(AValue: Char; const AEncoding: TIdEncoding = enDefault); overload;
procedure Write(AValue: LongWord; AConvert: Boolean = True); overload;
procedure Write(AValue: LongInt; AConvert: Boolean = True); overload;
procedure Write(AValue: SmallInt; AConvert: Boolean = True); overload;
procedure Write(AValue: Int64; AConvert: Boolean = True); overload;
procedure Write(AStream: TStream; ASize: Int64 = 0; AWriteByteCount: Boolean = False); overload; virtual;

你遇到的另一个问题是“我是否连续使用多个写命令或者在一个字节数组中将事物打包在一起并发送一次会有所不同吗?”对于大多数情况,是的,它会产生影响。对于高度紧张的服务器,您将不得不更多地参与如何来回发送字节,但在此级别,您应该将您的发送抽象为一个单独的协议类型类,该类构建要发送的数据并将其发送到突发并有一个接收协议接收一堆数据并将其作为一个完整的单元处理,而不是分解为发送/接收整数,字符,字节数组等。

作为一个非常粗略的简单例子:

TmyCommand = class(TmyProtocol)
private
  fCommand:Integer;
  fParameter:String;
  fDestinationID:String;
  fSourceID:String;
  fWhatever:Integer;
public
  property Command:Integer read fCommand write fCommand;
  ...

  function Serialize;
  procedure Deserialize(Packet:String);
end;

function TmyCommand.Serialize:String;
begin
  //you'll need to delimit these to break them apart on the other side
  result := AddItem(Command) + 
            AddItem(Parameter) + 
            AddItem(DestinationID) + 
            AddItem(SourceID) + 
            AddItem(Whatever);
end; 
procedure TMyCommand.Deserialize(Packet:String);
begin
   Command := StrToInt(StripOutItem(Packet));
   Parameter := StripOutItem(Packet);
   DesintationID := StripOutItem(Packet); 
   SourceID := StripOutItem(Packet);
   Whatever := StrToInt(StripOutItem(Packet));
end;

然后通过以下方式发送:

  FConnection.IOHandler.Write(myCommand.Serialize());

另一方面,您可以通过Indy接收数据,然后

  myCommand.Deserialize(ReceivedData);

答案 1 :(得分:4)

我不熟悉Indy,但您可能希望查看其API以获取TCP_NODELAY选项(您可能希望grey使用Indy源代码树这样的东西 - 对“延迟”不敏感的情况应该这样做。)

修改: Rob Kennedy 指出我所指的属性是 TIdIOHandlerSocket.UseNagle - 谢谢!

问题是TCP的本质所固有的。 TCP确保数据传输的顺序与发出的顺序相同,但保证消息边界。换句话说,源的,目标的操作系统和沿途的任何路由器可以自由地从连接合并分组或者随意分段它们。您必须将TCP传输视为流,作为一系列单独的数据包。因此,您必须实现一种机制,通过该机制您可以分隔单个消息(例如,通过魔术字节,如果消息数据中也可以出现,则必须转义),或者您可以发送以下消息的长度首先,然后是实际的消息。

当我需要发送消息边界很重要的消息时,我总是使用UDP和天真的ACK /重传方案,例如你的情况。可能想要考虑到这一点。 UDP更适合命令消息。

答案 2 :(得分:0)

听起来你必须冲洗你的缓冲区。试试这个:

TIdTCPConnection.FlushWriteBuffer;

如果您不想使用写缓冲区,请使用:

TIdTCPConnection.CancelWriteBuffer;

根据帮助,这首先调用ClearWriteBuffer,清除缓冲区,然后调用CloseWriteBuffer。

发送整数(使用Indy 10)的最佳方法是使用TIdIOHandler.Write(根据Indy 10帮助Write重载以处理不同类型的数据,包括整数)