Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Visual C++/MFC/WTL > CAsyncSocket и потоки


Автор: Dreamer_0x01 20.12.2005, 14:54
В классе CAsyncSocket есть переопределяемые функции типа OnAccept, OnReceive и т.д., в которых можно выполнять какие-то свои действия.
А кто их вызывает? Я имею в виду, какой поток вызывает сами эти функции?

Автор: JoyEx 20.12.2005, 16:36
Цитата(Dreamer_0x01 @ 20.12.2005, 14:54)
А кто их вызывает? Я имею в виду, какой
поток вызывает сами эти функции?


Переопредели у себя в классе A (class A : public CAsyncSocket) OnAccept, OnReceive, поставь бряк, а потом уже на нем просмтори стек вызовов


Автор: Dreamer_0x01 20.12.2005, 16:40
А однозначного ответа на это нет что ли, зависит от ситуации?

Автор: JoyEx 20.12.2005, 16:46
Так это же (стек вызовов) самое простое решение.
Добавлено @ 16:53
Debug->Windows->Call Stack

Автор: Dreamer_0x01 20.12.2005, 17:01
Ясно, щас займемся.
Просто до дебага я еще не скомпилировал ;)

Автор: Coocky 20.12.2005, 17:14
Dreamer_0x01
А зачем такие подробности, если не секрет?
Что надо вообщем-то?

Автор: Dreamer_0x01 21.12.2005, 13:43
Мне нужно работу с сокетами запихать в отдельный поток.

и в этом отдельном потоке нужно ожидать приема данных сокетом.
(Принцип простой - я запросил у внешнего устройства пакет данных и должен дождаться его получения).
Фот собственно с ожиданием и возникли проблемы - как быть?
В месте, где нужно ждать приема данных, так и просится создать цыкл какой-либо переменной, с заходом/выходом в критическую секцию, значение которой выставить по переопределяемой функции OnReceive класса сокета.
Вот и думаю - будет работать или нет? Ведь если сокет создает свой поток при инициализации, то все будет нормально. А если эти вункции вызываются тем потоком, в котором создан экземпляр сокета - то мой цикл попросту заснет...

Автор: Romikgy 21.12.2005, 13:48
Имхо ф-ция
Цитата(Dreamer_0x01 @ 21.12.2005, 13:43)
OnReceive
вызовится только тогда , когда прийдут данные на сокет, зачем здесь поток? Тем более что сокет асинхронный!!!

Автор: Dreamer_0x01 21.12.2005, 13:58
Функция вызывается не святым духом, а каким-то потоком. Каким? Моим, или каким-то другим, порожденным сокетом? Или оконным? Или главным потоком программы?

Автор: Romikgy 21.12.2005, 14:16
Имхо в стеке будет адресс твоего потока , но инициатором будет система

Автор: Dreamer_0x01 21.12.2005, 17:41
Так, посмотрел в дебаггере, кто вызывает эту функцию, написано, что WinMain. Это чей поток?

Автор: Romikgy 21.12.2005, 17:45
smile Твой

Автор: JoyEx 21.12.2005, 18:39
Romikgy прав.

Winsock (Ws2_32.dll) через ntdll.dll (NtDeviceIoControlFile, NtReadFile, NtWriteFile, NtCreateFile) обращается в kernel к девайсу \Device\AFD, которое отправляет TDI IRP-пакеты драйверам протокола, затем уже NDIS IRP-пакеты попадают в NDIS и через минипорт NDIS в драйвер сетевой карты, и потом в сеть. Если у тебя асинхронный сокет, то IRP пакет просто отмечается как отложенный (PENDING) и к тебе возвращается управление, а когда этот пакет "завершится" в kernel, он будет поставлен в очередь APC твоего потока и вот, когда твой поток получит управление (квант) он тебе и даст знать об OnReceive.

Автор: Coocky 22.12.2005, 11:56
Dreamer_0x01
что-то запарился ты с сокетами.
Давай сюда проблему твою, сейчас решим smile

Автор: Dreamer_0x01 22.12.2005, 15:05
Да собственно что нужно.

Нужно, чтобы в программе были функции, работающие одинаково и с сокетами, и с ком-портом.

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

