-
Делаю класс асинхронного сокета и столкнулся с проблемкой зависания проги. Прога коннектится к серверу и начинает принимать от него непрерывный поток данных. Соответственно, после каждого Recv в очередь сразу же ставится следующее сообщение с событием FD_READ, которое тут же и обрабатывается. В итоге сообщения от главного окна просто не могут пробиться сквозь этот цикл. Вместе с тем, если запускать в начале процедуры SocketEvent Application.ProcessMessages, то все работает (естественно). Однако есть у меня сомнение: ведь эта процедура сразу вытаскивает из очереди очередное сообщение. Вполне вероятно, что это может оказаться следующая мессага от того же сокета - и тогда SocketEvent будет запущен второй раз, а уже после обработки этого сообщения управление вернется к первой копии SocketEvent. То есть, к примеру, может получиться так, что сначала обработается FD_CLOSE, а потом придет очередь FD_READ, что не очень-то здорово. Поэтому хочу спросить у знатоков, есть ли какой-то хороший путь решения данной проблемы? Или, может, я зря волнуюсь насчет перепутывания сообщений? В идеале хотелось бы иметь способ вместо ProcessMessages обработать только мессаги формы, а потом уже с чистой совестью заниматься с сокетом. Посмотрел сорсы ICS, там реализована собственная выборка собщений. В принципе, можно сделать так же, но я пока не разобрался, как ее применять. В общем, если что посоветуете, буду рад... только два условия: 1) Не хочется юзать нити 2) Видеть реплики типа "Зачем тебе это надо" и "не занимайся ерундой" также не горю желанием.
-
Показывай код обработки оконных сообщений. посылаемых созданными тобой гнездами ...
-
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;
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; MsgWrite : ;
MsgClose : begin
SetState(nsStop);
SetState(nsInactive);
end;
end;
end;
HandleSocksConnect возвращает False, если соединение с соксом уже установдено.
-
> HandleSocksConnect возвращает False, если соединение с соксом > уже установдено
А если не установлено, то что творится в этой ф-ции ?
> Application.ProcessMessages;
Это, разумеется, следует убрать.
-
> А если не установлено, то что творится в этой ф-ции ?
Устанавливается коннект с проксей. Приводить, наверно, не буду, но смысл в том, что в зависимости от состояния коннекта читаются или отправляются пакеты для установки связи.
> Это, разумеется, следует убрать. Так тогда мессаги от юзера вообще не проходят!
-
А как огранизован приём сообщений классом TAsyncClientSocket? Какое окно за это отвечает? Кто вызывает TAsyncClientSocket.DoEvent?
-
> [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;
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. Огромное спасибо за статью на Королевстве. Я именно по ней изучал сокеты :)
-
> Устанавливается коннект с проксей
Кто такая "прокся" ? Как устанавливается коннект с ней ? Каково вообще назначение класса TAsyncClientSocket ? Это чистой воды транспортный класс ? Если да, то какого лешего он лезет в прикладной протокол ?
-
> [7] Сергей М. © (02.04.08 13:18)
Ну вот, снова началось...
Прокси-сервер, работающий по протоколу Socks5. По спецификациям протокола. Получать и отправлять данные в асинхронном режиме. Не знаю, как ты определяешь транспортный или прикладной класс. Однако я сделал так, как сделал. Причем в обоих паках инет-компонентов, что я глядел, соксы упрятаны на самый низкий уровень. Ибо нет смысла вытаскивать их выше.
А теперь, может, закончим оффтоп и вернемся к обработке сообщений?
-
> закончим оффтоп и вернемся к обработке сообщений? >
Это далеко не оффтоп.
Но если тебе по барабану все эти важные моменты, то показывай, что у тебя творится в теле HandleSocksConnect
-
Прости, но эти важные моменты, на мой взгляд, не имеют никакого отношения к проблеме. Соединение через сокс проходит нормально. Разъясни, как это влияет на ответ по сабжу, может, я чего-то недопонимаю... 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
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 else Result:=False;
end;
Я убрал заполнение свойств структур, чтобы не очень много получилось. Основнй смысл в том, что метод что-то делает только тогда, когда стоит признак использования прокси и состояние коннекта к сокс-серверу не равно ssEstablished.
-
Send и Recv - это методы класса TFrostAsyncClientSocket ? Какой режим гнезда они используют ?
-
Да, это методы класса, но они по большому счету просто вызывают винсоковские функции, плюс считают трафик и производят проверку активности сокета.
-
Какой режим гнезда они используют ? Они чтго, на время выполнения переводят гнездо в блок.режим ? Или обращаются совсем к другому гнезду, нежели то самое неблокирующее гнездо, вокруг которого ты затеял сыр-бор ?
-
> Какой режим гнезда они используют ?
Они не меняют режима, т.е. в данном случае в асинхронном. Считай, что это обычные вызовы send/recv winsock-a. А что тебя смутило, разве я как-то не так их использую?
-
> что тебя смутило, разве я как-то не так их использую?
Так ведь все эти твои send/recv, относящиеся к какому-то там "прокси", точно так же являются инициаторами потенциальных событий FD_READ и FD_WRITE, обрабатывая которые ты вновь вызываешь эту самую HandleSocksConnect, в которой ты опять вызываешь send/recv, относящиеся к какому-то там "прокси", которые являются инициаторами потенциальных событий FD_READ и FD_WRITE, которые ..
Короче, сказ про то как "у попа была собака")
Кстати, где обработка FD_WRITE ? Нет ее ! Только не говори, что она не нужна или ты не знаешь зачем она нужна)
-
> [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;
Так, это все конечно интересно, но есть ли что сказать по существу вопроса?
-
> она есть, даже если ты ее не видишь
Я даже по огрызкам вижу, что первым делом при входе в HandleSocksConnect ты тут же озабочен каким-то там fSocksState, хотя первым делом должен идти анализ причин возникновения события - то ли буфер передачи освободился, то ли буфер приема не пуст.
> второй вариант не рассматривается
Почему ? Что за откровение такое тебе пришло, что это событие никогда у тебя не возникнет ?
-
> то ли буфер передачи освободился, то ли буфер приема не пуст
> Что за откровение такое тебе пришло, что это событие никогда > у тебя не возникнет
Учитывая, что эта процедура выполняется сразу после создания сокета, а пересылаемые данные не превышают 100 байт, я ОЧЕНЬ сильно сомневаюсь в вероятности возникновения FD_WRITE как результата освобождения занятого буфера. Имеется в виду, во время установки связи через соксы. Потом - вполне возможно, но я его и пересылаю в обработчик.
Пожалуйста, не надо давить на меня интеллектом. Я и так знаю, что ты гуру. А вот начальный вопрос так и остается без ответа.
-
> во время установки связи через соксы
Какие "соксы" ?
Ты о чем - о SOCKS4, о SOCKS5 ? Там куча режимов, вплоть до необходимости реконнекта кл.гнезда по указанному сервером адресу ! Почем мне знать, какой протокол в каком режиме ты решил использовать ?
Ты бросил огрызок код - нате, мол, догадывайтесь сами о том что я не изволил привести и не собираюсь приводить.
Ну и какого ответа ты после этого ждешь ?
> я ОЧЕНЬ сильно сомневаюсь в вероятности
Не надо сомневаться. Не сильно ни слабо. Надо действовать в точном соответствии с логикой Winsock, а не надеяться на авось.
И уж если тебе приспичило запихнуть в один флакон все это, то проще, надежней и очевидней бфло бы вынести handshake-логику в отдельную процедуру, никак не связанную ни с какими событиями гнезда, для чего нужно всего лишь на время "переговоров" с прокси перевести гнездо в блок.режим.
-
Если я правильно понял, ты сочиняешь некий "упрощенный" TClientSocket, способный работать с сервером через некий socks-прокси в "прозрачном" режиме ?
-
> Вполне вероятно, что это может оказаться следующая мессага > от того же сокета - и тогда SocketEvent будет запущен второй > раз
Откуда ей, этой "мессаге от того же сокета" взяться, если recv() ты вызываешь после Application.ProcessMessages ?
Очередное FD_READ-событие возникнет не раньше чем будет выполнен вызов recv().
Равно как и очередной вызов send() следует выполнять не раньше FD_WRITE-события, если оно явилось следствием отказа WSAEWOULDBLOCK при предыдущем вызове send()
-
> [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()
Учту...
-
> если я вызову Application.ProcessMessages в начале обработчика > FD_READ
Событие готовности гнезда к чтению предполагает чтение из гнезда, а не выкрутасы с польз.интерфейсом !
> FD_CLOSE, который спокойно закроет сокет и вернет управление > обработчику FD_READ, который будет безуспешно пытаться прочитать > данные из закрытого сокета
Ну и что ? Первая же попытка чтения вернет отказ, на который твой обработчик должен адекватно отреагировать.
-
> Событие готовности гнезда к чтению предполагает чтение из > гнезда, а не выкрутасы с польз.интерфейсом !
А я что, спорю? Предложи альтернативный способ, я его с радостью рассмотрю.
> Ну и что ? Первая же попытка чтения вернет отказ, на который > твой обработчик должен адекватно отреагировать.
Да, но пришедшие данные будут утеряны!
-
> Предложи альтернативный способ, я его с радостью рассмотрю
Альтернатив нет - сначала recv(), а потом выкрутасы.
Не нравится ?
Выноси транспорт с его событиями в доп.поток.
> пришедшие данные будут утеряны
С чего ты так уверен ?
-
> С чего ты так уверен ?
Из закрытого сокета вроде как читать не получится... Хорошо, тогда будет 2 вопроса: 1) Как организовать обработку событий сокета в отдельном потоке, потому что мессаги все равно ведь приходят в Application. Возможно, их надо фильтровать по коду мессаги?
2) Как насчет варианта вместо ProcessMessages сделать свой аналог, который будет извлекать из очереди только сообщения, не относящиеся к сокетам.
-
> Из закрытого сокета вроде как читать не получится
Чтение-то не из сокета происходит, а из внутреннего буфера приема !
Если там что-то имеется на момент закрытия сокета, то recv вернет это самое "что-то".
> 1)
Ты обработку с выборкой не путаешь ?
> 2)
Никак. Да и нафих оно не нужно, потому что извращенная это логика.
Но если изврат твоя стихия, то можно извлекать из очереди сообщения, относящиеся только к твоим сокетам и тут же ставить их в хвост очереди сообщений безо всякой обработки.
-
> Чтение-то не из сокета происходит, а из внутреннего буфера > приема ! > Если там что-то имеется на момент закрытия сокета, то recv > вернет это самое "что-то".
Ну да. Но разве он не освобождается после закрытия? К тому же хэндл уэ точно не будет иметь смысла после Close.
> Ты обработку с выборкой не путаешь ?
Угу... есть такое дело. Так как сделать это в отдельном потоке? Или там нужно будет PostThreadMessage юзать?
-
> разве он не освобождается после закрытия?
Смотря с какой стороны его закрыть.
> как сделать это в отдельном потоке?
Сделать что ? Выборку ? Обработку ? И то и другое ?
-
Выборку
-
По умолчанию поток может выбирать сообщения только тем окнам, которые созданы в его собственном контексте.
-
Ну и...?
-
Что "ну и" ?
-
В каком смысле > созданы в его собственном контексте.? Функция CreateЦindow была вызвана внутри Execute? И тогда если в том же Execute я организую выборку сообщений, то она будет распространяться только на эти окна?
-
Хотя мне все равно кажется, что это не выход... блин, ну как-то же делают серваки с сотнями клиентов, которые не тормозят... и этот ICS все хвалили, а там ведь такая же система
-
> ну как-то же делают серваки с сотнями клиентов, которые > не тормозят
Без окон
> и этот ICS все хвалили
ай да, конечно.
-
> В каком смысле > созданы в его собственном контексте.? Функция > CreateЦindow была вызвана внутри Execute?
Да. Хотя каким боком к тебе этот Execute ? Ты вон все апями озабочен да BeginThread'ами)
> если в том же Execute я организую выборку сообщений, то > она будет распространяться только на эти окна?
По умолчанию - да.
> как-то же делают серваки с сотнями клиентов, которые > не тормозят
Их по-другому делают. Ты же не озаботился исследованием, ты сразу ринулся код лепить) Еще и оффтопом меня попрекнул)
Чудо, блин)
-
> Без окон
Может, есть и без окон, но большинтсво все-таки с гуем... виндоус все-таки. Ладно, уйдём от серваков. Возьмем "качалки" с кучей потоков загрузки - они-то не тормозят...
> Ты вон все апями озабочен да BeginThread'ами)
Вот не надо, треды через апи меня пока не привлекают)
> Ты же не озаботился исследованием, ты сразу ринулся код > лепить) > Еще и оффтопом меня попрекнул)
Гм. Исследованием чего? Ну а насчет оффа - все-таки речь не о соксах, согласись.
-
> Их по-другому делают
Вот я и хочу узнать, какой там принцип юзается. С нитями понятно, хотя все равно не хочется с ними дело иметь - иначе смысл городить огород, легче все на синхронных оставить...
-
> большинтсво все-таки с гуем
Причем здей гуй ?
> уйдём от серваков. Возьмем "качалки" с кучей потоков загрузки > - они-то не тормозят
И у тебя не будет "тормозить", если втанешь на "правильный курс".
> хочу узнать, какой там принцип юзается
Там транспортная и/или прикладная логика вынесена в дополнительные потоки.
> иначе смысл городить огород, легче все на синхронных оставить
Решать тебе.
-
То есть "правильный курс" - это дополнительные потоки? И в одном потоке никак?
-
В одном потоке твои клиенты будут обслуживаться последовательно. Если тебя это устраивает, делай в одном.
А потоки на то и придуманы, чтобы распараллелить длительные вычислительные алгоритмы, а не выполнять их последовательно.
-
Да меня в принципе устраивает и последовательно, проблема-то в том, что за этим обслуживанием теряется возможность пользовательского интерфейса - окошко там переждвинуть, кнопку нажать.
-
Если теряется, значит не устраивает.
|