以前没怎么研究winsock通信,基本上用WSAAsyncSelect来实现socket通信,但目前需要实现一个TCP Server,客户端1000左右,查了些资料,感觉用IOCP来实现比较好,但google了很多讲IOCP的内容,还是不是很明白,读了有人根据Network Programming for MS Window 上的代码修改的一个简单IOCP代码,也看了codeproject上的一些实现,感觉根据Network Programming for MS Window 的那个代码相对能理解IOCP的基本思想,可是没有讲服务器端如何将主动要发送的包发送给某个客户端,以及如何实现程序的安全退出, codeproject上的例子,感觉越看越糊涂,另外google的一些文章说,还需要有心跳检测,判断客户端断开连接,说需要另开线程来实现。
小弟不才,感觉对IOCP的理解实在不清晰,恳求各位兄台解惑,另希望能给代码示例,让小弟好好学习一下。
我附上在网上找到的由Network Programming for MS Window 而来的一个IOCP Demo代码。
include <iostream>
#include <tchar.h>#include <winsock2.h>
#pragma comment (lib,"ws2_32.lib")#define PORT 5150
#define DATA_BUFSIZE 8192// 单I/O操作数据
typedef struct
{
OVERLAPPED Overlapped;
WSABUF DataBuf;
CHAR Buffer[DATA_BUFSIZE];
DWORD BytesSEND;
DWORD BytesRECV;
}PER_IO_OPERATION_DATA, *LPPER_IO_OPERATION_DATA;// 单句柄数据
// 和这个句柄关联的其他有用信息,尽管放在这里面吧
typedef struct
{
SOCKET Socket;
}PER_HANDLE_DATA, *LPPER_HANDLE_DATA;// 完成端口工作者线程
DWORD WINAPI ServerWorkerThread(LPVOID CompletionPortID)
{
HANDLE CompletionPort = (HANDLE)CompletionPortID;
DWORD BytesTransferred;
LPPER_HANDLE_DATA PerHandleData;
LPPER_IO_OPERATION_DATA PerIoData;
DWORD SendBytes, RecvBytes;
DWORD Flags; while(TRUE)
{
if (GetQueuedCompletionStatus(CompletionPort, &BytesTransferred,
(LPDWORD)&PerHandleData, (LPOVERLAPPED *)&PerIoData, INFINITE) == 0)
{
_tprintf(_T("GetQueuedCompletionStatus failed with error %d\n"), GetLastError());
return 0;
} // 先检查一下,看看是否在套接字上已有错误发生
// 如果发生错误就关闭套接字并清除与套接字关联的SOCKET_INFORMATION结构
if (BytesTransferred == 0)
{
_tprintf(_T("Closing socket %d\n"), PerHandleData->Socket); if (closesocket(PerHandleData->Socket) == SOCKET_ERROR)
{
_tprintf(_T("closesocket() failed with error %d\n"), WSAGetLastError());
return 0;
} GlobalFree(PerHandleData);
GlobalFree(PerIoData);
continue;
} // 检查BytesRECV是否为0
// 如果为0,表示WSARecv()成功,更新BytesRECV为BytesTransferred的值
if (PerIoData->BytesRECV == 0)
{
PerIoData->BytesRECV = BytesTransferred;
PerIoData->BytesSEND = 0;
}
else
{
PerIoData->BytesSEND += BytesTransferred;
} if (PerIoData->BytesRECV > PerIoData->BytesSEND)
{
ZeroMemory(&(PerIoData->Overlapped), sizeof(OVERLAPPED)); PerIoData->DataBuf.buf = PerIoData->Buffer + PerIoData->BytesSEND;
PerIoData->DataBuf.len = PerIoData->BytesRECV - PerIoData->BytesSEND; if (WSASend(PerHandleData->Socket, &(PerIoData->DataBuf), 1, &SendBytes, 0,
&(PerIoData->Overlapped), NULL) == SOCKET_ERROR)
{
if (WSAGetLastError() != ERROR_IO_PENDING)
{
printf("WSASend() failed with error %d\n", WSAGetLastError());
return 0;
}
}
}
else
{
PerIoData->BytesRECV = 0; Flags = 0;
ZeroMemory(&(PerIoData->Overlapped), sizeof(OVERLAPPED)); PerIoData->DataBuf.len = DATA_BUFSIZE;
PerIoData->DataBuf.buf = PerIoData->Buffer; if (WSARecv(PerHandleData->Socket, &(PerIoData->DataBuf), 1, &RecvBytes, &Flags,
&(PerIoData->Overlapped), NULL) == SOCKET_ERROR)
{
if (WSAGetLastError() != ERROR_IO_PENDING)
{
printf("WSARecv() failed with error %d\n", WSAGetLastError());
return 0;
}
}
}
}
}int _tmain(int argc, _TCHAR* argv[])
{
SOCKADDR_IN InternetAddr;
SOCKET Listen;
SOCKET Accept;
HANDLE CompletionPort;
SYSTEM_INFO SystemInfo;
LPPER_HANDLE_DATA PerHandleData;
LPPER_IO_OPERATION_DATA PerIoData;
DWORD i;
DWORD RecvBytes;
DWORD Flags;
DWORD ThreadID;
WSADATA wsaData;
DWORD Ret; // 加载 Winsock 2.2
if ((Ret = WSAStartup(0x0202, &wsaData)) != 0)
{
_tprintf(_T("WSAStartup failed with error %d\n"), Ret);
return 1;
} // 1) 创建一个I/O完成端口 if ((CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0)) == NULL)
{
_tprintf(_T("CreateIoCompletionPort failed with error: %d\n"), GetLastError());
return 1;
} // 2) 获取CPU个数
GetSystemInfo(&SystemInfo); // 3) 根据CPU个数创建工作器线程 (根据经验而得公式: 线程数 = CPU数 * 2 + 2)
for(i = 0; i < (SystemInfo.dwNumberOfProcessors * 2 + 2); i++)
{
HANDLE ThreadHandle; // 创建一个工作器线程并将完成端口传递到该线程
if ((ThreadHandle = CreateThread(NULL, 0, ServerWorkerThread, CompletionPort,
0, &ThreadID)) == NULL)
{
_tprintf(_T("CreateThread() failed with error %d\n"), GetLastError());
return 1;
} CloseHandle(ThreadHandle);
} // 4) 准备好一个监听套接字,在端口5150上监听进入的连接请求。
// 创建一个监听套接字
if ((Listen = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0,
WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET)
{
_tprintf(_T("WSASocket() failed with error %d\n"), WSAGetLastError());
return 1;
} InternetAddr.sin_family = AF_INET;
InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY);
InternetAddr.sin_port = htons(PORT); // 绑定
if (bind(Listen, (PSOCKADDR) &InternetAddr, sizeof(InternetAddr)) == SOCKET_ERROR)
{
_tprintf(_T("bind() failed with error %d\n"), WSAGetLastError());
return 1;
} // 监听
if (listen(Listen, 5) == SOCKET_ERROR)
{
_tprintf(_T("listen() failed with error %d\n"), WSAGetLastError());
return 1;
} while(TRUE)
{
// 接收连接并分配完成端口
// 这儿可以用AcceptEx来代替,以创建可伸缩的Winsock应用程序
if ((Accept = WSAAccept(Listen, NULL, NULL, NULL, 0)) == SOCKET_ERROR)
{
_tprintf(_T("WSAAccept() failed with error %d\n"), WSAGetLastError());
return 1;
} // 6) 创建用来和套接字关联的单句柄数据信息结构
if ((PerHandleData = (LPPER_HANDLE_DATA) GlobalAlloc(GPTR, 
sizeof(PER_HANDLE_DATA))) == NULL)
{
_tprintf(_T("GlobalAlloc() failed with error %d\n"), GetLastError());
return 1;
} // 7) 将接受套接字和完成端口关联起来
_tprintf(_T("Socket number %d connected\n"), Accept);
PerHandleData->Socket = Accept; if (CreateIoCompletionPort((HANDLE)Accept, CompletionPort, (ULONG_PTR)PerHandleData, 0) == NULL)
{
_tprintf(_T("CreateIoCompletionPort failed with error %d\n"), GetLastError());
return 1;
} // 8) 开始在接受套接字上处理I/O
// 使用重叠I/O机制,在新建的套接字上投递一个或多个异步
// WSARecv 或 WSASend请求。这些I/O请求完成后,工作者线程
// 会为I/O请求提供服务,之后就可以坐享其成了 // 单I/O操作数据
if ((PerIoData = (LPPER_IO_OPERATION_DATA) GlobalAlloc(GPTR, sizeof(PER_IO_OPERATION_DATA))) == NULL)
{
_tprintf(_T("GlobalAlloc() failed with error %d\n"), GetLastError());
return 1;
} ZeroMemory(&(PerIoData->Overlapped), sizeof(OVERLAPPED));
PerIoData->BytesSEND = 0;
PerIoData->BytesRECV = 0;
PerIoData->DataBuf.len = DATA_BUFSIZE;
PerIoData->DataBuf.buf = PerIoData->Buffer; Flags = 0;
if (WSARecv(Accept, &(PerIoData->DataBuf), 1, &RecvBytes, &Flags,
&(PerIoData->Overlapped), NULL) == SOCKET_ERROR)
{
if (WSAGetLastError() != ERROR_IO_PENDING)
{
_tprintf(_T("WSARecv() failed with error %d\n"), WSAGetLastError());
return 1;
}
}
} return 0;
}

解决方案 »

  1.   

    如果是主动发出数据,可以主动投递一个WSASend.
    要安全退出,主要是IO正在操作时不能退出,必须等到Overlapped IO完成后才能退出,还有资源清理时得注意。
      

  2.   

    IOCP只管IO,不会收到事件选择式的那种FD_CLOSE事件。
    简单的做法,就是要的投递一个WSASend或是WSARead,来检测是否断线,如果断线,这些异步操作会返回。
      

  3.   

    你在codeproject上找到的完成端口类应该已经提供了安全退出的办法,不然他还敢发到codeproject上么?主动发数据,一般就是wsasend,但是要注意线程互斥的问题,因为你要wsasend的那个socket有可能刚刚被关闭。在线程互斥的时候,要特别留意线程死锁的问题心跳包,定时地发个数据包ping一下,无论是客户端还是服务器主动发起都可以,是必须的。
      

  4.   

    我看了一些codeproject上的IOCP的代码,好像不一定非要发心跳包的
      

  5.   

    我是新人请问,codeproject是什么地方啊,能否具体点 谢谢