1.В моем потоке команда типа Write отправляет информацию в сокет (это просто).
2.Мой прибор, работающий в режиме сервера, отвечает на этот запрос.(это сделано на отдельном контроллере).
3.В моем потоке команда типа Read ждет этого ответа прибора в течении какого-то времени. (У меня для этого используется таймаут ком-портов.)

Все это работает в своем, специально выделенном для этого потоке.

То есть мне самое сложное - грамотно дождаться ответа сокета в команде Read, так же, как это делается и для ком-порта. Пока что получается, но я бы сказал мягко говоря безграмотно - я наплодил кучу пользовательских сообщений, и вынес сокеты в отдельный поток, работает, но чувствую, что я с каждым днем перестаю понимать свой код =)

Автор: Coocky 22.12.2005, 15:33
Цитата(Dreamer_0x01 @ 22.12.2005, 15:05)
Нужно, чтобы в программе были функции, работающие одинаково и с сокетами, и с ком-портом.

Парралельно?
Какие проблемы!
просто в функции OnRecieve() управляй потоком для ком-портов smile
Или я не правильно понял?
Ты не прогу распиши, а проблему. Именно конкретно проблему!

Автор: Dreamer_0x01 22.12.2005, 16:12
поблема в том, что мне нужно сделать именно ОЖИДАНИЕ.
Рассмотрим на примере коннекта (его я тоже сделал по ожиданию)

Вот класс, производного от CAsyncSocket:

Код

#define CLIENTSOCK_DISCONNECT 0
#define CLIENTSOCK_CONNECT 1

class MyClientSocket : public CAsyncSocket
{
private:
    CRITICAL_SECTION cs; //для внтренних нужд
    BOOL Ready;    //флаг готовности приема данных
    
    unsigned char SockState;    //см.дефайны выше
    HANDLE MyTimer;    //для внутренних нужд
public:
    MyClientSocket();
    virtual ~MyClientSocket();
    virtual void OnClose(int nErrorCode);
    virtual void OnConnect(int nErrorCode);
    virtual void OnReceive(int nErrorCode);
    UINT Read(void * buf, UINT nToRead, UINT TimeOut, int *ErrorCode);//дождаться получения данных
    UINT Write(void * buf, UINT nToWrite, int * ErrorCode);//отправить запрос и выставить флаги
    unsigned char GetState(void);//узнать состояние сокета
    BOOL MakeConnect(LPCTSTR hostN , UINT portN,UINT TimeOut=1000);//дождаться коннекта
    void Disconnect(void);
};


Вот что порисходит по вызову функции OnConnect.
Код

void MyClientSocket::OnConnect(int nErrorCode)
{
    if(!nErrorCode)
    {
        ::EnterCriticalSection(&cs);
        SockState=CLIENTSOCK_CONNECT;//коннект установлен
        ::LeaveCriticalSection(&cs);
    }
    CAsyncSocket::OnConnect(nErrorCode);
}


А вот собственно функия, которую я хочу вызывать вне класса (по аналогичному принципу я собираюсь вызывать и функцию Read, использующую флаг Ready, выставляемый в функции OnReceive, но этопотом..)

Код

BOOL MyClientSocket::MakeConnect(LPCTSTR hostN , UINT portN,UINT TimeOut)
{
    BOOL result=TRUE;
    if(this->SockState==CLIENTSOCK_CONNECT)return TRUE;
    Create();
    if(this->Connect(hostN,portN))
    {
    }
    else
    {
        int errcode=GetLastError();
        if(errcode!=WSAEWOULDBLOCK)
        {
            result=FALSE;
            Close();
        }
    }
    if(!result)return FALSE;
    LARGE_INTEGER lDT;
    lDT.QuadPart=0;
    ::SetWaitableTimer(MyTimer,&lDT,1,NULL,NULL,NULL);
    unsigned char state;
    do//вот здесь крутимся, пока не поймаем коннект
    {
        ::EnterCriticalSection(&cs);
        state=this->SockState;
        ::LeaveCriticalSection(&cs);
        TimeOut--;
        ::WaitForSingleObject(MyTimer,100);
    }while((TimeOut)&&(state==CLIENTSOCK_DISCONNECT));
    ::CancelWaitableTimer(MyTimer);
    ::EnterCriticalSection(&cs);
    if(SockState==CLIENTSOCK_CONNECT)result=TRUE;
    else result=FALSE;
    ::LeaveCriticalSection(&cs);
    return result;
}

