#include <WinSock2.h> #include <windows.h> #pragma comment(lib, "WS2_32.lib") //监听端口 #define PORT 1688 //缓冲区大小 #define BUFFER_SIZE 260 typedef struct PER_IO_DATA { WSAOVERLAPPED Overlapped; SOCKET hClient; //客户端SOCKET,必须,没有会很麻烦,因为你不知道是哪个客户端连过来的。 WSABUF wsaBuf; //接收数据缓冲区,必须,和szMsg对应使用,很方便。 DWORD dwFlags, //Flags标志, dwByteRecv; //接收的字节数 CHAR szMsg[BUFFER_SIZE]; //存放数据的缓冲区,必须,要不你得自己想办法把数据找出来~~ }PER_IO_DATA, *PPER_IO_DATA; //工作线程 DWORD WINAPI ServerThread(LPVOID lParam); int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { WORD wRequestVersion = MAKEWORD(2, 2); WSADATA wsaData; WSAStartup(wRequestVersion, &wsaData); // 创建完成端口 HANDLE hIocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); SYSTEM_INFO stSystemInfo; GetSystemInfo(&stSystemInfo); // 根据 当前CPU的数量 * 2 创建工作者线程 for (UINT i = 0; i < 2 * stSystemInfo.dwNumberOfProcessors; i++) { CloseHandle(CreateThread(NULL, 0, ServerThread, (LPVOID)hIocp, 0, NULL)); } // 创建SOCKET协议为IP4 SOCKET hSocket = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, NULL, WSA_FLAG_OVERLAPPED); // 创建之后绑定到端口 SOCKADDR_IN stAddr; ZeroMemory(&stAddr, sizeof(stAddr)); stAddr.sin_family = AF_INET; stAddr.sin_addr.s_addr = htonl(ADDR_ANY); stAddr.sin_port = htons(PORT); bind(hSocket, (PSOCKADDR)&stAddr, sizeof(stAddr)); // 监听端口,准备开始服务 listen(hSocket, 5); //下面开始接收连接了,实际中,应该另开一个接收线程,以免阻塞了UI消息,这里我们没有UI,就直接放在主线程里了。 while (TRUE) { // 接受连接 SOCKET hClient = WSAAccept(hSocket, NULL, NULL, NULL, 0); PPER_IO_DATA pPerIoData = (PPER_IO_DATA)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(PER_IO_DATA)); pPerIoData->wsaBuf.buf = pPerIoData->szMsg; pPerIoData->wsaBuf.len = BUFFER_SIZE; pPerIoData->hClient = hClient; // 和完成端口绑定,并把客户端套接字作为完成KEY传入 //这个函数调用成功后的返回值是hIocp,千万不能把它CloseHandle,要不将永远接收不到系统通知。 CreateIoCompletionPort((HANDLE)hClient, hIocp, (DWORD)pPerIoData, 0); // 接收数据,触发IO端口工作 WSARecv(hClient, &pPerIoData->wsaBuf, 1, &pPerIoData->dwByteRecv, &pPerIoData->dwFlags, &pPerIoData->Overlapped, NULL); } // 投放一个退出标志,告诉工作线程,现在要退出了。 // 在这个例子中,程序永远也不会执行到这一步,因为上面的接收循环中没有错误处理,永远不会跳来到这里执行 // 为了程序的完整性,也加上了。用来示意服务器主动结束服务的方法。 PostQueuedCompletionStatus(hIocp, 0xFFFFFFFF, 0, NULL); CloseHandle(hIocp); closesocket(hSocket); WSACleanup(); return 0; } // 工作者线程 DWORD WINAPI ServerThread(LPVOID lParam) { HANDLE hIocp = (HANDLE)lParam; DWORD dwByteTransferred; //如果成功接收,收返回的是接收到的数据 PPER_IO_DATA pPerIoData = NULL; //用来指向在主线程中HeapAlloc创建的空间 LPOVERLAPPED pOverlapped = NULL; SOCKET hClient; while (TRUE) { BOOL bOK = GetQueuedCompletionStatus(hIocp, &dwByteTransferred, (LPDWORD)&pPerIoData, &pOverlapped, 300); if (bOK) //函数调用成功,说明接收到数据了。 { hClient = pPerIoData->hClient; if (0xFFFFFFFF == dwByteTransferred) //如果是退出标志,直接退出线程。 break; if (0 == dwByteTransferred) //函数虽然调用成功,但是发生错误,则关闭客户连接,再重新等待。 { closesocket(hClient); //关闭客户端连接 continue; } else //真正的调用成功了 { pPerIoData->wsaBuf.len = dwByteTransferred; //设置缓冲区大小 WSASend(hClient, &pPerIoData->wsaBuf, 1, NULL, 0, NULL, NULL); //把数据原样发回 // 继续接收 pPerIoData->wsaBuf.len = BUFFER_SIZE; // 重新设置缓冲区 WSARecv(hClient, &pPerIoData->wsaBuf, 1, &pPerIoData->dwByteRecv, &pPerIoData->dwFlags, &pPerIoData->Overlapped, NULL); } } else //如果失败,则查看失败原因 { DWORD dwError = GetLastError(); switch (dwError) { case WAIT_TIMEOUT: //如果是因为超时,则继续等待 continue; default: break; } } } closesocket(hClient); //关闭客户端连接 HeapFree(GetProcessHeap(), HEAP_ZERO_MEMORY, pPerIoData); //释放内存 return (0); }