关于重叠I/O用户断开后的出现的WSA_WAIT_FAILED

时间:2022-08-20 07:54:08
问题描述:
    
    我编译运行了《Visual C++ 网络游戏建模与实现》中的重叠I/O的示例代码。使用自己编写的客户端进行连接。假设我连接两个客户端,服务器端可以正常accept两个客户端的连接,连接上后,两个客户端发出的信息服务器端均可正常收到。当退出时,问题出现了,当先退出先打开的那个客户端时,后打开的那个客户端再发送消息,服务器端就无法收到了,经过我调试发现,当第一个客户端关闭时,WSAWaitForMultipleEvents就开始返回WSA_WAIT_FAILED。
    我贴上服务器端的两个线程(一个为Accept线程,一个为Work线程)。请高手指点迷津。万分感激。



// 监听线程
UINT ServerListenThread(LPVOID lParam)
{
LOGMESSAGE("服务器端监听中.....\n");

CServerSocket* pServer = (CServerSocket*)lParam;

SOCKADDR_IN ClientAddr;                   // 定义一个客户端得地址结构作为参数
int addr_length=sizeof(ClientAddr);

while(TRUE)
{
if(pServer->dwEventTotal>=WSA_MAXIMUM_WAIT_EVENTS)  // 因为超出了Windows的最大等待事件数量
{
LOGMESSAGE("已达到最大连接数!");
continue;
}

SOCKET sockTemp = WSAAccept(pServer->sockListen,(SOCKADDR*)&ClientAddr,&addr_length, NULL, 0); 
if(sockTemp  == INVALID_SOCKET)
{
LOGMESSAGE("Accept Connection failed!");
continue;
}

pServer->nSockIndex = pServer->GetEmptySocket();    // 从Socket数组中获得一个空闲的socket
pServer->sockAcceptArray[pServer->nSockIndex] = sockTemp;

// 这里可以取得客户端的IP和端口,但是我们只取其中的SOCKET编号
LPCTSTR lpIP =  inet_ntoa(ClientAddr.sin_addr);
UINT nPort = ClientAddr.sin_port;
char strSock[80];
sprintf(strSock,"New Client,SOCKET编号:%d\r\n",pServer->sockAcceptArray[pServer->nSockIndex] );
LOGMESSAGE(strSock);


// 接收客户端连接以后,为每一个连入的SOCKET都初始化建立一个重叠结构
pServer->Flags = 0;
pServer->EventArray[pServer->nSockIndex] = WSACreateEvent();

ZeroMemory(&pServer->AcceptOverlapped[pServer->nSockIndex],sizeof(WSAOVERLAPPED));

char buffer[DATA_BUFSIZE];
ZeroMemory(buffer,DATA_BUFSIZE);

pServer->AcceptOverlapped[pServer->nSockIndex].hEvent = pServer->EventArray[pServer->nSockIndex]; // 关联事件

pServer->DataBuf[pServer->nSockIndex].len = DATA_BUFSIZE;
pServer->DataBuf[pServer->nSockIndex].buf = buffer;



// 投递第一个WSARecv请求,以便开始在套接字上接受数据
if(WSARecv(pServer->sockAcceptArray[pServer->nSockIndex] ,&pServer->DataBuf[pServer->nSockIndex],1,&pServer->dwRecvBytes,&pServer->Flags,
& pServer->AcceptOverlapped[pServer->nSockIndex] ,NULL) == SOCKET_ERROR)
{
if(WSAGetLastError() != WSA_IO_PENDING)    
{
// 返回WSA_IO_PENDING是正常情况,表示IO操作正在进行,不能立即完成
// 如果不是WSA_IO_PENDING错误,就表示操作失败了
LOGMESSAGE("错误:第一次投递Recv操作失败!!此套接字将被关闭!");
closesocket(pServer->sockAcceptArray[pServer->nSockIndex]);
pServer->sockAcceptArray[pServer->nSockIndex] = INVALID_SOCKET;

WSACloseEvent(pServer->EventArray[pServer->nSockIndex]);

continue;
}
}

pServer->dwEventTotal ++;
}

return 0;
}