void MyClientSocket::Disconnect(void)
{
    ::EnterCriticalSection(&cs);
    Close();
    SockState==CLIENTSOCK_DISCONNECT;
    ::LeaveCriticalSection(&cs);
}


Я в функции OnConnect поставил бреакпоинт.
И при запуске фуекции MakeConnect(...) просто проходит цикл опроса, пока не исчерпается таймаут, и все...На брекпоинт мы не попадаем, хотя соединение должно установиться. (Во всяком случае серверная часть написанная по-простому, OnAccept выдает).
Вот я и не понимаю, кто должен вызвать функцию OnConnect ?

Автор: Coocky 22.12.2005, 19:08
Ты конектишься и хочешь работать только в том случае, если коннект удачный?
Но ответ от функции коннет занимает некоторое время, которое ты должен подождать, что б узнать результат соединения?
Я правильно понял?
Добавлено @ 19:15

Цитата(Dreamer_0x01 @ 22.12.2005, 16:12)
в функции OnConnect поставил бреакпоинт.
И при запуске фуекции MakeConnect(...) просто проходит цикл опроса, пока не исчерпается таймаут, и все...На брекпоинт мы не попадаем, хотя соединение должно установиться. (Во всяком случае серверная часть написанная по-простому, OnAccept выдает).
Вот я и не понимаю, кто должен вызвать функцию OnConnect ?

Покажи мне код, где ты создаешь гнездо,и прослушиваешь запрос на соединение?
Ты делаешь Listen() ?

Цитата(Dreamer_0x01 @ 22.12.2005, 16:12)
Вот я и не понимаю, кто должен вызвать функцию OnConnect ?


Все виртуальные функции OnConnect(),OnAccept(),OnRecieve() должны быть перегружены в твоем классе,производном от CAsyncSocket, что у тебя и иметтся.
Они вызываются когда СИСТЕМА генерирует события о прослушивании,приеме, закрытии и т.д.
Как это происходит внутри МФС, и незнаю, да и ненадо это знать

Автор: Coocky 22.12.2005, 19:25
И вообще, покажи ,как ты создаешь гнезда в клинтской и серверной части.
Надеюсь у тебя их две smile

Автор: Dreamer_0x01 22.12.2005, 19:45
Цитата(Coocky @ 22.12.2005, 19:08)
Ты конектишься и хочешь работать только в том случае, если коннект удачный?
Но ответ от функции коннет занимает некоторое время, которое ты должен подождать, что б узнать результат соединения?
Я правильно понял?


Именно. И то же самое для приема данных - нужно обязательно дождаться.



Цитата(Coocky @ 22.12.2005, 19:08)
Покажи мне код, где ты создаешь гнездо,и прослушиваешь запрос на соединение?
Ты делаешь Listen() ?


В смысле, Listen в серверной части? Обязательно! Но здась у меня именно клиентская, и я делаю именно Connetc. Она работоспособная, за это ручаюсь, так как в старой версией, где все сокеты были засунуты в оконные классы и им слались сообщения, все соединялось, передавалось как надо.
И серверную часть я сейчас не трогаю, она в отдельном приложении.

Но если такие уж сомнения, вот серверная часть, но она работает, проверялось не раз:

Код

#define MY_SOCK_MSG WM_USER+1

#define MY_SOCK_ACCEPT 1
#define MY_SOCK_CLOSE 2
#define MY_SOCK_CONNECT 3
#define MY_SOCK_RECEIVE 4
#define MY_SOCK_SEND 5


// MyCock command target

class MyCock : public CAsyncSocket
{
public:
    MyCock(CWnd *);
    virtual ~MyCock();
private:
    CWnd * parent;
public:
    virtual void OnAccept(int nErrorCode);
    virtual void OnClose(int nErrorCode);
    virtual void OnConnect(int nErrorCode);
    virtual void OnReceive(int nErrorCode);
    virtual void OnSend(int nErrorCode);
};



