Буферизация записи Indy / эффективная связь TCP


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

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

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

Не отправляет этот байт сразу (по крайней мере, обработчик выполнения серверов не вызывается). Если я измените "1" на "9", например, все работает нормально. Я предположил, что инди буферизует исходящие байты и попытался отключить буферизацию записи с помощью

FConnection.IOHandler.WriteBufferClose;

Но это не помогло. Как я могу отправить один байт и убедиться, что он немедленно отправлен? И - я добавлю здесь еще один маленький вопрос - как лучше всего отправить целое число с помощью indy? К сожалению, я не могу найти функцию, как WriteInteger в IOHandler TIdTCPServer...и

WriteLn (IntToStr (SomeIntVal))
Мне кажется, что это не очень эффективно. Имеет ли значение, использую ли я несколько команд записи в строке или упакую вещи вместе в массив байтов и отправлю их один раз?

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

EDIT: я добавил намек на то, что использую Indy 10, поскольку, похоже, есть серьезные изменения, касающиеся процедур чтения и записи.

3 7

3 ответа:

По умолчанию буферизация записи отключена. Вы можете проверить буферизацию записи, чтобы увидеть, активна ли она в вашем коде, Протестировав fConnection.IOHandler.Свойство WriteBufferingActive.

Насколько это лучший способ отправить целое число... "это зависит" от вашего протокола и общих целей. В частности, используйте функцию FConnection.IOHandler.Write (), поскольку существуют перегруженные методы для записи практически любого типа данных, включая целое число.

Взято из Идиохандлера:

// 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);

Я не знаком с Indy, но вы можете поискать в его API опцию TCP_NODELAY (вы можете использовать дерево исходных текстов Indy для чего - то подобного-регистр нечувствителен к "задержке".)

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

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

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

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

TIdTCPConnection.FlushWriteBuffer;

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

TIdTCPConnection.CancelWriteBuffer;

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

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