Главная страница
Top.Mail.Ru    Яндекс.Метрика
Текущий архив: 2008.02.24;
Скачать: CL | DM;

Вниз

Винет поток на функции Socket().   Найти похожие ветки 

 
DVM ©   (2007-06-04 11:02) [0]

Ситуация такая:

Есть кнопка, при нажатии на которую создается поток, выполняющий HTTP запрос и самоликвидирующийся после выполнения. Все вобщем работает нормально, если кнопку нажимать не очень часто. Если кнопку нажимать очень часто, то спустя некоторое время (100-150 нажатий), потоки продолжают создаваться, но уже не завершаются, т.к. виснут на вызове функции Socket(). В чем может быть проблема?
Вторичные потоки никак не взаиможействуют с главным после запуска, все параметры передаются в них при старте. Друг с другом тоже не взаимодействуют.


 
Сергей М. ©   (2007-06-04 11:21) [1]


> поток..самоликвидирующийся после выполнения


Поток не может "самоликвидироваться".

"Ликвидация" объекта-нити  - это закрытие всех хэндлов, ассоциированных с этим объектом.


 
SpellCaster   (2007-06-04 11:40) [2]

> Поток не может "самоликвидироваться".

А как же вызов Terminate в методе Execute с FreeOnTerminate=False?


 
clickmaker ©   (2007-06-04 11:42) [3]


> [2] SpellCaster   (04.06.07 11:40)

procedure TThread.Terminate;
begin
 FTerminated := True;
end;

;)


 
Сергей М. ©   (2007-06-04 11:46) [4]


> SpellCaster   (04.06.07 11:40) [2]


А причем здесь это ?

Подозреваю, что речь не идет о TThread.

А в этом случае автор должен сам позаботиться о закрытии хэндлов.


 
DVM ©   (2007-06-04 12:02) [5]


> Подозреваю, что речь не идет о TThread.

Нет здесь используется именно TThread c флагом FreeOnTerminate=true

Но дело не в потоке. Его выполнение останавливается на Socket()


 
Сергей М. ©   (2007-06-04 12:07) [6]


> используется именно TThread


Какой резон использовать VCL (хотя бы в части задействования класса TThread), если работа с Winsock организована непосредственно на WinsockAPI ?

Поясни ..


> выполнение останавливается на Socket


Как определил ?

Приводи код TThread ..


 
DVM ©   (2007-06-04 12:16) [7]


> Какой резон использовать VCL (хотя бы в части задействования
> класса TThread), если работа с Winsock организована непосредственно
> на WinsockAPI ?

Да собственно, особенного резона, конечно нет. С оберткой немного удобнее.


> Как определил ?

Элементарно поставил Windows.Beep(500,50); сначала до, потом после строки этой (Socket()) - до всегда пикает, после с некоторого момента пикать перестает.


> Приводи код TThread ..


Это метод Execute:


procedure TCommandThread.Execute;
var
 Wsa: TWSADATA;
 WsaErr: integer;
 Res: integer;
begin
 FreeOnTerminate := true;
 WsaErr := WSAStartUp($0101, Wsa);
 try
   if WsaErr = 0 then
     begin
       Init;
       FSock := SocketConnect();
       try
         if FSock <> -1 then
           begin
             Res := SendRequest(FSock, FRequest);
             if Res = 0 then ReadData(FBuffer, 0);
           end
       finally
         SocketDisconnect();
       end;
     end;
 finally
   WSACleanUp;
 end;
end;


Это то место где коннект:


function TCommandThread.SocketConnect: integer;
var
 Len: integer;
 Block: Cardinal;
 Wfd: TFDSet;
 TimeVal: TTimeVal;
 BeginConnectTime: Cardinal;