Код



MyCock::MyCock(CWnd * wnd)
{
    this->parent=wnd;
}

void MyCock::OnAccept(int nErrorCode)
{
    // TODO: Add your specialized code here and/or call the base class
    parent->SendMessage(MY_SOCK_MSG,MY_SOCK_ACCEPT,nErrorCode);


//    CAsyncSocket::OnAccept(nErrorCode);
}

void MyCock::OnClose(int nErrorCode)
{
    // TODO: Add your specialized code here and/or call the base class
    parent->SendMessage(MY_SOCK_MSG,MY_SOCK_CLOSE,nErrorCode);

//    CAsyncSocket::OnClose(nErrorCode);
}

void MyCock::OnReceive(int nErrorCode)
{
    // TODO: Add your specialized code here and/or call the base class
    parent->SendMessage(MY_SOCK_MSG,MY_SOCK_RECEIVE,nErrorCode);


//    CAsyncSocket::OnReceive(nErrorCode);
}

void MyCock::OnSend(int nErrorCode)
{
    // TODO: Add your specialized code here and/or call the base class
    parent->SendMessage(MY_SOCK_MSG,MY_SOCK_SEND,nErrorCode);


//    CAsyncSocket::OnSend(nErrorCode);
}

void MyCock::OnConnect(int nErrorCode)
{
    // TODO: Add your specialized code here and/or call the base class
    parent->SendMessage(MY_SOCK_MSG,MY_SOCK_CONNECT,nErrorCode);

//    CAsyncSocket::OnConnect(nErrorCode);
}



Вот создание прослушки:

Код

//(в классе:    MyCock *sock;MyCock temp_sock;)

    this->sock=new MyCock(this);
    this->portN=4000;//объявлено ранее как UINT

    this->sock->Create(this->portN);
    this->sock->Listen();



И собственно вот:
Код

LRESULT CEtNetDlg::OnServSockMsg(UINT w,LONG l)
{
    switch(w)
    {
        case MY_SOCK_CLOSE:
            temp_sock.Close();
//далее мои действия, касающегося всего остального...
        break;
        case MY_SOCK_ACCEPT:
            if(this->sock->Accept(this->temp_sock))
            {
            }
            else
            {
                MessageBox("Serv:OnAccept ERROR!");
            }
//мои действия, касающегося всего остального...
        break;
        case MY_SOCK_RECEIVE:
            char* pBuf = new char[1025];
            int iBufSize = 1024;
            int iRcvd;
            CString strRecvd;
            iRcvd = this->temp_sock.Receive(pBuf, iBufSize);
            if (iRcvd == SOCKET_ERROR)
            {
                MessageBox("╬°шсър яЁшхьр");
            } else
            {
//мои действия, касающегося всего остального...
            }
        break;
    }
//мои действия, касающегося всего остального...
    return 0;
}



Так что с серверной частью порядок, прослушку она делает, Ассепт по коннекту тоже...

Вот в клиентской части, как видишь, возникли проблемы с ожиданием...

Автор: JoyEx 22.12.2005, 20:52
А с обработкой сообщений всё Ок? WM_SOCKET_NOTIFY идут? Для теста удобно использовать сканер, например, XSpider - можно проверить (на localhost) клиента и сервер.

Автор: Dreamer_0x01 22.12.2005, 21:17
Цитата(JoyEx @ 22.12.2005, 20:52)
WM_SOCKET_NOTIFY идут?

Это что такое?

Автор: Coocky 23.12.2005, 12:55
Цитата(Dreamer_0x01 @ 22.12.2005, 19:45)
Вот в клиентской части, как видишь, возникли проблемы с ожиданием...

Блин, так какую проблему рещать-проблему с синхронизацией ожидания и приема или проблему с неполучением сообщений?

Автор: Dreamer_0x01 23.12.2005, 15:49
Желательно, обе. Так как решив торую, первая тоже решится сама собой.
Ну либо принципиально другое красивое решение первой проблемы...
Добавлено @ 15:54
Хорошо, я переформирую заново вопрос таким образом.

