Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Системное программирование и WinAPI > WSAEventSelect Model and event Notification


Автор: knut 9.11.2007, 09:54
день добрый.
Делаю клиентский ТCP-шний сокет используя меxанизм евентов а точнее WSAEventSelect.
есть 2 класса
1. сам класс CSocket 
Код

class CSocket
{
public:
        CSocket();
        virtual ~CSocket();
public:
    bool Connect(const char *DestAddress,int aDestPort);
    void Close();
    bool OpenSoket(int aSocketType, int aPort, const char * aLocalAddress);

    int OnSend(PACKET &paket);
    int OnRecive();
    int GetSendResult(bool bWait);
public:
    SOCKET m_hsocket;
    DWORD dwError;
    bool bltConn;
};

и соответственно реализация всеx метов.
Код

bool CSocket::OpenSoket(int aSocketType, int aPort, const char * aLocalAddress)
{
    m_hsocket = socket(AF_INET,aSocketType,IPPROTO_TCP );
    if(m_hsocket == INVALID_SOCKET)
    {
        dwError = ::WSAGetLastError();
        return false;
    }
    else
        return m_hsocket;
}

bool CSocket::Connect(const char *DestAddress,int aDestPort)
{
    
    sockaddr_in dest_addr;
    HOSTENT *hst;
    
    dest_addr.sin_family = AF_INET;
    dest_addr.sin_port   = htons(PORT);
    if (inet_addr(SERVERADDR)!=INADDR_NONE)
         dest_addr.sin_addr.s_addr=inet_addr(SERVERADDR);
     else
           if (hst=gethostbyname(SERVERADDR))
           ((unsigned long *)&dest_addr.sin_addr)[0]=((unsigned long **)hst->h_addr_list)[0][0];
    else
    {
        printf("Invalid address %s\n",SERVERADDR);
        closesocket(m_hsocket);
    }
           if(SOCKET_ERROR != connect(m_hsocket,(sockaddr *)&dest_addr,sizeof(dest_addr)))
                bltConn = true;
           else
               printf("Connect error %d\n",WSAGetLastError());
    
    return bltConn;


}

int CSocket::OnRecive()
{

    WSABUF recv_buf;
    DWORD NumberOfDataREcived = 0;
    LPOVERLAPPED lpOverlapped;
    DWORD dwFlag = 0;


    recv_buf.buf = 0;
    recv_buf.len = 0;


    int LastError = 0;
    if(INVALID_SOCKET == WSARecv(m_hsocket,&recv_buf,1,&NumberOfDataREcived,&dwFlag,lpOverlapped,NULL))
    {
        LastError = ::WSAGetLastError();
        return LastError;
    }
    return 0;

    
}

int CSocket::OnSend(PACKET &paket)
{
    WSABUF buffer;
    int nLastError;
    buffer.buf = paket.pBuffer;
    buffer.len = strlen(paket.pBuffer);
    DWORD dwNumberOfBytesSent = 0;
    
    LPWSAOVERLAPPED lpOverlapped = &paket.overlapped;
    if(WSASend(m_hsocket,&buffer,1,&dwNumberOfBytesSent,0,lpOverlapped,NULL))
    {
        nLastError = WSAGetLastError();
            return nLastError;
    }


    return 0;
}

int CSocket::GetSendResult(bool bWait)
{
    WSAOVERLAPPED poverlapped;
    DWORD pTransfer = 0;
    DWORD dwFlag = 0;
    DWORD deError;
        if(!WSAGetOverlappedResult(m_hsocket,&poverlapped,&pTransfer,bWait,&dwFlag))    
        deError = WSAGetLastError();
    return dwError;
}

и класс thread и сажаю все евенты.
Код

class CSocketThread
{
public:
    CSocketThread();
    virtual ~CSocketThread();
public:
    bool  StartThread(CSocket * pSocket,HANDLE hEvent);
    static void  _cdecl ThreadProc(LPVOID pVoid);
    bool RegisterEvent(SOCKET s, DWORD dwEvent);

public:
    DWORD       m_threadID;
    HANDLE      m_ThreadHandle;
    CSocket           *m_pSocket;