begin  
 Result := socket(AF_INET, SOCK_STREAM, 0);

 if Result = INVALID_SOCKET then
   begin
     Result := -1;  
     exit;
   end;

 Block := 1;
 if ioctlsocket(Result, FIONBIO, Block) = SOCKET_ERROR then
   begin
     CloseSocket(Result);
     Result := -1;
     exit;
   end;

 Len := SizeOf(FAddr);
 TimeVal.tv_sec := 0;
 TimeVal.tv_usec := 100;
 FD_ZERO(wfd);
 FD_SET(result, wfd);

 if Connect(Result, @FAddr, Len) = SOCKET_ERROR then
   begin
     
     if WSAGetLastError =  WSAEWOULDBLOCK then
       begin
         BeginConnectTime := GetTickCount;
         while (not Terminated) and ((GetTickCount - BeginConnectTime) <= 5000) do
           begin
             case select(Result, nil, @wfd, nil, @TimeVal) of
               0:
                 begin
                   FD_ZERO(wfd);
                   FD_SET(result, wfd);
                   delay(50);
                 end;
               1:
                 if FD_ISSET(Result, wfd) then break;
               SOCKET_ERROR:
                 begin
                   Block := 0;
                   ioctlsocket(Result, FIONBIO, Block);
                   CloseSocket(Result);
                   Result := -1;
                 end;
             end;
             
           end
        end
      else
        begin
          CloseSocket(Result);
          Result := -1;
        end;
   end;

 Block := 0;
 if ioctlsocket(Result, FIONBIO, Block) = SOCKET_ERROR then
   begin
     CloseSocket(Result);
     Result := -1;
     exit;
   end;
end;


 
Сергей М. ©   (2007-06-04 12:29) [8]

Куча серьезных замечаний по коду.

Ну да это не столь важно пока.

Пробуй вот такой код:

..
try
 FSock := SocketConnect();
except
 on e: Exception do begin
   MessageBox(0, PChar(e.Classname + " " + e.Message), "", MB_OK or MB_SETFOREGROUND);
   raise;
 end;
end;
..

Изменения в поведении программы при этом наблюдаются ?


 
DVM ©   (2007-06-04 12:42) [9]


> Сергей М. ©   (04.06.07 12:29) [8]

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


> Куча серьезных замечаний по коду.

Каких?


 
Сергей М. ©   (2007-06-04 12:59) [10]

1. Зачем вызывать SocketDisconnect() безусловно, если предшествующий SocketConnect() может вернуть отказ ?

2. Зачем возвращать гнездо в блок.режим перед его закрытием ?

3. Зачем нужен неблокирующий коннект, если все остальные операции с гнездом  - блокирующие ?


 
DVM ©   (2007-06-04 13:08) [11]


> 1. Зачем вызывать SocketDisconnect() безусловно, если предшествующий
> SocketConnect() может вернуть отказ ?

Согласен


> 2. Зачем возвращать гнездо в блок.режим перед его закрытием
> ?

Согласен, правда этого куска раньше не было, я в поисках ошибки вставил его и потом забыл.


> 3. Зачем нужен неблокирующий коннект, если все остальные
> операции с гнездом  - блокирующие ?

А вот зачем. Если поток висит на Connect() в случае блокирующих сокетов (например когда узел недоступен), то завершение такого потока невозможно до того как выйдет таймаут. А это долго (секунд 10-15). Чтобы быстро прервать Connect() я и превожу сокеты в неблокирующий режим. Далее работаю с блокирующими, т.к. это проще.


 
DVM ©   (2007-06-04 13:14) [12]

Кстати, по поводу


> ..
> try
>  FSock := SocketConnect();
> except
>  on e: Exception do begin
>    MessageBox(0, PChar(e.Classname + " " + e.Message), "",
>  MB_OK or MB_SETFOREGROUND);
>    raise;
>  end;
> end;
> ..

:

Я пробовал вчера делать так:

try
FSock := SocketConnect();
except
 Windows.Beep(...);
end;

Тишина была.


 
Сергей М. ©   (2007-06-04 13:16) [13]


> А это долго (секунд 10-15)


А ты куда-то торопишься ?

Если торопишься, то зачем тогда delay(50) ? Это же лишняя бестолковая задержка !


> например когда узел недоступен


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


 
Сергей М. ©   (2007-06-04 13:18) [14]


> пробовал вчера делать так:
>
> try
> FSock := SocketConnect();
> except
>  Windows.Beep(...);
> end;


Странная у тебя методика отладки) ...

Дилетантством отлает .. А вроде бы ты и не новичок, чтобы бипами контролировать работу своего кода ..


 
DVM ©   (2007-06-04 13:22) [15]


> Если торопишься, то зачем тогда delay(50) ? Это же лишняя
> бестолковая задержка !

Может и лишняя строка, у меня у самого сомнения были на счет нее. Просто подумал, зачем так часто опрашивать select() - грузить систему. 50 мсек - это же совсем мало.


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

Допустим я посылаю запрос узлу 192.168.0.20, собственный адрес 192.168.0.10 - connect() блокирует поток на 20 секунд где то. Попробуйте сами - положите на форму компонент от инди и кнопку и выполните запрос:


