Indy Write Buffering / Эффективная связь по TCP - PullRequest
8 голосов
/ 03 марта 2009

Я знаю, я задаю много вопросов ... но, как новый разработчик Delphi, я продолжаю задумываться над всеми этими вопросами:)

В данном случае речь идет о связи TCP с использованием indy 10. Чтобы сделать связь эффективной, я кодирую запрос операции клиента в виде одного байта (в большинстве сценариев, конечно, за ним следуют другие байты данных, но в этом случае только один единственный байт). Проблема в том, что

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? К сожалению, я не могу найти функцию наподобие WriteInteger в IOHandler TIdTCPServer ... и

WriteLn (IntToStr (SomeIntVal))

кажется не очень эффективным для меня. Имеет ли значение, использую ли я несколько команд записи в строке или упаковываю вещи в байтовый массив и отправляю это один раз?

Спасибо за любые ответы!

РЕДАКТИРОВАТЬ: я добавил подсказку, что я использую Indy 10, так как, кажется, есть серьезные изменения, касающиеся процедур чтения и записи.

Ответы [ 3 ]

6 голосов
/ 04 марта 2009

Буферизация записи отключена по умолчанию. Вы можете проверить буферизацию записи, чтобы увидеть, активна ли она в вашем коде, проверив свойство 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);
4 голосов
/ 03 марта 2009

Я не знаком с Indy, но вы, возможно, захотите просмотреть его API для опции TCP_NODELAY (вы можете захотеть использовать grep для исходного дерева Indy для чего-то подобного - регистронезависимый для «delay» должен это сделать)

Редактировать: Роб Кеннеди указал, что свойство, о котором я говорил, TIdIOHandlerSocket.UseNagle - спасибо!

Проблема присуща природе TCP. TCP гарантирует доставку данных в том же порядке, в котором они были отправлены, но не гарантирует границы сообщений. Другими словами, операционная система источника, цели и любых маршрутизаторов на этом пути может свободно объединять пакеты из соединения или фрагментировать их по желанию. Вы должны рассматривать передачу TCP как поток, , а не как серию отдельных пакетов. Таким образом, вам придется реализовать механизм, с помощью которого вы будете либо разграничивать отдельные сообщения (например, с помощью магического байта, который вы должны экранировать, если это также может происходить в данных вашего сообщения), либо вы можете отправить длину следующего сообщения сначала фактическое сообщение.

Я всегда использовал UDP в сочетании с наивной схемой ACK / повторной передачи, когда мне нужно было отправлять сообщения, где важна граница сообщения, как, например, в вашем случае. Возможно, хочу принять это во внимание. UDP намного лучше подходит для командных сообщений.

0 голосов
/ 03 марта 2009

Похоже, вы должны очистить буфер. Попробуйте это:

TIdTCPConnection.FlushWriteBuffer;

Если вам не нужен буфер записи, используйте это:

TIdTCPConnection.CancelWriteBuffer;

Согласно справке, сначала вызывается ClearWriteBuffer, чтобы очистить буфер, а затем вызывается CloseWriteBuffer.

Лучший способ отправить целое число (используя Indy 10) - использовать TIdIOHandler.Write (согласно справке Indy 10, запись перегружена для обработки различных типов данных, включая целые числа)

...