    int     m_nLastError;
    DWORD       m_EventTotal;

    SOCKET      m_Socket[WSA_MAXIMUM_WAIT_EVENTS];
    SOCKET      m_dwEvents[WSA_MAXIMUM_WAIT_EVENTS];
    WSAEVENT             m_Event[WSA_MAXIMUM_WAIT_EVENTS];

};

с медодами старт и воркертред.
Код

bool CSocketThread::StartThread(CSocket *pSocket,HANDLE hEvent)
{

    m_pSocket = pSocket;
    WSAEventSelect(m_pSocket->m_hsocket, &hEvent,FD_READ|FD_WRITE);
    m_ThreadHandle = (HANDLE)_beginthread(&ThreadProc,0,NULL);
    if(!m_ThreadHandle)
    {
        m_pSocket == NULL;
        return false;
    }
    else
            return m_threadID;


    return false;
}

и сам воркер
Код

void CSocketThread::ThreadProc(LPVOID pVoid,)
{
    CSocketThread  &this_ =  *(CSocketThread*)pVoid;
    WSANETWORKEVENTS NetworkEvents;
    while(1)
    {
        
        DWORD dw_Index = WSAWaitForMultipleEvents(1,this_.m_Event,FALSE,WSA_INFINITE,FALSE);
        if(dw_Index == SOCKET_ERROR)
        {
            this_.m_nLastError = WSAGetLastError();
        }

        WSANETWORKEVENTS NetworkEvents;
        if(SOCKET_ERROR == WSAEnumNetworkEvents(this_.m_Socket[dw_Index - WSA_WAIT_EVENT_0],
            this_.m_Event[dw_Index - WSA_WAIT_EVENT_0],&NetworkEvents))
        {

            if(NetworkEvents.lNetworkEvents & FD_CONNECT)
            {
                obsocket.Connect(SERVERADDR,PORT);
            }
            if(NetworkEvents.lNetworkEvents & FD_READ)
            {
                obsocket.OnRecive();
            }
            if(NetworkEvents.lNetworkEvents & FD_WRITE)
            {
                obsocket.OnSend(opacket);
            }
        }

        WSAResetEvent(this_.m_Event[dw_Index - WSA_WAIT_EVENT_0]);
    }


}

так вот кажвый евент сажаю в поток.
это все что я делаю.
Код

socket_object.OpenSoket(SOCK_STREAM ,9998,"192.168.0.136");
socket_object.Connect("192.168.0.136",9998);
HANDLE hEvent = WSACreateEvent();
_thread.StartThread(&socket_object,hEvent);

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

Автор: ama_kid 9.11.2007, 10:41
выложи хоть минимально обрезанный компилируемый исходник, а то я забодался обтачивать напильником твои огрызки для получения воспроизводимости проблемы... В частности, непонятно что за переменные obsocket и  opacket... Ну и по мелочи, в итоге набегает пара десятков ошибок... Так же не видно функции WSAStartup()...

Автор: knut 9.11.2007, 11:21
ama_kid
obsocket и  opacket это обекты классов CSocket и структуры Paket.
Так же не видно функции WSAStartup()..
есть она точно и не в ней проблемма .

так вот зделав классы сокет и тред.начинаем релизац.клиентакого сокета.
1.создаем сокет.
2.конектимся к серверу и ждем рисива но не какого рисива не получаем.
 вот 2 метода релиз. евентную модель.
Код

bool CSocketThread::StartThread(CSocket *pSocket)
{

    m_pSocket = pSocket;
    m_Event[m_EventTotal] = WSACreateEvent();
    ++m_EventTotal;

    
    WSAEventSelect(m_pSocket->m_hsocket, &m_Event,FD_READ|FD_WRITE);
        m_ThreadHandle = (HANDLE)_beginthread(&ThreadProc,0,NULL);
    if(!m_ThreadHandle)
    {
        m_pSocket == NULL;
        return false;
    }
    else
        return m_threadID;


    
}

 и воркер евентов.
