使用函数 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
,而不是之后。否则,新数据可能会在调用 recvfrom
和 WSAResetEvent
之间到达,将事件对象设置为有信号。在这种情况下,WSAResetEvent
会将事件设置回 non-signalled,导致您丢失新数据可用的通知。
另外,根据WSAEventSelect
的文档,从socket中读取数据后,如果有更多的数据可供读取,事件会自动设置为signaled again,以表示有更多的数据可以读取数据可用。如果您之后调用 WSAResetEvent
,那么您会将事件设置回 non-signalled,导致您丢失新数据可用的通知。这可能是您在问题中描述的行为的原因。
您应该在 WSAWaitForMultipleEvents
/WaitForMultipleObjectsEx
. See the documentation 之后立即为函数 WSAWaitForMultipleEvents
调用 WSAResetEvent
/ResetEvent
作为代码示例(该示例使用重叠的 I/O不过 WSAEventSelect
。
我正在尝试执行单元测试,以查看使用函数“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
,而不是之后。否则,新数据可能会在调用 recvfrom
和 WSAResetEvent
之间到达,将事件对象设置为有信号。在这种情况下,WSAResetEvent
会将事件设置回 non-signalled,导致您丢失新数据可用的通知。
另外,根据WSAEventSelect
的文档,从socket中读取数据后,如果有更多的数据可供读取,事件会自动设置为signaled again,以表示有更多的数据可以读取数据可用。如果您之后调用 WSAResetEvent
,那么您会将事件设置回 non-signalled,导致您丢失新数据可用的通知。这可能是您在问题中描述的行为的原因。
您应该在 WSAWaitForMultipleEvents
/WaitForMultipleObjectsEx
. See the documentation 之后立即为函数 WSAWaitForMultipleEvents
调用 WSAResetEvent
/ResetEvent
作为代码示例(该示例使用重叠的 I/O不过 WSAEventSelect
。