IdHTTP1.Get("http://192.168.0.21");


Если узла такого нет, то у меня блокировка на 20 секунд возникает


 
DVM ©   (2007-06-04 13:25) [16]


> Странная у тебя методика отладки) ...
>
> Дилетантством отлает .. А вроде бы ты и не новичок, чтобы
> бипами контролировать работу своего кода ..

Да я не всегда бипами. Просто так оно быстрее писать иногда, а эффект тот же. :) Если бип прошел или не прошел, тогда уж по обстоятельствам смотрю. Может это и дилетантство.


 
Сергей М. ©   (2007-06-04 13:35) [17]


> подумал, зачем так часто опрашивать select()


А действительно, зачем опрашивать вообще ?

Есть же неблок.режим с асинхронными нотификациями (WSAAsyncSelect, WSAEventSelect) ..


 
DVM ©   (2007-06-04 13:42) [18]


> Есть же неблок.режим с асинхронными нотификациями

Я привык к блокирущим сокетам. Неблокирующий режим немного недолюбливаю и потому не использую без особой нужды. Хотя, наверное, я не прав.


 
SpellCaster   (2007-06-04 17:30) [19]

> [3] clickmaker ©   (04.06.07 11:42)

Я в курсе )). Но поскольку FTerminated приватная, это единственный способ нормально завершить поток...


 
Сергей М. ©   (2007-06-04 17:57) [20]


> SpellCaster   (04.06.07 17:30) [19]


Ничто не обязывает программиста проверять флаг Terminated в теле метода Execute.


 
DVM ©   (2007-06-04 23:26) [21]


> Сергей М. ©   (04.06.07 12:29) [8]

Последовал Вашему совету, вставил указанный код. На домашнем компьютере вылазит следующее:

EAccessViolation AccessViolation at address 7c80979d in module kernel32.dll. Write of address 00c35298.

Что это может быть? На рабочем компьютере так и не добился этой ошибки.


 
DVM ©   (2007-06-04 23:36) [22]

Двигая try...except по коду - выявил меcто исключения:


function TCommandThread.SocketConnect: integer;
var
 Len: integer;
 Block: Integer;
 Wfd: TFDSet;
 TimeVal: TTimeVal;
 BeginConnectTime: Cardinal;
begin
 Result := socket(AF_INET, SOCK_STREAM, 0);
 if Result = INVALID_SOCKET then
   begin
     Result := -1;
     exit;
   end;
 Block := 1;
 if ioctlsocket(Result, FIONBIO, Block) = SOCKET_ERROR then
   begin
     CloseSocket(Result);
     Result := -1;
     exit;
   end;
 Len := SizeOf(FAddr);
 TimeVal.tv_sec := 0;
 TimeVal.tv_usec := 100;
 FD_ZERO(wfd);
 FD_SET(result, wfd);

 try
   Connect(Result, fAddr, Len) ;  ///////// тут!
 except
 on e: Exception do
   begin
     MessageBox(0, PChar(e.Classname + " " + e.Message), "", MB_OK or MB_SETFOREGROUND);
     raise;
   end;
 end;

// if Connect(Result, FAddr, Len) = SOCKET_ERROR then
   begin

     if WSAGetLastError =  WSAEWOULDBLOCK then
       begin
         BeginConnectTime := GetTickCount;
         while (not Terminated) and ((GetTickCount - BeginConnectTime) <= 5000) do
           begin
             case select(Result, nil, @wfd, nil, @TimeVal) of
               0:
                 begin
                   FD_ZERO(wfd);
                   FD_SET(result, wfd);
                   delay(50);
                 end;
               1:
                 if FD_ISSET(Result, wfd) then break;
               SOCKET_ERROR:
                 begin
                   Block := 0;
                   ioctlsocket(Result, FIONBIO, Block);
                   CloseSocket(Result);
                   Result := -1;
                 end;
             end;

           end
        end
      else
        begin
          CloseSocket(Result);
          Result := -1;
        end;

   end;
 Block := 0;
 if ioctlsocket(Result, FIONBIO, Block) = SOCKET_ERROR then
   begin
     CloseSocket(Result);
     Result := -1;
     exit;
   end;
end;


 
Сергей М. ©   (2007-06-05 11:05) [23]

И что же это за исключение ?


 
DVM ©   (2007-06-05 12:46) [24]


> И что же это за исключение ?

EAccessViolation AccessViolation at address 7c80979d in module kernel32.dll. Write of address 00c35298.