Код

void CSocketThread::ThreadProc(LPVOID pVoid)
{
    CSocketThread  &this_ =  *(CSocketThread*)pVoid;
    WSANETWORKEVENTS NetworkEvents;
    while(1)
    {
        
        DWORD dw_Index =    WSAWaitForMultipleEvents(m_EventTotal,this_.m_Event,FALSE,WSA_INFINITE,FALSE);
        if(dw_Index == SOCKET_ERROR)
        {
            this_.m_nLastError = WSAGetLastError();
        }

        WSANETWORKEVENTS NetworkEvents;
        if(SOCKET_ERROR == WSAEnumNetworkEvents(this_.m_Socket[dw_Index - WSA_WAIT_EVENT_0],
            this_.m_Event[dw_Index - WSA_WAIT_EVENT_0],&NetworkEvents))
        {

            if(NetworkEvents.lNetworkEvents & FD_CONNECT)
            {
                obsocket.Connect(SERVERADDR,PORT);
            }
            if(NetworkEvents.lNetworkEvents & FD_READ)
            {
                obsocket.OnRecive();
            }
            if(NetworkEvents.lNetworkEvents & FD_WRITE)
            {
                obsocket.OnSend(opacket);
            }
        
        }

        WSAResetEvent(this_.m_Event[dw_Index - WSA_WAIT_EVENT_0]);
    }


}

Автор: dumb 9.11.2007, 12:45
Цитата(knut @  9.11.2007,  11:21 Найти цитируемый пост)
m_Event[m_EventTotal] = WSACreateEvent();
о. уже переделал... smile а раньше вообще на неинициализированном m_Event wait делал или таки где-то его инитил? не видно ж...
Цитата(ama_kid @  9.11.2007,  10:41 Найти цитируемый пост)
выложи хоть минимально обрезанный компилируемый исходник
+1

Автор: xvr 9.11.2007, 14:57
Код

 if(SOCKET_ERROR == WSAEnumNetworkEvents(this_.m_Socket[dw_Index - WSA_WAIT_EVENT_0],
            this_.m_Event[dw_Index - WSA_WAIT_EVENT_0],&NetworkEvents))
Может все же SOCKET_ERROR != WSAEnumNetworkEvents ?

Автор: knut 9.11.2007, 15:33
xvr
да этого ифа не доходит 
Код

WSAWaitForMultipleEvents(...)  

ждет ждет но не чего не получает

Автор: xvr 9.11.2007, 16:55
Цитата(knut @ 9.11.2007,  15:33)
xvr
да этого ифа не доходит 
Код

WSAWaitForMultipleEvents(...)  

ждет ждет но не чего не получает

Глупый вопрос - а сервер точно что-то отправляет? Может и получать нечего? 

Автор: knut 9.11.2007, 17:07
xvr
Цитата

Глупый вопрос - а сервер точно что-то отправляет? Может и получать нечего?  

да отправляет 

Автор: xvr 9.11.2007, 17:44
Цитата(knut @ 9.11.2007,  17:07)
xvr
Цитата

Глупый вопрос - а сервер точно что-то отправляет? Может и получать нечего?  

да отправляет

Отправляет просто по факту подключения к нему? Я что то не заметил в исходных кодах, что бы твой клиент что-нибудь посылал серверу при подключении.
Попробуй проверить сервер - подключись к нему например telnet'ом

Автор: knut 10.11.2007, 15:16
картина такая.
создат сокет,конекнулся к серверу и послал пакет пока все нормально.
Код

OpenSoket(SOCK_STREAM ,9998,"192.168.0.136");
Connect("192.168.0.136",9998);
OnSend(paket);

потом вызеваю StartThread(); создаю собтие,создаю новый тред и жду ивента на рисив в созданном потоке.
Код

