Когда я начинал ковырять в MFC, я всегда поражался, для чего сделан такой бесполезный класс, как CSocket. Точнее, почему он сделан именно ТАК. CSocket наследуется от CAsyncSocket, поэтому он не блокирующий в сокетном смысле. Он псевдоблокирующий.
int CSocket::Receive(void* lpBuf, int nBufLen, int nFlags)
{
if (m_pbBlocking != NULL)
{
WSASetLastError(WSAEINPROGRESS);
return FALSE;
}
int nResult;
while ((nResult = CAsyncSocket::Receive(lpBuf, nBufLen, nFlags)) == SOCKET_ERROR)
{
if (GetLastError() == WSAEWOULDBLOCK)
{
if (!PumpMessages(FD_READ))
return SOCKET_ERROR;
}
else
return SOCKET_ERROR;
}
return nResult;
}
За этот код правильно было бы расстрелять. Сразу, без суда, как в военное время. Тут используется скрытое окно, в которое CAsyncSocket спамит сообщениями, да еще и CSocket иногда помогает (а как мы все значем, сообщения в виндовс не самый быстрый способ коммуникации...) Естественно, в твоем треде окна этого нет, вот и не работает ничего (оно одно на все сокеты и создано в другом треде) Более того, если бы окно и было, скорее всего это привело бы к путанице с пакетами, точнее, с очередностью их прихода.
Вывод — товарищи, используйте в сокетах API, а не обертки типа MFC Кстати, еще есть библиотека — raw sockets library или что то в этом роде. Давно было, уже не помню точно. Вот ее использовать было можно, но... все ж api, наверное, проще.
Успехов.
Собственно, сабж. Куча клиентов чего-то спрашивают у сервера, он им чего-то отвечает. Певроначально сервер сделал как консольное приложение с поддержкой одного канала: listen(), accept() — новый сокет, канал отработал, убил, ждем нового соединения. И все работало прекрасно, пока не перешел к следующему шагу: добавить многопоточность (она же многоканальность . После accept() создаю отдельную нитку, в которой и выполняется общение сервера с клиентом. Сервер в это время снова listen(). Однако такой вариант отказывается работать: из недр sockcore.cpp стал вылетать ASSERT(pState->m_hSocketWindow != NULL); Попытался разобраться... и окончательно запутался:
При каждом send()/receive() вызывается одноименный метод CAsyncSocket и если вдруг он возвращает ошибку WSAEWOULDBLOCK CSocket пытается слать мессаги PumpMessage(). Как показала отладка, такая же ситуация бывает и в старой однопоточной версии, но там хэндл окна не пустой (и ассерт не вылазит).
Отсюда два вопроса:
1. CSocket — блокирующий, а в MSDN сказано, что ошибка WSAEWOULDBLOCK возникает, если сокет помечен как неблокирующий и receive() хочет его заблокировать. Не понял, почему эта ошибка вообще возникает...
2. Почему при запуске поддержки соединения в отдельном потоке (AfxBeginThread) хэндл окна оказывается нулевой (откуда он там вообще появляется) и слетает PumpMessage() и как это победить? Кстати, на кой вообще мне нужен этот PumpMessage()?
P.S. Я понимаю, что я многого не понимаю...
Re[2]: Рализация Multithreaded CSocket - одни вопросы???
Здравствуйте, Andrew S, Вы писали:
AS>За этот код правильно было бы расстрелять. Сразу, без суда, как в военное время. Тут используется скрытое окно, в которое CAsyncSocket спамит сообщениями, да еще и CSocket иногда помогает (а как мы все значем, сообщения в виндовс не самый быстрый способ коммуникации...) Естественно, в твоем треде окна этого нет, вот и не работает ничего (оно одно на все сокеты и создано в другом треде)
Согласен. Что-ж, наверное будем переходить на API
Копаясь в MSDN (а конретно, в описании метода CSocket::Attach()) наткнулся на пример, где все же используется CSocket в отдельном потоке. Как это работает на самом деле, непонятно, наверное, за счет того, что сокет поддержки соединения содержится в классе, порожденном от CWinThread (Attach()/Detach() там используется для передачи хэндла сокета в поток). На все равно даже этот вариант выглядит кривовато.
Дак что же, народ: нормальный многопоточный сервак не делается на CSocket?
Re[3]: Рализация Multithreaded CSocket - одни вопросы???
Здравствуйте, Finder, Вы писали:
F>Здравствуйте, Andrew S, Вы писали:
AS>>За этот код правильно было бы расстрелять. Сразу, без суда, как в военное время. Тут используется скрытое окно, в которое CAsyncSocket спамит сообщениями, да еще и CSocket иногда помогает (а как мы все значем, сообщения в виндовс не самый быстрый способ коммуникации...) Естественно, в твоем треде окна этого нет, вот и не работает ничего (оно одно на все сокеты и создано в другом треде):)
F>Согласен. Что-ж, наверное будем переходить на API ;) F>Копаясь в MSDN (а конретно, в описании метода CSocket::Attach()) наткнулся на пример, где все же используется CSocket в отдельном потоке. Как это работает на самом деле, непонятно, наверное, за счет того, что сокет поддержки соединения содержится в классе, порожденном от CWinThread (Attach()/Detach() там используется для передачи хэндла сокета в поток). На все равно даже этот вариант выглядит кривовато. F>Дак что же, народ: нормальный многопоточный сервак не делается на CSocket? F> :crash:
Если стоит задача реализации мноргоканальности при помощи многопоточности, то CSocket для этих целий лучше не использовать.
С другой стороны, мноргоканальность можно реализовать, используя CSocket, в одном потоке.
Re[4]: Рализация Multithreaded CSocket - одни вопросы???
Здравствуйте, kmn, Вы писали:
kmn>С другой стороны, мноргоканальность можно реализовать, используя CSocket, в одном потоке.
Кроме самого CSocket, сервер еще кое-чего полезного делает, и тут кроме многопоточности альтернативы нет. Кстати, несколько лет назад я уже делал аналогичную вещь на CBuider (исходники только затерялись где-то ) — так вот там таких граблей не наблюдалось...
Re[5]: Рализация Multithreaded CSocket - одни вопросы???
Не а. В VCL есть подобные же грабли. Они связаны с тем, что треды (TThread) синхронизируются с основным приложением при помощи сообщений. Окно, через которое они это делают, создается при первом создании TThread. А вот когда первый TThread создается не в основном потоке, в котором к тому же нет выборки сообщений... то все становится просто удивительно
А сокетные классы там сделаны похоже, за исключением того, что есть почти нормальный мультитреадный режим (используется асинхронный WriteFile), и в нем работает все почти нормально. Но опять же — сделано почти так же, как в MFC, так что возможно все.
Например, посмотрим Synchronize, интенсивно вызываемый из TServerClientThread
procedure TThread.Synchronize(Method: TThreadMethod);
begin
FSynchronizeException := nil;
FMethod := Method;
SendMessage(ThreadWindow, CM_EXECPROC, 0, Longint(Self));
if Assigned(FSynchronizeException) then raise FSynchronizeException;
end;
Напоминает MFC, правда?
Так что счастлив Ваш Бог, что проблем не было... Или проблемы просто не были замечены
F>Кроме самого CSocket, сервер еще кое-чего полезного делает, и тут кроме многопоточности альтернативы нет. Кстати, несколько лет назад я уже делал аналогичную вещь на CBuider (исходники только затерялись где-то ) — так вот там таких граблей не наблюдалось...
Посмотри внимательно — может проще будет
переехать не на API а на AsyncSocket?
У меня под руками прога — своя SOCKS-прокся (с кучей другого функционала) — так на CAsyncSoket-e сделано — и без проблем
Чем безопаснеe — тем неудобнее ;-)
Re[6]: Рализация Multithreaded CSocket - одни вопросы???
Небольшое замечание:
Приведенный код — из Delphi 5. В шестерке они сменили механизм. Где-то a форуме Delphi/Builder пробегал постинг ХакераДельфи на эту тему.
Данный код будет правильно работать в том и только том случае, если первый созданный поток является интерфейсным, т.е. производит выборку очереди сообщений, и если он обрабатывает сообщение CM_EXECPROC. В принципе, ребята сделали хорошую работу, т.к. они добились того, что код выполняется реально в главном потоке, а не во вторичном, заблокировав главный. Этакая кооперативная многозадачность. Такая структура позволяет делать VCL потоконебезопасным, и не использовать там никакие механизмы блокировок.
Основной косяк технологии — убийство TThread. Дело в том, что его метод WaitFor весьма нетривиален — если поток попытается вызвать Synchronize до опроверки на Terminated, а главный поток выполняет апишный Wait — тогда все встанет колом. Я напоролся на это, когда пытался сделать убивалку с таймаутом. В итоге пришлось списать с исходников Borland
... << RSDN@Home 1.0 beta 3 >>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re: Рализация Multithreaded CSocket - одни вопросы???
Насколько я знаю, смысл в том, что в момент Accept создается новый сокет. Он стряпает невидимое окно для получения сообщений о результатах неблокирующих вызовов. Тонкость: в винде окна привязываются к потоку, который их создал. Теперь проблема: окно сокета создано главным потоком, а работает с этим сокетом новый поток. Увы и ах.
Возможный выход: при приходе соединения нужно породить новый поток, который уже сделает Accept и продолжит общение. Не забыть про блокировки между главным потоком и новым в этот момент. Попробуй — может помочь.
... << RSDN@Home 1.0 beta 3 >>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[7]: Рализация Multithreaded CSocket - одни вопросы???
Нет. Основной косяк — то, что они пытаются создать окно не в _главном_ треде приложения (т.е. в том, в котором живет основная форма), а в том, что окно создается в потоке, в котором происходит первое создание объекта TThread. Очевидно, ежели первый объект TThread создается не в основном потоке, тогда происходит облом... Т.е. тут — явно глюк в коде. Ну а по поводу CSocket я уже сказал Кстати, постинги в форуме Билдер по подобной теме были моими
S>Основной косяк технологии — убийство TThread. Дело в том, что его метод WaitFor весьма нетривиален — если поток попытается вызвать Synchronize до опроверки на Terminated, а главный поток выполняет апишный Wait — тогда все встанет колом. Я напоролся на это, когда пытался сделать убивалку с таймаутом. В итоге пришлось списать с исходников Borland
Здравствуйте, Andrew S, Вы писали:
AS>Нет. Основной косяк — то, что они пытаются создать окно не в _главном_ треде приложения (т.е. в том, в котором живет основная форма), а в том, что окно создается в потоке, в котором происходит первое создание объекта TThread. Очевидно, ежели первый объект TThread создается не в основном потоке, тогда происходит облом... Т.е. тут — явно глюк в коде.
А, то есть ты намекаешь, что если мы руками состряпаем тред, в котором попытаемся состряпать TThread, то упс? Похоже на правду. Вообще, конечно, надо уже либо TThread пользовать, либо вручную колбаситься. Ничего хорошего и з сочетания этих двух подходов, имхо, не выйдет.
Аа. не, эти не мои. Честно говоря, особого отличия, кроме добавления лишнего кода со списками и событиями я не заметил. В результате то все равно вызывается PostMessage. А можно и SendMessage безо всяких объектов синхронизации — какая собственно разница
S>Вообще-то я имел в виду http://www.rsdn.ru/Forum/Message.aspx?mid=69160&only=1