Indy 写缓冲/高效 TCP 通信

发布于 2024-07-14 20:31:27 字数 830 浏览 5 评论 0原文

我知道,我问了很多问题...但是作为一名新的 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,因为读写过程似乎有重大变化。

I know, I'm asking a lot of questions...but as a new delphi developer I keep falling over all these questions :)

This one deals with TCP communication using indy 10. To make communication efficient, I code a client operation request as a single byte (in most scenarios followed by other data bytes of course, but in this case only one single byte). Problem is that

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

does not send that byte immediately (at least the servers execute handler is not invoked). If I change the '1' to a '9' for example everything works fine. I assumed that Indy buffers the outgoing bytes and tried to disable write buffering with

FConnection.IOHandler.WriteBufferClose;

but it did not help. How can I send a single byte and make sure that it is immediatly sent? And - I add another little question here - what is the best way to send an integer using indy? Unfortunately I can't find function like WriteInteger in the IOHandler of TIdTCPServer...and

WriteLn (IntToStr (SomeIntVal))

seems not very efficient to me. Does it make a difference whether I use multiple write commands in a row or pack things together in a byte array and send that once?

Thanks for any answers!

EDIT: I added a hint that I'm using Indy 10 since there seem to be major changes concerning the read and write procedures.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

妥活 2024-07-21 20:31:27

默认情况下禁用写缓冲。 您可以通过测试 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);

Write buffering is disabled by default. You can check write buffering to see if it's active in your code by testing the fConnection.IOHandler.WriteBufferingActive property.

As far as the best way to send an integer... 'it depends' on your protocol and overall goals. Specifically, use FConnection.IOHandler.Write() as there are overloaded methods to write just about any type of data, including an integer.

Taken from 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;

Another question you had was "Does it make a difference whether I use multiple write commands in a row or pack things together in a byte array and send that once?" For the majority of cases, yes it makes a difference. For highly stressed servers you are going to have to get more involved in how bytes are sent back and forth, but at this level you should abstract out your sends into a separate protocol type class that builds the data to be sent and sends it in a burst and have a receiving protocol that receives a bunch of data and processes it as a complete unit instead of breaking things down to sending/receiving an integer, character, byte array, etc..

As a very rough quick example:

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;

Then send this via:

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

On the other side you can receive the data via Indy and then

  myCommand.Deserialize(ReceivedData);
扮仙女 2024-07-21 20:31:27

我不熟悉 Indy,但您可能想查看其 API 中的 TCP_NODELAY 选项(您可能想 grep Indy 源代码树以获取类似的内容 - 不区分大小写的“延迟”应该可以做到这一点。)

编辑:< strong>Rob Kennedy 指出我所指的属性是 TIdIOHandlerSocket.UseNagle - 谢谢!

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

当我需要发送消息边界很重要的消息时(例如您的情况),我总是使用 UDP 与简单的 ACK/重传方案相结合。 可能需要考虑到这一点。 UDP 更适合命令消息。

I'm not familiar with Indy, but you might want to look around its API for a TCP_NODELAY option (you might want to grep the Indy source tree for something like that - case insensitive for "delay" should do it.)

Edit: Rob Kennedy pointed out that the property I was referring to is TIdIOHandlerSocket.UseNagle - thanks!

The problem is inherent in the nature of TCP. TCP does guarantee data delivery in the same order as it was emitted but does not guarantee message boundaries. In other words, the operating system of the source, of the target, and any routers along the way are free to coalesce packets from the connection or to fragment them at will. You must look at a TCP transmission as a stream, not as a series of individual packets. Thus you will have to implement a mechanism by which you either delimit the individual messages (by a magic byte, for example, which you must escape if it can also occur in your message data), or you could send the length of the following message first, then the actual message.

I've always used UDP coupled with a naive ACK/retransmission scheme when I needed to send messages where the message boundary was important, such as is your case. Might want to take that into account. UDP is much better suited for command messages.

诠释孤独 2024-07-21 20:31:27

听起来你必须刷新缓冲区。 试试这个:

TIdTCPConnection.FlushWriteBuffer;

如果你不需要写缓冲区,请使用这个:

TIdTCPConnection.CancelWriteBuffer;

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

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

Sounds like you have to flush your buffer. Try this:

TIdTCPConnection.FlushWriteBuffer;

If you don't want a write buffer, use this:

TIdTCPConnection.CancelWriteBuffer;

According to the help, this first calls ClearWriteBuffer, to clear the buffer and then calls CloseWriteBuffer.

The best way to send an integer (using Indy 10) is using TIdIOHandler.Write (according to Indy 10 help Write is overloaded to handle different kinds of data, including Integers)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文