// 重叠I/O处理线程
UINT OverlappedThread(LPVOID lParam)
{
CServerSocket* pServer = (CServerSocket*)lParam;

while(pServer->bOverlapped)                    // 循环检测事件数组中的事件,并对接收的数据进行处理:)
{
DWORD dwIndex;

// 等候重叠I/O调用结束
// 因为我们把事件和Overlapped绑定在一起,重叠操作完成后我们会接到事件通知
dwIndex = WSAWaitForMultipleEvents(pServer->dwEventTotal,pServer->EventArray,FALSE,INFINITE,FALSE);

if(dwIndex == WSA_WAIT_TIMEOUT)
continue;

if(dwIndex == WSA_WAIT_FAILED)  // 出现监听错误
{
Sleep(200);
continue;
}

// 取得索引值,得知事件的索引号
dwIndex = dwIndex - WSA_WAIT_EVENT_0;

// 获得索引号以后,这个事件已经没有利用价值,重置之
WSAResetEvent(pServer->EventArray[dwIndex]);

// 然后确定当前索引号的SOCKET的重叠请求状态
DWORD dwBytesTransferred;
WSAOVERLAPPED& CurrentOverlapped = pServer->AcceptOverlapped[dwIndex]; // 这里纯粹为了减少代码长度,付给了一个临时变量
SOCKET& sockCurrent = pServer->sockAcceptArray[dwIndex] ;  // 同上

WSAGetOverlappedResult(sockCurrent,&CurrentOverlapped ,
&dwBytesTransferred,FALSE,&pServer->Flags);

// 先检查通信对方是否已经关闭连接
// 如果==0则表示连接已经,则关闭套接字
if(dwBytesTransferred == 0)
{
char strSock[80];
sprintf(strSock,"Client disconnect:SOCKET编号:%d\r\n",pServer->sockAcceptArray[dwIndex] );
LOGMESSAGE(strSock);

closesocket(sockCurrent);
sockCurrent = INVALID_SOCKET;
//WSACloseEvent( EventArray[dwIndex] );

pServer->dwEventTotal--;
continue;
}


// DataBuf中包含接收到的数据,我们发到对话框中给予显示
char strSock[80];
sprintf(strSock,"Socket data SOCKET编号:%d,%s\r\n",sockCurrent,pServer->DataBuf[dwIndex].buf);
LOGMESSAGE(strSock);

// 然后在套接字上投递另一个WSARecv请求(不要晕,注意看,代码和前面第一次WSARecv一样^_^)
pServer->Flags = 0;
ZeroMemory(&CurrentOverlapped,sizeof(WSAOVERLAPPED));

char buffer[DATA_BUFSIZE];
ZeroMemory(buffer,DATA_BUFSIZE);

CurrentOverlapped.hEvent = pServer->EventArray[dwIndex];
pServer->DataBuf[dwIndex].len = DATA_BUFSIZE;
pServer->DataBuf[dwIndex].buf = buffer;

// 开始另外一个WSARecv
if(WSARecv(sockCurrent,&pServer->DataBuf[dwIndex],1,&pServer->dwRecvBytes,&pServer->Flags,
&CurrentOverlapped ,NULL) == SOCKET_ERROR)
{
if(WSAGetLastError() != WSA_IO_PENDING) 
{
LOGMESSAGE("错误:投递Recv操作失败!!此套接字将被关闭!");

closesocket(sockCurrent);
sockCurrent = INVALID_SOCKET;

WSACloseEvent(pServer->EventArray[dwIndex]);

pServer->dwEventTotal--;

continue;
}
}
}

return 0;
}

10 个解决方案

#1


再补充一点:此类的定义部分:


#pragma once
#include <WinSock2.h>
#pragma comment (lib,"ws2_32.lib")

typedef  unsigned (WINAPI *PBEGINTHREADEX_THREADFUNC)( LPVOID lpThreadParameter);
typedef  unsigned *PBEGINTHREADEX_THREADID;
#define LOGMESSAGE(x) printf(x)

class CServerSocket
{
friend UINT ServerListenThread(LPVOID lParam);
friend UINT OverlappedThread(LPVOID lParam);

public:
CServerSocket(void);
~CServerSocket(void);

bool StartupAcceptThread();
int  StartListening(const UINT&);
bool StartupWorkThread();
int StopListening();

private:

UINT m_nPort;
int InitWSASocket();
int GetEmptySocket();
HANDLE m_hAcceptThread;
HANDLE m_hWorkThread;
public:

SOCKET sockListen;

SOCKET sockAcceptArray[WSA_MAXIMUM_WAIT_EVENTS]; // 与客户端通信的SOCKET

WSAEVENT EventArray[WSA_MAXIMUM_WAIT_EVENTS];    // 与重叠结构配套的事件组

WSAOVERLAPPED AcceptOverlapped[WSA_MAXIMUM_WAIT_EVENTS]; // 重叠结构,每个SOCKET操作对应一个

WSABUF DataBuf[WSA_MAXIMUM_WAIT_EVENTS];  // 接收缓冲区,WSARecv的参数,每个SCOKET对应一个

DWORD dwEventTotal,dwRecvBytes,Flags;

int  nSockIndex;            // socket数组的编号

BOOL bOverlapped;       // 是否处理重叠请求
};

