Главная страница
    Top.Mail.Ru    Яндекс.Метрика
Форум: "Сети";
Текущий архив: 2008.02.24;
Скачать: [xml.tar.bz2];

Вниз

Винет поток на функции 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;
Скачать: [xml.tar.bz2];

Наверх




Память: 0.58 MB
Время: 0.044 c
2-1201717781
вадик
2008-01-30 21:29
2008.02.24
Интерфейсы


2-1201596048
Михаил (Питер)
2008-01-29 11:40
2008.02.24
Интернет бот


11-1184395850
Thaddy
2007-07-14 10:50
2008.02.24
kol-ce question


2-1201764728
lead-in
2008-01-31 10:32
2008.02.24
трабла с кодировкой


15-1201220309
Riply
2008-01-25 03:18
2008.02.24
К вопросу "каким способом надо решать задачу"





Afrikaans Albanian Arabic Armenian Azerbaijani Basque Belarusian Bulgarian Catalan Chinese (Simplified) Chinese (Traditional) Croatian Czech Danish Dutch English Estonian Filipino Finnish French
Galician Georgian German Greek Haitian Creole Hebrew Hindi Hungarian Icelandic Indonesian Irish Italian Japanese Korean Latvian Lithuanian Macedonian Malay Maltese Norwegian
Persian Polish Portuguese Romanian Russian Serbian Slovak Slovenian Spanish Swahili Swedish Thai Turkish Ukrainian Urdu Vietnamese Welsh Yiddish Bengali Bosnian
Cebuano Esperanto Gujarati Hausa Hmong Igbo Javanese Kannada Khmer Lao Latin Maori Marathi Mongolian Nepali Punjabi Somali Tamil Telugu Yoruba
Zulu
Английский Французский Немецкий Итальянский Португальский Русский Испанский