我编译运行了《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等数组同理)。如果是这样做的话,如果有大量用户离开,岂不是很耗费资源吗?
正确的做法应该怎么办呢?请高手指点迷津
因为第一个客户端断开,导致socket数组中的第一个和EventArray中的第一个,都成了没用的东西。那么每个数组中就从第二个开始才有效。所以才发生了错误。
如此一来,是不是应该每次断开时,都整理一次Event和Socket数组呢?(Buf等数组同理)。如果是这样做的话,如果有大量用户离开,岂不是很耗费资源吗?
正确的做法应该怎么办呢?请高手指点迷津
#3
struct cli
{
UINT flags; // 一些标记, 用于标识这个客户端的各种状态,包括是否断开/有效等。
SOCKET sock;
WSAEVENT event;
}
cli array[N];
{
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等数组同理)。如果是这样做的话,如果有大量用户离开,岂不是很耗费资源吗?
正确的做法应该怎么办呢?请高手指点迷津
因为第一个客户端断开,导致socket数组中的第一个和EventArray中的第一个,都成了没用的东西。那么每个数组中就从第二个开始才有效。所以才发生了错误。
如此一来,是不是应该每次断开时,都整理一次Event和Socket数组呢?(Buf等数组同理)。如果是这样做的话,如果有大量用户离开,岂不是很耗费资源吗?
正确的做法应该怎么办呢?请高手指点迷津
#3
struct cli
{
UINT flags; // 一些标记, 用于标识这个客户端的各种状态,包括是否断开/有效等。
SOCKET sock;
WSAEVENT event;
}
cli array[N];
{
UINT flags; // 一些标记, 用于标识这个客户端的各种状态,包括是否断开/有效等。
SOCKET sock;
WSAEVENT event;
}
cli array[N];
#4
楼上的是什么意思?
#5
没人帮解决一下?
#6
这么长,自己调试吧
#7
不是的,其实问题很简单,代码也编译的过去。
我只是贴过来为了方便查找错误。
我只是贴过来为了方便查找错误。
#8
来个朋友帮忙一下,谢谢了,绝对给高分
#9
对方断了,wait failed不是很正常么?你需要在这种情况下断开你合那个端的链接啊?这是问题么?
#10
不是的,当断开后,最多应该有一次waitfailed,但是不应该一直failed啊。