#2


我现在有个思路,不知道是否正确

因为第一个客户端断开,导致socket数组中的第一个和EventArray中的第一个,都成了没用的东西。那么每个数组中就从第二个开始才有效。所以才发生了错误。
如此一来,是不是应该每次断开时,都整理一次Event和Socket数组呢?(Buf等数组同理)。如果是这样做的话,如果有大量用户离开,岂不是很耗费资源吗?
正确的做法应该怎么办呢?请高手指点迷津

#3


struct cli
{
UINT flags; // 一些标记, 用于标识这个客户端的各种状态,包括是否断开/有效等。
SOCKET sock;
WSAEVENT event;
}

cli array[N];

#4


楼上的是什么意思?

#5


没人帮解决一下?

#6


这么长,自己调试吧

#7


不是的,其实问题很简单,代码也编译的过去。
我只是贴过来为了方便查找错误。

#8


来个朋友帮忙一下,谢谢了,绝对给高分

#9


对方断了,wait failed不是很正常么?你需要在这种情况下断开你合那个端的链接啊?这是问题么?

#10


不是的,当断开后,最多应该有一次waitfailed,但是不应该一直failed啊。

#1


再补充一点:此类的定义部分:


#pragma once
#include <WinSock2.h>
#pragma comment (lib,"ws2_32.lib")

typedef  unsigned (WINAPI *PBEGINTHREADEX_THREADFUNC)( LPVOID lpThreadParameter);
typedef  unsigned *PBEGINTHREADEX_THREADID;
#define LOGMESSAGE(x) printf(x)

class CServerSocket
{
friend UINT ServerListenThread(LPVOID lParam);
friend UINT OverlappedThread(LPVOID lParam);

public:
CServerSocket(void);
~CServerSocket(void);

bool StartupAcceptThread();
int  StartListening(const UINT&);
bool StartupWorkThread();
int StopListening();

private:

UINT m_nPort;
int InitWSASocket();
int GetEmptySocket();
HANDLE m_hAcceptThread;
HANDLE m_hWorkThread;
public:

SOCKET sockListen;

SOCKET sockAcceptArray[WSA_MAXIMUM_WAIT_EVENTS]; // 与客户端通信的SOCKET

WSAEVENT EventArray[WSA_MAXIMUM_WAIT_EVENTS];    // 与重叠结构配套的事件组

WSAOVERLAPPED AcceptOverlapped[WSA_MAXIMUM_WAIT_EVENTS]; // 重叠结构,每个SOCKET操作对应一个

WSABUF DataBuf[WSA_MAXIMUM_WAIT_EVENTS];  // 接收缓冲区,WSARecv的参数,每个SCOKET对应一个

DWORD dwEventTotal,dwRecvBytes,Flags;

int  nSockIndex;            // socket数组的编号

BOOL bOverlapped;       // 是否处理重叠请求
};

#2


我现在有个思路,不知道是否正确

因为第一个客户端断开,导致socket数组中的第一个和EventArray中的第一个,都成了没用的东西。那么每个数组中就从第二个开始才有效。所以才发生了错误。
如此一来,是不是应该每次断开时,都整理一次Event和Socket数组呢?(Buf等数组同理)。如果是这样做的话,如果有大量用户离开,岂不是很耗费资源吗?
正确的做法应该怎么办呢?请高手指点迷津

#3


struct cli
{
UINT flags; // 一些标记, 用于标识这个客户端的各种状态,包括是否断开/有效等。
SOCKET sock;
WSAEVENT event;
}

cli array[N];

#4


楼上的是什么意思?

#5


没人帮解决一下?

#6


这么长,自己调试吧

#7


不是的,其实问题很简单,代码也编译的过去。
我只是贴过来为了方便查找错误。

#8


来个朋友帮忙一下,谢谢了,绝对给高分

#9


对方断了,wait failed不是很正常么?你需要在这种情况下断开你合那个端的链接啊?这是问题么?

#10


不是的,当断开后,最多应该有一次waitfailed,但是不应该一直failed啊。