StartThread(...)
{
    m_pSocket = pSocket;
    m_Event[m_EventTotal] = WSACreateEvent();
    ++m_EventTotal;
    WSAEventSelect(m_pSocket->m_hsocket, &m_Event,FD_READ|FD_WRITE);
        m_ThreadHandle = CreateThread(NULL, 0, &ThreadProc, (LPVOID)this, 0, &m_threadID);
}

ThreadProc(LPVOID pVoid)
{
  CSocketThread  &this_ =  *(CSocketThread*)pVoid;
    WSANETWORKEVENTS NetworkEvents;
    memset(&NetworkEvents, 0, sizeof(NetworkEvents));
    while(1)
    {
                
      DWORD dw_Index = WSAWaitForMultipleEvents(this_.m_EventTotal,this_.m_Event,FALSE,WSA_INFINITE,FALSE);
        //...
    }

}


так вот пакет отправлен,поток создан все что надо это получить рисив тогда когда WSAWaitForMultipleEvents(...) сработает..
но он на WSAWaitForMultipleEvents(..) зависает и не какого ответного ивента на рисив не получает.

p.s
может я че то путаю с организацей? 

Автор: dumb 10.11.2007, 19:03
knut, как думаешь - легко в голове сложить все твои отрывки, попробовать догадаться, как ты реализовал то, что рассказываешь "в прозе", добавить недостающее и оценить правильность организации? - и кто будет заниматься подобной ....?! smile

Автор: xvr 10.11.2007, 19:28
Цитата(knut @ 10.11.2007,  15:16)
картина такая.
создат сокет,конекнулся к серверу и послал пакет пока все нормально.
Код

OpenSoket(SOCK_STREAM ,9998,"192.168.0.136");
Connect("192.168.0.136",9998);
OnSend(paket);


Я что то не нашел в этом коде где ты послал пакет ? Вижу только конект и ожидание приема, передачу чего-либо не вижу.

Автор: knut 10.11.2007, 20:43
dumb
согласен с ясностю поса у меня не так получилось ка xотелос.но я выкладываю всю реализацию выесняется не то. выкладываю куски  опять не то.
мил. человек в чем же дело? 
xvr


Цитата

Я что то не нашел в этом коде где ты послал пакет ? Вижу только конект и ожидание приема, передачу чего-либо не вижу. 


если внимательно посмотреть  на OnSend(paket);  и  пост  номет 1 где реализ. метода OnSend то будет ясность в моем постеsmile
 

Автор: xvr 10.11.2007, 21:12
Цитата(knut @ 10.11.2007,  20:43)
xvr


Цитата

Я что то не нашел в этом коде где ты послал пакет ? Вижу только конект и ожидание приема, передачу чего-либо не вижу. 


если внимательно посмотреть  на OnSend(paket);  и  пост  номет 1 где реализ. метода OnSend то будет ясность в моем постеsmile

Посмотрел, увидел отправку пакета в overlapped режиме, но не увидел ожидания окончания отправки (метод есть, но он не вызывается)
Так же неясно, будут ли валидными буфера с содержимым для передачи и overlapped структура до окончания собственно передачи (а они ДОЛЖНЫ быть валидными). Попробуй для начала отправить пакет в не-overlapped режиме (подай NULL вместо указателя на overlapped структуру в WSASend в OnSend)
Так же можно попробовать принять пакет в не-overlapped режиме. Если заработает - ищи ошибку в реализации overlapped режима, иначе - в приеме/передаче/подсоединении.

Автор: ama_kid 11.11.2007, 00:49
Цитата(knut @  10.11.2007,  20:43 Найти цитируемый пост)
я выкладываю всю реализацию выесняется не то. выкладываю куски  опять не то.
мил. человек в чем же дело? 
Камрад, просто всю реализацию ты так ни разу и не выложил...  Неужто так сложно вырезать из проекта все, что не относится к проблеме, проверить что проблема сохраняется, проект компилируется, запаковать проект в архив и приаттачить к посту? Поверь, скачав архив с проектом, запустив сразу же на исполнение, расставив брейкпойнты на критичные места, получив визуальные данные отладчика и дописав\исправив\удалив необходимые куски кода,  люди нашли бы ответ на твой вопрос гораздо быстрее, чем потратив кучу времени на лепку из твоих кусков кода своего проекта, увидев, что ты забыл выложить объявления каких-то структур и переменных, придумывать свои собственные вместо них и потом еще пытаться найти ответ на необходимый вопрос...
Я вот, к примеру, до сих пор не понял, что за структура такая: PACKET? Может ткнешь меня, где она у тебя тут описана? В штатных виндовых струтктурах у меня её тоже нет...