Мне нужно иметь три функции:

1. Функция "Соединиться" - она должна послать запрос на соединение и дождаться результата.
2. Функция "Послать буфер данных".
3. Функция "Принять буфер данных" - должна иметь какой-то таймаут, в течение которого должна дождаться получения нужного количества байт из сокета.

Как, сколько классов надо будет завернуть в это дело, сколько потоков наплодить - в общем-то без разницы, главное, чтобы работало.

Автор: JoyEx 23.12.2005, 17:04
Цитата(Dreamer_0x01 @ 22.12.2005, 21:17)
Цитата (JoyEx @ 22.12.2005, 20:52)
WM_SOCKET_NOTIFY идут?


Это что такое?


Смотри реализацию CAsyncSocket в sockcore.cpp
OnReceive и т.п. OnXXX вызываются из обработчика WM_SOCKET_NOTIFY CSocketWnd::OnSocketNotify()

Автор: Dreamer_0x01 23.12.2005, 18:09
да, но при чем тут СSocketWnd ?
Ведь CAsyncSocket унаследован от CObject...., а CSocketWnd лишь объявлен для него как "друг"...

(смотрю файл afxsock.h)

Автор: JoyEx 23.12.2005, 18:54
Цитата(Dreamer_0x01 @ 23.12.2005, 18:09)
да, но при чем тут СSocketWnd ?


Смотри CAsyncSocket::AttachHandle() и выше. Видать $M решили извещения посылать через обычную очередь сообщений (для этого и СSocketWnd), если будешь ее "тормозить", то тогда и не дождёшься OnReceive и т.п..

Автор: Dreamer_0x01 23.12.2005, 19:02
блин, так что, в каждом сокете еще и оконный класс получается создается?
Во изващение...

Автор: JoyEx 23.12.2005, 19:21
Цитата(Dreamer_0x01 @ 23.12.2005, 19:02)
блин, так что, в каждом сокете еще и оконный класс получается создается?
Во изващение...

Окно ведь invisible! Мне оно не мешает.
А как Windows XP переводится? Окошки XP?! Да? smile

Чтобы поток был GUI он должен иметь окно (точнее очередь сообщений и т.п.), первичный поток всегда GUI-поток. Ну вот, MFC сделали по принципу "всё гениальное просто" и не стали придумывать колесо - сделали извещения через СSocketWnd

Автор: Dreamer_0x01 23.12.2005, 19:32
Да, но оконный класс я так понял, хоть и имеет свою очредь сообщений, но эта очередь сообщений должна "прокручиваться" каким-то интерфейсным потоком. Который я так понимаю является тем потоком, в котором я создаю сокет и объявляю AfxSocketInit, так?
Тогда мне наверное следует создать свой интерфейсный поток именно для этих целей, чтобы в ожидании не "усыплять" поток, который должен будет "прокручива" эту очередь сообщений.
Правильно?

Автор: JoyEx 23.12.2005, 19:47
Сишный оконный класс всего лишь обслуживает настоящее Windows окно (subклассит), очередь сообщений есть у GUI-потока, она "прокручивается" всегда примерно вот так:
Код

    while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
    { 
        if (bRet == -1)
        {
            // handle the error and possibly exit
        }
        else
        {
            TranslateMessage(&msg); 
            DispatchMessage(&msg); 
        }
    } 


Там, где есть окно там и есть GUI поток.

Цитата(Dreamer_0x01 @ 23.12.2005, 19:32)
Тогда мне наверное следует создать свой интерфейсный поток именно для этих целей, чтобы в ожидании не "усыплять" поток, который должен будет "прокручива" эту очередь сообщений.
Правильно?

ну да, чтобы не было deadlock (взаимоблокировки).

Автор: Dreamer_0x01 23.12.2005, 19:54
Вот, наконец-то я тогда начинаю понимать, почему не вызываются события сокета - потому что собственно некому их вызывать...

Ладно, щас попробуем все еще и в потоковые классы завернуть.
Была у меня конечно идея использовать синхронный сокет (который CSocket), но там заче-то надо порождать классы CArchive, СSocketFile, по-моему, слишком тяжеловесно. Думаю, получится написать свой синхронный сокет, благо мне не та много от него надо.