Только мне что-то непонятна причина его возникновения.


 
Сергей М. ©   (2007-06-05 12:55) [25]


> мне что-то непонятна причина его возникновения


Похоже где-то ты гадишь содержимое переменной FAddr


 
Сергей М. ©   (2007-06-05 12:57) [26]

"Криминал" вполне может твориться в SendRequest и ReadData


 
DVM ©   (2007-06-05 13:09) [27]


> Сергей М. ©   (05.06.07 12:57) [26]


Да я вроде FAddr не касаюсь больше нигде.


function TCommandThread.SendRequest(ASock: integer; ARequest: string): integer;
var
 ReturnCode: integer;
begin
 ReturnCode := send(ASock, Pointer(ARequest)^, Length(ARequest), 0);
 if ReturnCode < 0 then
   begin
     Result := -1;
     SocketDisconnect();
   end
 else
   begin
     Result := 0;
   end;
end;

function TCommandThread.ReadData(ABuffer: TBuffer; BytesExpected: integer): integer;
const
 MaxLen = 262144;
var
 TotalBytesToRead, Found, TotalBytesRead, BytesToRead, BytesRead: integer;
 Rfds: TFDSet;
 TempBuff: array [0..Pred(MaxLen)] of Char;
begin
 FD_ZERO(Rfds);
 FD_SET(FSock, Rfds);
 Found := select(FSock, @Rfds, nil, nil, @FTimeout);
 if Found = 0 then
   begin
     Result := -1;
     exit;
   end
 else
   if Found = SOCKET_ERROR then
     begin
       Result := -1;
       exit;
     end;
 TotalBytesToRead := 0;
 if BytesExpected <> 0 then
   begin
     TotalBytesToRead := BytesExpected;
   end
 else
   begin
     if ioctlsocket(FSock, FIONREAD, TotalBytesToRead) = SOCKET_ERROR then
       begin
         Result := -1;
         exit;
       end;
     if TotalBytesToRead = 0  then
       begin
         SocketDisconnect();
         Result := 0;
         exit;
       end;
   end;
 TotalBytesRead := 0;
 repeat
   if TotalBytesToRead > MaxLen then
     BytesToRead := MaxLen
   else
     BytesToRead := TotalBytesToRead;
   ZeroMemory(@TempBuff[0], MaxLen);
   BytesRead := recv(FSock, TempBuff, BytesToRead, 0);
   if BytesRead = SOCKET_ERROR then
     begin
       SocketDisconnect();
       Result := -1;
       exit;
     end
   else
     if BytesRead = 0 then
       begin
         SocketDisconnect();
         Result := 0;
         exit;
       end
     else
       begin
         if ABuffer.Size >= 2097152 then
           begin
             SocketDisconnect();
             Result := -1;
             exit;
           end;
         ABuffer.Append(@TempBuff[0], BytesRead);
         TotalBytesRead := TotalBytesRead + BytesRead;
         TotalBytesToRead := TotalBytesToRead - BytesRead;
       end;
 until (TotalBytesToRead <= 0) or Terminated;
 Result := TotalBytesRead;
end;



 
DVM ©   (2007-06-05 13:15) [28]

Да, и ведь глюк появляется на домашнем компьютере исключительно. На рабочем не появляется. Может с системой что не так?


 
Сергей М. ©   (2007-06-05 13:26) [29]


> Да я вроде FAddr не касаюсь больше нигде


А ты контролировал содержимое FAddr в момент исключения ?
Нет, не контролировал.


> if ioctlsocket(FSock, FIONREAD, TotalBytesToRead) = SOCKET_ERROR then


После успешного вызова ioctlsocket переменная TotalBytesToRead может содержать любое значение в диапазоне [0.. полный размер данных ответа на запрос].
Задумайся над этим.


 
DVM ©   (2007-06-05 14:49) [30]


> После успешного вызова ioctlsocket переменная TotalBytesToRead
> может содержать любое значение в диапазоне [0.. полный размер
> данных ответа на запрос].
> Задумайся над этим.

Я знаю. Ответ приходит порциями, естественно. Назначение функции ReadData() - считать очередную порцию, а не весь ответ сервера. Вообще эта функция должна вызываться в цикле, до тех пор пока не вернет 0 (нет больше данных для чтения).

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


 
DVM ©   (2007-06-05 14:50) [31]


> А ты контролировал содержимое FAddr в момент исключения
> ?
> Нет, не контролировал.