Автор: knut 11.11.2007, 13:44
вот обрезанный пример где точно есть наличие проблемы.
ормально кмпилитсяsmile
p.s
буду признателен за помпшь

Автор: xvr 12.11.2007, 19:04
Цитата(knut @ 11.11.2007,  13:44)
вот обрезанный пример где точно есть наличие проблемы.
ормально кмпилитсяsmile
p.s
буду признателен за помпшь

Да уж, наличие проблемы есть smile

Код

bool CSocketThread::StartThread(CSocket *pSocket)
{
    m_pSocket = pSocket;
    m_Event[m_EventTotal] = WSACreateEvent();
    ++m_EventTotal;
    
    //recvOv.hEvent = m_Event[m_EventTotal];

    WSAEventSelect(m_pSocket->m_hsocket, m_Event,FD_READ|FD_WRITE);
    m_ThreadHandle = CreateThread(NULL, 0, &ThreadProc, (LPVOID)this, 0, &m_threadID);
...
В WSAEventSelect рекомендуется передавать ОДИН event, а не весь массив целиком smile 2й параметр должен быть m_Event[m_EventTotal-1]

Далее, передача данных:
Код

typedef struct  tagPACKET
{
    friend class CSocket; 
    char        *pBuffer;
    DWORD        buffer1_len;
    WSAOVERLAPPED  overlapped;
    
    
    tagPACKET();
    ~tagPACKET(){}

}PACKET;

tagPACKET::tagPACKET()
{
    
    char *pBuffer = "dlfjk;sldkjf;lksd;fk";
    
}
В дальнейшем данные передаются из PACKET::pBuffer, который не инициализированн, т.к. в конструкторе инициализируется ЛОКАЛЬНАЯ ПЕРЕМЕННАЯ pBuffer, а не поле структуры.

Про == вместо != я уже писал:
Код

if(SOCKET_ERROR == WSAEnumNetworkEvents(this_.m_Socket[dw_Index - WSA_WAIT_EVENT_0],
    this_.m_Event[dw_Index - WSA_WAIT_EVENT_0],&NetworkEvents))


Автор: knut 12.11.2007, 22:24
xvr
спасибо тебе за замечание
но вот исправленный вариант.
но меня убивает то что я не могу понять почему у меня WSAEventSelect е зависает  и  не как не выxодет. smile 
я WSARecive(...)  вызвал в main() е у меня рисив нормально прошел.
а вот посадить его на ивент не как не получается. не пойму то я не так делаю в организации потоковай модели.?

Автор: xvr 12.11.2007, 23:32
Цитата(knut @ 12.11.2007,  22:24)
xvr
спасибо тебе за замечание
но вот исправленный вариант.
но меня убивает то что я не могу понять почему у меня WSAEventSelect е зависает  и  не как не выxодет. smile 
я WSARecive(...)  вызвал в main() е у меня рисив нормально прошел.
а вот посадить его на ивент не как не получается. не пойму то я не так делаю в организации потоковай модели.?

Убери строку
Код

 WSAResetEvent(this_.m_Event[dw_Index - WSA_WAIT_EVENT_0]);

в конце цикла обработки event'ов в CSocketThread::ThreadProc. Этот event автоматически сбрасывается в WSAWaitForMultipleEvents и снова устанавливается в теле обработки, после чего ты его сбрасываешь вручную и в таком состоянии он остается навсегда :(
Кстати, отправка данных по FD_WRITE тоже не будет работать - вот выдержка из описания WSAEventSelect 
Цитата

An FD_WRITE network event is recorded when a socket is first connected with connect/WSAConnect or accepted with accept/WSAAccept, and then after a send fails with WSAEWOULDBLOCK and buffer space becomes available. Therefore, an application can assume that sends are possible starting from the first FD_WRITE network event setting and lasting until a send returns WSAEWOULDBLOCK. After such a failure the application will find out that sends are again possible when an FD_WRITE network event is recorded and the associated event object is set.

Автор: ama_kid 13.11.2007, 12:12
knut
Эээ... как это так?! 
Код
paket.overlapped.hEvent = HANDLE(FD_WRITE);
Как это ты так хитрым мановением руки превращаешь константу в хендл евента? а ведь этот хэндл передается на вход WSASend!
Далее, почему-то везде используются расширения WSA... а тут:
Код

if(SOCKET_ERROR != connect(m_hsocket,(sockaddr *)&dest_addr,sizeof(dest_addr)))
почему-то просто connect. Может надо WSAConnect? Если уж используешь расширения WSA - так используй везде (оно не обязательно, но все-таки). Кстати, МСДН глаголет, что
Цитата
With a nonblocking socket, the connection attempt cannot be completed immediately. In this case, connect will return SOCKET_ERROR, and WSAGetLastError will return WSAEWOULDBLOCK. In this case, there are three possible scenarios:
...
    * If the application is using WSAEventSelect to indicate interest in connection events, then the associated event object will be signaled indicating that the connect operation is complete (successfully or not).
поэтому connect (равно как и WSAConnect) должен вернуть SOCKET_ERROR (насколько я понял, идея - в использовании неблокирующих сокетов), поэтому приведенное сравнение нелогично...
Так же не совсем понятна идея использования массива евентов при вызове WSAEventSelect. В МСДН http://msdn2.microsoft.com/en-us/library/ms741576.aspx:
Цитата
It is not possible to specify different event objects for different network events. The following code will not work; the second call will cancel the effects of the first, and only the FD_WRITE network event will be associated with hEventObject2:
Код

rc = WSAEventSelect(s, hEventObject1, FD_READ);
rc = WSAEventSelect(s, hEventObject2, FD_WRITE); //bad
А следовательно - неоправдано применение и WSAWaitForMultipleEvents. Если бы ты писал сервер-клиента, тогда еще можно понять: один евент на сервер, один - на клиент, но у тебя только клиентская сторона, поэтому достаточно одного событийного объекта...
А зачем ты из процедуры потока ThreadProc напрямую обращаешься к объектам CSocket obsocket и tagPACKET opacket без синхронизации? В таком случае - ты никак не защищен от межпотоковых коллизий и порчи данных... Про утечки памяти при таком использовании потока я уж и не говорю (нет выхода из цикла потоковой функции)...
Ну и самое интересное: при запуске потока ты инициализируешь на обработку евентов сокет m_hsocket объекта CSocket
Код
int res = WSAEventSelect(m_pSocket->m_hsocket, m_Event[m_EventTotal-1],FD_READ|FD_WRITE);
m_ThreadHandle = CreateThread(NULL, 0, &ThreadProc, (LPVOID)this, 0, &m_threadID);
...
а уже в потоке тестируешь на евенты массив неинициализированных и неиспользуемых сокетов this_.m_Socket класса CSocketThread:
Код
if(SOCKET_ERROR != WSAEnumNetworkEvents(this_.m_Socket[dw_Index - WSA_WAIT_EVENT_0],
this_.m_Event[dw_Index - WSA_WAIT_EVENT_0],&NetworkEvents))
несколько странно имхо...
Ну и по мелочи - куча недочетов типа: Функция CSocket::OpenSoket возвращает тип bool, а ты пишешь return m_hsocket типа SOCKET
Вообще, складывается такое ощущение, что ты просто натаскал из разных источников куски кода и пытался придать им работающий вид... 
Цитата
я не могу понять почему у меня WSAEventSelect е зависает  и  не как не выxодет
Кстати, у меня на этом месте не зависает почему-то...

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