Конференция "Сети" » Асинхронные сокеты "забивают" очередь сообщений
 
  • SpellCaster (01.04.08 17:01) [0]
    Делаю класс асинхронного сокета и столкнулся с проблемкой зависания проги. Прога коннектится к серверу и начинает принимать от него непрерывный поток данных. Соответственно, после каждого Recv в очередь сразу же ставится следующее сообщение с событием FD_READ, которое тут же и обрабатывается. В итоге сообщения от главного окна просто не могут пробиться сквозь этот цикл. Вместе с тем, если запускать в начале процедуры SocketEvent Application.ProcessMessages, то все работает (естественно). Однако есть у меня сомнение: ведь эта процедура сразу вытаскивает из очереди очередное сообщение. Вполне вероятно, что это может оказаться следующая мессага от того же сокета - и тогда SocketEvent будет запущен второй раз, а уже после обработки этого сообщения управление вернется к первой копии SocketEvent. То есть, к примеру, может получиться так, что сначала обработается FD_CLOSE, а потом придет очередь FD_READ, что не очень-то здорово.
    Поэтому хочу спросить у знатоков, есть ли какой-то хороший путь решения данной проблемы? Или, может, я зря волнуюсь насчет перепутывания сообщений? В идеале хотелось бы иметь способ вместо ProcessMessages обработать только мессаги формы, а потом уже с чистой совестью заниматься с сокетом.
    Посмотрел сорсы ICS, там реализована собственная выборка собщений. В принципе, можно сделать так же, но я пока не разобрался, как ее применять.
    В общем, если что посоветуете, буду рад... только два условия:
    1) Не хочется юзать нити
    2) Видеть реплики типа "Зачем тебе это надо" и "не занимайся ерундой" также не горю желанием.
  • Сергей М. © (01.04.08 17:09) [1]
    Показывай код обработки оконных сообщений. посылаемых созданными тобой гнездами ...
  • SpellCaster (01.04.08 17:33) [2]

    // Socket message receiver
    procedure TAsyncClientSocket.DoEvent(WParam, LParam: Longint);
    var SockErr: Integer;
       Event: Word;
    begin
     Event:=WSAGetSelectEvent(LParam);
     SockErr:=WSAGetSelectError(LParam);
     // если произошла ошибка
     if SockErr<>0 then
       begin DoError(WParam,Event,SockErr); Exit; end;

     // если пришло ожидаемое событие, обнуляем таймер
     if fRespEvent=Event then fRequestTime:=0;

     // на обработку соединялки с проксей отправляем только события READ и WRITE
     case Event of
       FD_CONNECT        : begin
                             fActive:=True;
                             if fProxyUsed then begin fSocksState:=ssConnecting; Exit; end;
                           end;
       FD_READ, FD_WRITE : if HandleSocksConnect then Exit;
       FD_CLOSE          : begin
                             fSocksState:=ssNone;
                             fRequestTime:=0;
                             Close;
                           end;
       else Exit;
     end;
     if Assigned(OnEvent) then OnEvent(Self,WParam,Event);
    end;



    ну и в программе


    procedure TClient.SocketEvent(Sender: TObject; Sckt: TSocket; Event: Word);
    var received: Integer;
       req: ShortString;
       Msg: TMsg;
    begin
     Application.ProcessMessages;

     case Event of
       // сокет соединился
       MsgConnect:
         begin
            ...
         end;
       // в буфер сокета поступили данные
       MsgRead:
         begin
              ...
               begin
                 // Проверяем результат: если ошибка - соединение закрываем и переподключаемся
                 received:=fSock_in.Recv(@fBuf[0],PacketSize);
                 if received<=0 then
                   begin SetState(nsErr,'receive: '+WSALastErr); Close; Exit; end
                 // всё ОК - пишем полученные данные в выходной поток и обнуляем счетчик попыток
                 else
                 begin
                   ...
                 end;
         end; // MsgRead
       MsgWrite  :   ;
       MsgClose  : begin
                     SetState(nsStop);
                     SetState(nsInactive);
                   end;
     end;
    end;




    HandleSocksConnect возвращает False, если соединение с соксом уже установдено.
  • Сергей М. © (02.04.08 08:31) [3]

    > HandleSocksConnect возвращает False, если соединение с соксом
    > уже установдено


    А если не установлено, то что творится в этой ф-ции ?


    > Application.ProcessMessages;


    Это, разумеется, следует убрать.
  • SpellCaster (02.04.08 11:48) [4]
    > А если не установлено, то что творится в этой ф-ции ?

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

    > Это, разумеется, следует убрать.
    Так тогда мессаги от юзера вообще не проходят!
  • Григорьев Антон © (02.04.08 12:54) [5]
    А как огранизован приём сообщений классом TAsyncClientSocket? Какое окно за это отвечает? Кто вызывает TAsyncClientSocket.DoEvent?
  • SpellCaster (02.04.08 13:03) [6]
    > [5] Григорьев Антон ©   (02.04.08 12:54)

    Вот такая процедурка, одна на всех

    function InnerWndProc(wnd: hWnd; msg, wParam, lParam: Longint): Longint; stdcall;
    var sock: TObject;
      s: string;
    begin
     if not ((msg = WM_SOCKMSG) or (msg = WM_TIMER)) then
     begin
       Result:=DefWindowProc(wnd,msg,wparam,lparam);
       Exit;
     end;
     // else
     Result:=0;
     sock:=TObject(Pointer(GetWindowLong(wnd,GWL_USERDATA)));
     if sock=nil then Exit;
     s:=sock.ClassName;
     if s='TAsyncClientSocket' then
       case msg of
         WM_SOCKMSG: TAsyncClientSocket(sock).DoEvent(WParam, LParam);
         WM_TIMER:   TAsyncClientSocket(sock).DoTimer;
       end
     else
     if s='TAsyncServerSocket' then
       case msg of
         WM_SOCKMSG: TAsyncServerSocket(sock).DoEvent(WParam, LParam);
         WM_TIMER:   TAsyncServerSocket(sock).DoTimer;
       end;
    end;



    Окошко у каждого сокета своё, внутреннее.

    P.S. Огромное спасибо за статью на Королевстве. Я именно по ней изучал сокеты :)
  • Сергей М. © (02.04.08 13:18) [7]

    > Устанавливается коннект с проксей


    Кто такая "прокся" ?
    Как устанавливается коннект с ней ?
    Каково вообще назначение класса TAsyncClientSocket ?
    Это чистой воды транспортный класс ?
    Если да, то какого лешего он лезет в прикладной протокол ?
  • SpellCaster (02.04.08 13:44) [8]
    > [7] Сергей М. ©   (02.04.08 13:18)

    Ну вот, снова началось...

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

    А теперь, может, закончим оффтоп и вернемся к обработке сообщений?
  • Сергей М. © (02.04.08 13:52) [9]

    > закончим оффтоп и вернемся к обработке сообщений?
    >


    Это далеко не оффтоп.

    Но если тебе по барабану все эти важные моменты, то показывай, что у тебя творится в теле HandleSocksConnect
  • SpellCaster (02.04.08 14:15) [10]
    Прости, но эти важные моменты, на мой взгляд, не имеют никакого отношения к проблеме. Соединение через сокс проходит нормально. Разъясни, как это влияет на ответ по сабжу, может, я чего-то недопонимаю...
    // Connect to Socks
    function TFrostAsyncClientSocket.HandleSocksConnect: Boolean;
    var len: Integer;
       auth:  TSocks5AuthSel;
       ameth: TSocks5AuthMeth;
       req:   TSocks5Request;
       repl:  TSocks5Reply;
       Addr: array[0..255] of Byte;

    begin
     Result:=True;
     if fProxyUsed and (fSocksState in [ssConnecting..ssRequesting]) then
     case fSocksState of
       ssConnecting:      // Договариваемся о методах аутентификации
             begin
               fSocksState:=ssMethSelecting;
                ...
               Send(@auth,3,True);
             end;
       ssMethSelecting:   // Устанавливаем связь с хостом
             begin
               if Recv(@ameth,SizeOf(ameth))<=0 then begin Close; WSASetLastError(WSAESOCKSCONNECT); Exit; end;
               ...
               fSocksState:=ssRequesting;
               Send(@req,SizeOf(req));
               Send(@Addr[0],len,True);
             end;
       ssRequesting:  // теперь читаем ответ - ровно столько, сколько прислали
             begin
               // сначала читаем версию, ответ, резерв, тип адреса и 1 байт адреса
               len:=Recv(@repl,SizeOf(repl));
                ...
               if Recv(@Addr,len-1+2)<=0 then begin Close; WSASetLastError(WSAESOCKSCONNECT); Exit; end;
                ...
               fSocksState:=ssEstablished;
               fActive:=True;
               if Assigned(OnEvent) then OnEvent(Self,fSckt,FD_CONNECT);
               PostMessage(fHwnd,WM_SOCKMSG,fSckt,MakeLong(FD_WRITE,0));
             end;
     end  // case
     else Result:=False;
    end;


    Я убрал заполнение свойств структур, чтобы не очень много получилось. Основнй смысл в том, что метод что-то делает только тогда, когда стоит признак использования прокси и состояние коннекта к сокс-серверу не равно ssEstablished.
  • Сергей М. © (02.04.08 14:38) [11]
    Send и Recv - это методы класса TFrostAsyncClientSocket ?
    Какой режим гнезда они используют ?
  • SpellCaster (02.04.08 16:51) [12]
    Да, это методы класса, но они по большому счету просто вызывают винсоковские функции, плюс считают трафик и производят проверку активности сокета.
  • Сергей М. © (02.04.08 17:34) [13]
    Какой режим гнезда они используют ?
    Они чтго, на время выполнения переводят гнездо в блок.режим ?
    Или обращаются совсем к другому
    гнезду, нежели то самое неблокирующее гнездо, вокруг которого ты затеял сыр-бор ?
  • SpellCaster (02.04.08 19:46) [14]
    > Какой режим гнезда они используют ?

    Они не меняют режима, т.е. в данном случае в асинхронном. Считай, что это обычные вызовы send/recv winsock-a. А что тебя смутило, разве я как-то не так их использую?
  • Сергей М. © (02.04.08 21:10) [15]

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


    Так ведь все эти твои send/recv, относящиеся к какому-то там "прокси", точно так же являются инициаторами потенциальных событий FD_READ и FD_WRITE, обрабатывая которые ты вновь вызываешь эту самую  HandleSocksConnect, в которой ты опять вызываешь send/recv, относящиеся к какому-то там "прокси", которые являются инициаторами потенциальных событий FD_READ и FD_WRITE, которые ..

    Короче, сказ про то как "у попа была собака")

    Кстати, где обработка FD_WRITE ?
    Нет ее !
    Только не говори, что она не нужна или ты не  знаешь зачем она нужна)
  • SpellCaster (03.04.08 15:08) [16]
    > [15] Сергей М. ©   (02.04.08 21:10)
    > Короче, сказ про то как "у попа была собака")

    Для этого как раз и используется переменная fSocksState, которая отслеживает текущее состояние соединения с проксей. Никакого сказа про попа нету, иначе сокет никогда бы не сумел нормально добраться до удалённого хоста, а крутился бы в локалке.

    > Кстати, где обработка FD_WRITE ?
    > Только не говори, что она не нужна или ты не  знаешь зачем
    > она нужна)

    Событие FD_WRITE возникает в двух случаях: после FD_CONNECT и когда предыдущий вызов Send закончился ошибкой по причине переполнения выходного буфера. В нашем случае второй вариант не рассматривается. А обработка - она как суслик: она есть, даже если ты ее не видишь ;)
    > [2] SpellCaster   (01.04.08 17:33)

    FD_READ, FD_WRITE : if HandleSocksConnect then Exit;



    Так, это все конечно интересно, но есть ли что сказать по существу вопроса?
  • Сергей М. © (03.04.08 15:18) [17]

    > она есть, даже если ты ее не видишь


    Я даже по огрызкам вижу, что первым делом при входе в HandleSocksConnect ты тут же озабочен каким-то там fSocksState, хотя первым делом должен идти анализ причин возникновения события - то ли буфер передачи освободился, то ли буфер приема не пуст.


    > второй вариант не рассматривается


    Почему ? Что за откровение такое тебе пришло, что это событие никогда у тебя не возникнет ?
  • SpellCaster (03.04.08 16:07) [18]
    > то ли буфер передачи освободился, то ли буфер приема не пуст


    > Что за откровение такое тебе пришло, что это событие никогда
    > у тебя не возникнет

    Учитывая, что эта процедура выполняется сразу после создания сокета, а пересылаемые данные не превышают 100 байт, я ОЧЕНЬ сильно сомневаюсь в вероятности возникновения FD_WRITE как результата освобождения занятого буфера.
    Имеется в виду, во время установки связи через соксы. Потом - вполне возможно, но я его и пересылаю в обработчик.

    Пожалуйста, не надо давить на меня интеллектом. Я и так знаю, что ты гуру. А вот начальный вопрос так и остается без ответа.
  • Сергей М. © (03.04.08 16:33) [19]

    > во время установки связи через соксы


    Какие "соксы" ?

    Ты о чем - о SOCKS4, о SOCKS5 ?
    Там куча режимов, вплоть до необходимости реконнекта кл.гнезда по указанному сервером адресу !
    Почем мне знать, какой протокол в каком режиме ты решил использовать ?

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

    Ну и какого ответа ты после этого ждешь ?


    > я ОЧЕНЬ сильно сомневаюсь в вероятности


    Не надо сомневаться. Не сильно ни слабо. Надо действовать в точном соответствии с логикой Winsock, а не надеяться на авось.

    И уж если тебе приспичило запихнуть в один флакон все это, то проще, надежней и очевидней бфло бы вынести handshake-логику в отдельную процедуру, никак не связанную ни с какими событиями гнезда, для чего нужно всего лишь на время "переговоров" с прокси перевести гнездо в блок.режим.
  • Сергей М. © (03.04.08 17:07) [20]
    Если я правильно понял, ты сочиняешь некий "упрощенный" TClientSocket, способный работать с сервером через некий socks-прокси в "прозрачном" режиме ?
  • Сергей М. © (03.04.08 17:20) [21]

    > Вполне вероятно, что это может оказаться следующая мессага
    > от того же сокета - и тогда SocketEvent будет запущен второй
    > раз


    Откуда ей, этой "мессаге от того же сокета" взяться, если recv() ты вызываешь после Application.ProcessMessages ?

    Очередное FD_READ-событие возникнет не раньше чем будет выполнен вызов recv().

    Равно как и очередной вызов send() следует выполнять не раньше FD_WRITE-события, если оно явилось следствием отказа WSAEWOULDBLOCK при предыдущем вызове send()
  • SpellCaster (04.04.08 13:53) [22]
    > [19] Сергей М. ©   (03.04.08 16:33)
    > Ты о чем - о SOCKS4, о SOCKS5 ?

    Говорил же...
    >Прокси-сервер, работающий по протоколу Socks5.
    Обычный метод, без аутентификации.

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

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

    > для чего нужно всего лишь на время "переговоров" с прокси
    > перевести гнездо в блок.режим

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

    > Если я правильно понял, ты сочиняешь некий "упрощенный"
    > TClientSocket, способный работать с сервером через некий
    > socks-прокси в "прозрачном" режиме ?

    В целом, верно.

    > Откуда ей, этой "мессаге от того же сокета" взяться, если
    > recv() ты вызываешь после Application.ProcessMessages ?

    Воот, наконец-то вернулись к сути вопроса. Разумеется, я не имел в виду FD_READ. Но ведь событие может быть и другим, например, FD_CLOSE. Тогда-то и случится засада: если я вызову Application.ProcessMessages в начале обработчика FD_READ, будет вызван обработчик FD_CLOSE, который спокойно закроет сокет и вернет управление обработчику FD_READ, который будет безуспешно пытаться прочитать данные из закрытого сокета. Так ведь получается?

    > Равно как и очередной вызов send() следует выполнять не
    > раньше FD_WRITE-события, если оно явилось следствием отказа
    > WSAEWOULDBLOCK при предыдущем вызове send()

    Учту...
  • Сергей М. © (04.04.08 14:30) [23]

    > если я вызову Application.ProcessMessages в начале обработчика
    > FD_READ


    Событие готовности гнезда к чтению предполагает чтение из гнезда, а не выкрутасы с польз.интерфейсом !


    > FD_CLOSE, который спокойно закроет сокет и вернет управление
    > обработчику FD_READ, который будет безуспешно пытаться прочитать
    > данные из закрытого сокета


    Ну и что ? Первая же попытка чтения вернет отказ, на который твой обработчик должен адекватно отреагировать.
  • SpellCaster (04.04.08 17:31) [24]
    > Событие готовности гнезда к чтению предполагает чтение из
    > гнезда, а не выкрутасы с польз.интерфейсом !

    А я что, спорю? Предложи альтернативный способ, я его с радостью рассмотрю.

    > Ну и что ? Первая же попытка чтения вернет отказ, на который
    > твой обработчик должен адекватно отреагировать.

    Да, но пришедшие данные будут утеряны!
  • Сергей М. © (04.04.08 21:57) [25]

    > Предложи альтернативный способ, я его с радостью рассмотрю


    Альтернатив нет - сначала recv(), а потом выкрутасы.

    Не нравится ?

    Выноси транспорт с его событиями в доп.поток.


    > пришедшие данные будут утеряны


    С чего ты так уверен ?
  • SpellCaster (09.04.08 14:10) [26]
    > С чего ты так уверен ?

    Из закрытого сокета вроде как читать не получится...
    Хорошо, тогда будет 2 вопроса:
    1) Как организовать обработку событий сокета в отдельном потоке, потому что мессаги все равно ведь приходят в Application. Возможно, их надо фильтровать по коду мессаги?

    2) Как насчет варианта вместо ProcessMessages сделать свой аналог, который будет извлекать из очереди только сообщения, не относящиеся к сокетам.
  • Сергей М. © (09.04.08 14:24) [27]

    > Из закрытого сокета вроде как читать не получится


    Чтение-то не из сокета происходит, а из внутреннего буфера приема !

    Если там что-то имеется на момент закрытия сокета, то recv вернет это самое "что-то".


    > 1)


    Ты обработку с выборкой не путаешь ?


    > 2)


    Никак.
    Да и нафих оно не нужно, потому что извращенная это логика.

    Но если изврат твоя стихия, то можно извлекать из очереди сообщения, относящиеся только к твоим сокетам и тут же ставить их в хвост очереди сообщений безо всякой обработки.
  • SpellCaster (09.04.08 18:15) [28]
    > Чтение-то не из сокета происходит, а из внутреннего буфера
    > приема !
    > Если там что-то имеется на момент закрытия сокета, то recv
    > вернет это самое "что-то".

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

    > Ты обработку с выборкой не путаешь ?

    Угу... есть такое дело. Так как сделать это в отдельном потоке? Или там нужно будет PostThreadMessage юзать?
  • Сергей М, (09.04.08 20:31) [29]

    > разве он не освобождается после закрытия?


    Смотря с какой стороны его закрыть.


    > как сделать это в отдельном потоке?


    Сделать что ?
    Выборку ?
    Обработку ?
    И то и другое ?
  • SpellCaster (10.04.08 12:59) [30]
    Выборку
  • Сергей М. © (10.04.08 14:19) [31]
    По умолчанию поток может выбирать сообщения только тем окнам, которые созданы в его собственном контексте.
  • SpellCaster (10.04.08 16:06) [32]
    Ну и...?
  • Сергей М. © (10.04.08 16:11) [33]
    Что "ну и" ?
  • SpellCaster (10.04.08 17:38) [34]
    В каком смысле > созданы в его собственном контексте.? Функция CreateЦindow была вызвана внутри Execute?
    И тогда если в том же Execute я организую выборку сообщений, то она будет распространяться только на эти окна?
  • SpellCaster (10.04.08 18:14) [35]
    Хотя мне все равно кажется, что это не выход... блин, ну как-то же делают серваки с сотнями клиентов, которые не тормозят... и этот ICS все хвалили, а там ведь такая же система
  • DiamondShark © (10.04.08 20:09) [36]

    > ну как-то же делают серваки с сотнями клиентов, которые
    > не тормозят

    Без окон


    > и этот ICS все хвалили

    ай да, конечно.
  • Сергей М, (10.04.08 20:19) [37]

    > В каком смысле > созданы в его собственном контексте.? Функция
    > CreateЦindow была вызвана внутри Execute?


    Да.
    Хотя каким боком к тебе этот Execute ?
    Ты вон все апями озабочен да BeginThread'ами)


    > если в том же Execute я организую выборку сообщений, то
    > она будет распространяться только на эти окна?


    По умолчанию - да.


    > как-то же делают серваки с сотнями клиентов, которые
    > не тормозят


    Их по-другому делают.
    Ты же не озаботился исследованием, ты сразу ринулся код лепить)
    Еще и оффтопом меня попрекнул)

    Чудо, блин)
  • SpellCaster (14.04.08 13:34) [38]
    > Без окон

    Может, есть и без окон, но большинтсво все-таки с гуем... виндоус все-таки. Ладно, уйдём от серваков. Возьмем "качалки" с кучей потоков загрузки - они-то не тормозят...

    > Ты вон все апями озабочен да BeginThread'ами)

    Вот не надо, треды через апи меня пока не привлекают)

    > Ты же не озаботился исследованием, ты сразу ринулся код
    > лепить)
    > Еще и оффтопом меня попрекнул)

    Гм. Исследованием чего?
    Ну а насчет оффа - все-таки речь не о соксах, согласись.
  • SpellCaster (14.04.08 13:36) [39]
    > Их по-другому делают

    Вот я и хочу узнать, какой там принцип юзается. С нитями понятно, хотя все равно не хочется с ними дело иметь - иначе смысл городить огород, легче все на синхронных оставить...
  • Сергей М. © (14.04.08 14:53) [40]

    > большинтсво все-таки с гуем


    Причем здей гуй ?


    > уйдём от серваков. Возьмем "качалки" с кучей потоков загрузки
    > - они-то не тормозят


    И у тебя не будет "тормозить", если втанешь на "правильный курс".


    > хочу узнать, какой там принцип юзается


    Там транспортная и/или прикладная логика вынесена в дополнительные потоки.


    > иначе смысл городить огород, легче все на синхронных оставить


    Решать тебе.
  • SpellCaster (14.04.08 16:46) [41]
    То есть "правильный курс" - это дополнительные потоки? И в одном потоке никак?
  • Сергей М. © (14.04.08 16:58) [42]
    В одном потоке твои клиенты будут обслуживаться последовательно.
    Если тебя это устраивает, делай в одном.

    А потоки на то и придуманы, чтобы распараллелить длительные вычислительные алгоритмы, а не выполнять их последовательно.
  • SpellCaster (16.04.08 10:39) [43]
    Да меня в принципе устраивает и последовательно, проблема-то в том, что за этим обслуживанием теряется возможность пользовательского интерфейса - окошко там переждвинуть, кнопку нажать.
  • Сергей М. © (16.04.08 11:27) [44]
    Если теряется, значит не устраивает.
 
Конференция "Сети" » Асинхронные сокеты "забивают" очередь сообщений
Есть новые Нет новых   [134431   +14][b:0][p:0.005]