使用函数 WaitForMultipleObjects (msdn) 等待套接字

Waiting on sockets using the function WaitForMultipleObjects (msdn)

我正在尝试执行单元测试,以查看使用函数“WaitForMultipleObjects”是否可以与套接字一起正常工作。为此,我实现了一个简单的 UDP 服务器-客户端协议。服务器绑定他的套接字并等待使用函数“WaitForMultipleObjects”从接收器接收数据。一旦服务器从客户端接收到数据,它就会显示它,然后等待 5 秒。这里的问题是,如果客户端在这五秒钟内尝试发送 2 条消息,则显示第一个,而第二个将永远阻止函数“WaitForMultipleObjects”。我知道我可以使用“Select”函数来做到这一点,因为套接字是我正在等待的唯一对象,但这只是一个单元测试。在我的真实项目中,我需要同时等待套接字和另一个对象类型,即 windows 事件(类型为 HANDLE)。这就是我尝试在套接字上使用“WaitForMultipleObjects”的原因。

这是服务器代码:

/*
    Simple UDP Server
*/

#include<winsock2.h>
#include<windows.h>
#include <ws2tcpip.h>
#include<stdio.h>
#include<iostream>

#pragma comment(lib,"ws2_32.lib") //Winsock Library

#define BUFLEN 512  //Max length of buffer
#define PORT 8888   //The port on which to listen for incoming data





int main()
{
    SOCKET s;
    struct sockaddr_in server, si_other;
    int slen , recv_len;
    char buf[BUFLEN];
    WSADATA wsa;
    HANDLE SEvent;
    

    slen = sizeof(si_other) ;
    
    //Initialise winsock
    printf("\nInitialising Winsock...");
    if (WSAStartup(MAKEWORD(2,2),&wsa) != 0)
    {
        printf("Failed. Error Code : %d",WSAGetLastError());
        exit(EXIT_FAILURE);
    }
    printf("Initialised.\n");
    
    //Create a socket
    if((s = socket(AF_INET , SOCK_DGRAM , 0 )) == INVALID_SOCKET)
    {
        printf("Could not create socket : %d" , WSAGetLastError());
    }
    printf("Socket created.\n");
    
    //Prepare the sockaddr_in structure
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = INADDR_ANY;
    server.sin_port = htons( PORT );
    
    //Bind
    if( bind(s ,(struct sockaddr *)&server , sizeof(server)) == SOCKET_ERROR)
    {
        printf("Bind failed with error code : %d" , WSAGetLastError());
        exit(EXIT_FAILURE);
    }


    puts("Bind done");


    SEvent = WSACreateEvent();
    WSAEventSelect( s, SEvent, FD_READ);
    //keep listening for data
    while(1)
    {
        printf("Waiting for data...");
        fflush(stdout);
        
        //clear the buffer by filling null, it might have previously received data
        memset(buf,'[=10=]', BUFLEN);

            INT r = WaitForMultipleObjectsEx(1,&SEvent,FALSE,INFINITE,TRUE);
            if( r == WAIT_OBJECT_0)
            {
                if (recvfrom(s, buf, BUFLEN, 0, (struct sockaddr *) &si_other, &slen) == SOCKET_ERROR)
                {
                    printf("recvfrom() failed with error code : %d" , WSAGetLastError());
                    exit(EXIT_FAILURE);
                }

                //print details of the client/peer and the data received
                printf("Received packet from %s:%d\n", inet_ntoa(si_other.sin_addr), ntohs(si_other.sin_port));
                printf("Data: %s\n" , buf);

                //now reply the client with the same data
                if (sendto(s, buf, recv_len, 0, (struct sockaddr*) &si_other, slen) == SOCKET_ERROR)
                {
                    printf("sendto() failed with error code : %d" , WSAGetLastError());
                    exit(EXIT_FAILURE);
                }
                ResetEvent(SEvent);
                Sleep(5000);
            }
            else
            {
                std::cerr<<"WaitForMultipleObject() Error ( "<<GetLastError()<<" )"<<std::endl;
                exit(0);
            }
        
    }

    closesocket(s);
    WSACleanup();
    
    return 0;
}

这是客户端代码:


/*
    Simple udp client
*/
#include<stdio.h>
#include<winsock2.h>

#pragma comment(lib,"ws2_32.lib") //Winsock Library

#define SERVER "127.0.0.1"  //ip address of udp server
#define BUFLEN 512  //Max length of buffer
#define PORT 8888   //The port on which to listen for incoming data

int main(void)
{
    struct sockaddr_in si_other;
    int s, slen=sizeof(si_other);
    char buf[BUFLEN];
    char message[BUFLEN];
    WSADATA wsa;

    //Initialise winsock
    printf("\nInitialising Winsock...");
    if (WSAStartup(MAKEWORD(2,2),&wsa) != 0)
    {
        printf("Failed. Error Code : %d",WSAGetLastError());
        exit(EXIT_FAILURE);
    }
    printf("Initialised.\n");
    
    //create socket
    if ( (s=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == SOCKET_ERROR)
    {
        printf("socket() failed with error code : %d" , WSAGetLastError());
        exit(EXIT_FAILURE);
    }
    
    //setup address structure
    memset((char *) &si_other, 0, sizeof(si_other));
    si_other.sin_family = AF_INET;
    si_other.sin_port = htons(PORT);
    si_other.sin_addr.S_un.S_addr = inet_addr(SERVER);
    
    //start communication
    while(1)
    {
        printf("Enter message : ");
        gets(message);
        
        //send the message
        if (sendto(s, message, strlen(message) , 0 , (struct sockaddr *) &si_other, slen) == SOCKET_ERROR)
        {
            printf("sendto() failed with error code : %d" , WSAGetLastError());
            exit(EXIT_FAILURE);
        }
        
        //receive a reply and print it
        //clear the buffer by filling null, it might have previously received data
        /*memset(buf,'[=11=]', BUFLEN);
        //try to receive some data, this is a blocking call
        if (recvfrom(s, buf, BUFLEN, 0, (struct sockaddr *) &si_other, &slen) == SOCKET_ERROR)
        {
            printf("recvfrom() failed with error code : %d" , WSAGetLastError());
            exit(EXIT_FAILURE);
        }
        
        puts(buf);*/
    }

    closesocket(s);
    WSACleanup();

    return 0;
}

注意:我没有写所有的代码。我使用了已经编写好的代码(来自互联网),但做了一些修改。

有谁知道如何解决这个问题? (在套接字上正确使用“WaitForMultipleObjects”)

The problem here that if the client try to send 2 messages during this five seconds, the first one is displayed while the second one will clock the function "WaitForMultipleObjects" forever.

您的服务器代码有 race condition.

你应该打电话给 WSAResetEvent/ResetEvent before calling recvfrom,而不是之后。否则,新数据可能会在调用 recvfromWSAResetEvent 之间到达,将事件对象设置为有信号。在这种情况下,WSAResetEvent 会将事件设置回 non-signalled,导致您丢失新数据可用的通知。

另外,根据WSAEventSelect的文档,从socket中读取数据后,如果有更多的数据可供读取,事件会自动设置为signaled again,以表示有更多的数据可以读取数据可用。如果您之后调用 WSAResetEvent,那么您会将事件设置回 non-signalled,导致您丢失新数据可用的通知。这可能是您在问题中描述的行为的原因。

您应该在 WSAWaitForMultipleEvents/WaitForMultipleObjectsEx. See the documentation 之后立即为函数 WSAWaitForMultipleEvents 调用 WSAResetEvent/ResetEvent 作为代码示例(该示例使用重叠的 I/O不过 WSAEventSelect