Автор: JoyEx 23.12.2005, 19:58
Dreamer_0x01
Зачем переделывать код? Надо всего лишь "прокачать" очередь сообщений, чтобы сообщения были обработаны родной оконой процедурой какого-то окна, а потом продолжать чё-то свое делать.

Автор: Dreamer_0x01 23.12.2005, 20:02
Цитата(JoyEx @ 23.12.2005, 19:58)
Надо всего лишь "прокачать" очередь сообщений, чтобы сообщения были обработаны родной оконой процедурой какого-то окна, а потом продолжать чё-то свое делать.


Это как? Притащить в оно цепочку указателей на мой сокет, и там принудительно сделать его DispatchMessage ?

Автор: JoyEx 23.12.2005, 20:19
Нет.
С помощью PeekMessage()-TranslateMessage()-DispatchMessage() можно "прокачать" очередь в любом месте потока.
Добавлено @ 20:26
Код

    MSG msg;

    while(::PeekMessage (&msg, NULL, 0, 0, PM_REMOVE)){
        if(msg.message == WM_QUIT) break;
        TranslateMessage (&msg);
        DispatchMessage (&msg);
    }

Автор: Dreamer_0x01 23.12.2005, 20:41
Не понимаю, в какое именно место мне надо такой код добавить?

Автор: JoyEx 23.12.2005, 20:41
Цитата(Dreamer_0x01 @ 23.12.2005, 20:41)
Не понимаю, в какое именно место мне надо такой код добавить?


там, где у тебя долго выполняется код


smile I'll be back (from The Terminator)
Пока!

Автор: Dreamer_0x01 23.12.2005, 20:57
ВАХ! ну наконец-то заработало!
Единственное...поясни пожалуйста, почем в PeekMessage в качестве дескриптера окна передается NULL.
smile

Автор: JoyEx 24.12.2005, 13:29
Цитата(Dreamer_0x01 @ 23.12.2005, 20:57)
Единственное...поясни пожалуйста, почем в PeekMessage в качестве дескриптера окна передается NULL


Чтобы вытаскивать из очереди сообщения всех окон данного потока, вот что пишут $M:
Цитата(MSDN)

If hWnd is NULL, PeekMessage retrieves messages for any window that belongs to the current thread.


PeekMessage() в отличие от GetMessage() "прокачивает" очередь, т.е. если в очереди нету сообщений PeekMessage() возвращает управление (=0), а GetMessage() только при получении WM_QUIT.

Вот тебе мой макет
Код

class ASocket: public CAsyncSocket{

    void OnConnect(int nErrorCode ){

        if(nErrorCode != 0) return;

        char buff[] = "Hello!!!";
        int len = strlen(buff)+1;
        Send (buff, len);
    }
};


Это ф-ция потока:
Код

UINT MyControllingFunction( LPVOID pParam ){

    ASocket asocket;

    WORD wVersionRequested;
    WSADATA wsaData;
    wVersionRequested = MAKEWORD(2, 2);

    WSAStartup( wVersionRequested, &wsaData );

    asocket.Create ();
    asocket.Connect ("127.0.0.1", 80);

    MSG msg;
    while(GetMessage (&msg, 0, 0, 0) == TRUE){

        if(msg.message == 0x00000373 /*WM_SOCKET_NOTIFY*/){
            TRACE("\nOkay!!!\n");
        }
        TranslateMessage (&msg);
        DispatchMessage (&msg);
    }

    return 0;
}

Автор: Dreamer_0x01 25.12.2005, 13:41
Что ж, ты мне этой штукой (конструкцией с PeekMessage)очень помог, причем как выяснилось, не тоьлко в сокетах, а вообще в большинстве мест, где моя программа "подтормаживала".

Спасибо.

Репутация "+".

Автор: JoyEx 25.12.2005, 15:52
Dreamer_0x01, спасибо.

Для справки: В MS VС++ из Class View v7 очень удобно и быстро можно попасть на базовый класс (и его .cpp) через "Bases and Interfaces".

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)