Как я могу избежать зависания при записи в сокет в Indy, если другой конец не читает данные из сокета - PullRequest
2 голосов
/ 14 мая 2019

У меня есть приложение с клиентскими и серверными сокетами, использующее Indy, скомпилированное с Delphi 10.2.

Приложение имеет рабочий поток, который обрабатывает запросы из разных портов и записывает ответы в одном потоке, используя вызов чего-то вроде этого:

procedure TMyCommManager.WriteResponse(AHandler: TIdIOHandler; SomeData: SomeType);
var 
  idBytes: TidBytes;
  PacketSize: Integer;
begin
  SomeData.GetBytes(idBytes, PacketSize);
  AHandler.Write(DataBuffer, PacketSize);
end;

Почти все время все работаеткак и ожидалось, но мы заметили, что рабочий поток время от времени зависает на производстве.После различных итераций мы наконец дошли до того, что все это происходит при вызове TidIOHandler.Write(), и я совершенно уверен, что это происходит, потому что другой конец для одного порта не читает ответы из сокета.

После сброса на порт из другого конца рабочий поток размораживает и продолжает работать как положено.

Я нашел этот ответ от РемиЛебо на вопрос Delphi (Indy) Server Freezing при записи , где он упоминает в комментариях (выделено мое):

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

Я ищу правильный способ установки этого тайм-аута через INDY или через прямой вызов API в Windows, но я застрял в этомпоэтому я прихожу сюда в поисках помощи.

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

1 Ответ

4 голосов
/ 14 мая 2019

Я ищу правильный способ установки этого тайм-аута через INDY или через прямой вызов API в Windows

В моем предыдущем комментарии , когда я сказал "установить тайм-аут отправки на уровне сокета, используя API-интерфейс сокетов напрямую", я ссылался на параметр сокета SO_SNDTIMEO через setsockopt() функция.

С точки зрения Indy, вы можете вызвать setsockopt() с помощью метода TIdSocketHandle.SetSockOpt(), например:

// Windows

SomeConnection.Socket.Binding.SetSockOpt(Id_SOL_SOCKET, Id_SO_SNDTIMEO, TimeoutInMS);

// 'Nix

var tv: timeval;
...
SomeConnection.Socket.Binding.SetSockOpt(Id_SOL_SOCKET, Id_SO_SNDTIMEO, Integer(@timeval));

or:

GBSDStack.SetSocketOption(SomeConnection.Socket.Binding.Handle, Id_SOL_SOCKET, Id_SO_SNDTIMEO, timeval, sizeof(timeval));

(свойство TIdTCPConnection.Socket является сокращением для доступа к TIdIOHandlerSocket, когда TIdTCPConnection.IOHandler был присвоен TIdIOHandlerSocket или потомок).

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

...