Нет не контролировал. Но проверить смогу теперь только дома, т.к. глюк проявляется только там.


 
Сергей М. ©   (2007-06-05 15:23) [32]


> не все сервера с которыми я работаю исполняют команду, если
> клиент после запроса не начинает читать ответ сервера


Ерунда полнейшая.

Ни один сервер не может знать, чем занят его клиент в момент готовности исполнить поступившую от клиента команду.

Если команда поступила, то на то она и поступила, чтобы ее исполнять.


 
DVM ©   (2007-06-05 15:26) [33]


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

Да я понимаю, но это факт проверенный. Почему так происходит, я не знаю - сервер встроен в некий девайс, что внутри - не знаю.

Но происходит именно так - делаю запрос к этой железяке, если я уберу из кода строку if Res = 0 then ReadData(FBuffer, 0); - то запрос вообще не выполняется (там реле переключиться должно), с этой же строкой - все нормально - запрос исполняется.

Я сам не понимаю как такое может происходить, но происходит же.


 
DVM ©   (2007-06-05 15:30) [34]

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


 
Сергей М. ©   (2007-06-05 16:16) [35]

"Железный" вэб-сервер абсолютно ничем не отличается от любого другого вэб-сервера, хоть "деревянного", хоть "стеклянного".

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


> там реле переключиться должно


Если оно должно переключиться в ходе исполнения запроса , но не переключилось, то это означает только одно - сервер не получил полный и/или корректный запрос. Все ! Остальное - досужие домыслы.


 
Сергей М. ©   (2007-06-05 16:49) [36]


> DVM ©   (05.06.07 15:30) [34]


Читать вовсе не обязательно, а вот рвать соединение с сервером сразу же после успешной отправки запроса как раз и может быть чревато отменой выполнения запроса - многие серверы (и не только вэб-, но и к примеру sql-серверы) в момент исполнения запроса отслеживают статус соединения с клиентом и прерывают исполнение запроса в случае обнаружения разрыва соединения (аварийного или по инициативе клиента), дабы не держать занятыми ставшие при этом ненужными ресурсы.
Так что чтение или нечтение тут совершенно ни при чем - ты послал запрос и тут же закрыл гнездо, и твой "железный" сервер, видимо, тут же прерывает выполнение запроса, т.е. до "щелкания реле" дело попросту не доходит.


 
DVM ©   (2007-06-05 18:16) [37]


> Сергей М. ©   (05.06.07 16:16) [35]

Набор прописных истин. Чего сказать то хотел? Я все это знаю не хуже тебя.


> Читать вовсе не обязательно, а вот рвать соединение с сервером
> сразу же после успешной отправки запроса как раз и может
> быть чревато отменой выполнения запроса - многие серверы
> (и не только вэб-, но и к примеру sql-серверы) в момент
> исполнения запроса отслеживают статус соединения с клиентом
> и прерывают исполнение запроса в случае обнаружения разрыва
> соединения (аварийного или по инициативе клиента), дабы
> не держать занятыми ставшие при этом ненужными ресурсы.

А это мысль. Т.е просто подождать некоторое время после запроса надо.
Попробую, за это спасибо.

Только ты сам противоречишь себе в постах 35 и 36.

Из 35 следует, что сервер выполнит запрос если сам запрос верен. Т.е правильный запрос - это достаточное условие.

В 36 ты вводишь еще утверждение, что оказвается еще не надо сразу рвать соединение. Т.е 35 недостаточное условие.


 
Сергей М. ©   (2007-06-06 08:17) [38]


> DVM ©   (05.06.07 18:16) [37]
>
>


> Чего сказать то хотел? Я все это знаю не хуже тебя


А раз знаешь, то к чему упоминать "железность" сервера ? Чего сказать-то хотел этим ?


> ты сам противоречишь себе в постах 35 и 36.


Да неужели ?



Страницы: 1 вся ветка

Текущий архив: 2008.02.24;
Скачать: CL | DM;

Наверх




Память: 0.6 MB
Время: 0.013 c
9-1167719462
@!!ex
2007-01-02 09:31
2008.02.24
Collision Detection


2-1201594285
Владимир Ка.
2008-01-29 11:11
2008.02.24
Визуальная компонента


15-1201238844
user_
2008-01-25 08:27
2008.02.24
QT и БД FireBird


15-1201188802
Sergei
2008-01-24 18:33
2008.02.24
Версия BIOS


15-1200776836
Kostafey
2008-01-20 00:07
2008.02.24
Хоть и не Delphi...