Потеря данных в Delphi с использованием компонентов TIdTCPServer / TIdTCPClient

Я пишу клиент-серверное приложение с использованием инди-компонентов TIdTCPClient / TIdTcpServer в Delphi.

Передача данных обычно работает нормально, но часто я читаю неверные данные с сервера; Я получаю ответы на предыдущие запросы, а не на текущие.

Во время отладки оба приложения работают локально, поэтому данные не могут быть потеряны во время передачи.

Таймауты составляют 1000-3000 мсек, этого достаточно, чтобы избежать отправки второго запроса до получения ответа на первый.

Я использую простой формат данных: первые 4 байта - это длина пакета данных, остальные - двоичные данные этой длины.

Код на стороне сервера (упрощен для отправки только строк; я также использую двоичные буферы таким же образом, но этот код проще понять и проверить):

Var
  lng: LongInt;
  ib: TIdBytes;
begin
  // Prepare data to send:
  lng:=length(s);// s is an AnsiString to be sent
  SetLength(ib,lng+4);
  Move(lng,ib[0],4);
  Move(s[1],ib[4],length(s));
  // Send:
  AContext.Connection.IOHandler.WriteDirect(ib);
end;

Код на стороне клиента для отправки запроса такой же (вызов TIdTcpClient.IOHandler.WriteDirect () в последней строке). Клиентский код для чтения ответа сервера:

Var 
  ib: TIdBytes;
  size,done,lng: LongInt;
begin
  Result:=false;
  //  answer length:
  try
    SetLength(ib,0);
    tcp.IOHandler.ReadBytes(ib,4,false);
    Move(ib[0],size,4);
    if length(ib)<0 then Exit;// wrong data
  except
    on E: Exception do;// code skipped
  end;
  //  read answer body:
  done:=0;
  b.Clear;// b is my buffer, TStream descendant
  while done<size do
    begin
    lng:=Min(size-done,MaxBlockSize);
    // read:
    SetLength(ib,0);// to be sure
    tcp.IOHandler.ReadBytes(ib,lng,false);
    if length(ib)=0 then Exit;// error reading
    // append my buffer:
    b.Wr(ib[0],length(ib));
    // progress:
    Inc(done,length(ib));
    end;
end;

Порядок обмена данными:

  1. Клиент отправляет запрос на сервер,

  2. Сервер читает запрос и отправляет ответ клиенту,

  3. Клиент читает ответ.

На шаге 3 появляются неверные данные.

Может я что то вообще не так делаю?

Я пробовал ReadBytes () прямо перед отправкой запроса на сервер для очистки входящего буфера, но это тоже не помогает, как и многие другие вещи, которые я пробовал ...

Теперь у меня просто нет идей :(


person user3558897    schedule 16.01.2015    source источник
comment
Мне это кажется проблемой параллелизма. Не могли бы вы предоставить информацию о каком-либо варианте использования, когда несколько клиентов (на одном или нескольких клиентских машинах) достигают сервера? Как вам это удается? Множественные серверные потоки?   -  person mg30rg    schedule 16.01.2015
comment
Нет, ошибка возникает даже с одним локальным клиентом. Серверный код реализуется в событии OnExecute, поэтому, насколько я понимаю, он не может обрабатывать один клиентский запрос, пока другой клиентский запрос еще не получил ответа - есть только один, основной, поток.   -  person user3558897    schedule 16.01.2015
comment
TIdTCPServer - это многопоточный компонент. Каждый клиент работает в собственном рабочем потоке, а не в основном потоке. Событие OnExecute запускается в контексте этих рабочих потоков. Таким образом, он МОЖЕТ обрабатывать несколько клиентов параллельно, и поэтому вы должны убедиться, что ваш код обработчика событий является потокобезопасным.   -  person Remy Lebeau    schedule 16.01.2015
comment
Хорошо, я буду иметь это в виду, спасибо   -  person user3558897    schedule 19.01.2015


Ответы (1)


Ваша логика ввода-вывода намного сложнее, чем должна быть, особенно на стороне клиента. Вы вручную делаете то, что Indy может делать за вас автоматически.

На стороне клиента, поскольку вы сохраняете данные в TStream, вы можете заставить Indy считывать данные напрямую в TStream:

begin
  ...
  b.Clear;// b is my buffer, TStream descendant
  // ReadStream() can read a '<length><bytes>' formatted
  // message.  When its ASize parameter is -1 and its
  // AReadUntilDisconnect parameter is False, it reads
  // the first 4 or 8 bytes (depending on the LargeStream
  // property) and interprets them as the byte count,
  // in network byte order...
  tcp.IOHandler.RecvBufferSize := MaxBlockSize;
  tcp.IOHandler.LargeStream := False; // read 4-byte length
  // read answer:
  try
    tcp.IOHandler.ReadStream(b, -1, false);
  except
    on E: Exception do begin
      // the socket is now in an indeterminate state.
      // You do not know where the reading left off.
      // The only sensible thing to do is disconnect
      // and reconnect...
      tcp.Disconnect;
      ...
    end;
  end;
  ...
end;

На стороне сервера есть два разных способа отправить сообщение, совместимое с приведенным выше кодом:

var
  lng: LongInt;
  ib: TIdBytes;
begin
  // Prepare data to send:
  // s is an AnsiString to be sent
  lng := Length(s);
  SetLength(ib, lng);
  Move(PAnsiChar(s)^, PByte(ib)^, lng);
  // Send:
  AContext.Connection.IOHandler.Write(lng); // send 4-byte length, in network byte order
  AContext.Connection.IOHandler.Write(ib); // send bytes
end;

Or:

var
  strm: TIdMemoryBufferStream;
begin
  // Prepare data to send:
  // s is an AnsiString to be sent
  strm := TIdMemoryBufferStream.Create(PAnsiChar(s), Length(s));
  try
    // Send:
    // Write(TStream) can send a '<length><bytes>' formatted
    // message.  When its ASize parameter is 0, it sends the
    // entire stream, and when its AWriteByteCount parameter
    // is True, it first sends the byte count as 4 or 8 bytes
    // (depending on the LargeStream property), in network
    // byte order...
    AContext.Connection.IOHandler.LargeStream := False; // send 4-byte lengtb
    AContext.Connection.IOHandler.Write(strm, 0, True);
  finally
    strm.Free;
  end;
end;

Как видите, этот код отправляет сообщения того же типа, что и вы изначально, но изменился код, который управляет сообщениями. Кроме того, он принудительно отправляет счетчик байтов сообщения в сетевом порядке байтов, тогда как вы отправляли его в байтовом порядке хоста. Многобайтовые целые числа всегда следует отправлять в сетевом порядке байтов, когда это возможно, для согласованности и совместимости с несколькими платформами.

person Remy Lebeau    schedule 16.01.2015
comment
Спасибо, ваш код выглядит намного лучше :), я не знал, что Indy может обрабатывать пакеты ‹size› ‹length› самостоятельно. Похоже, это помогло решить большинство проблем, еще раз спасибо! - person user3558897; 19.01.2015