有关IOCP发送/接收消息锁非分页内存问题

好吧你狠 2016-07-10 08:13:10
目前在学习IOCP网络模型,编写DEMO实现了简单的服务器端。但是通过客户端模拟发送数据给服务器端过程中,使用任务管理器发现,提交内存每次增加4K。
问题描述:初步认为是系统锁定tcp/ip层的缓冲区。
采用的解决方案:通过查阅资料,大部分提出:服务器端投递overlapped结构,wsabuffer.len =0。有消息到达,GetQueuedCompletionStatus()处理函数中,使用非阻塞的recv()接收数据。但是使用上述方案,通过观察任务管理器中“提交内存”选项,发现依旧存在内存以4K的速度上涨。

想请教各位,上述方案是正确实施方案吗?或者有其他方案可以解决锁非分页内存问题。谢谢了!

...全文
179 3 打赏 收藏 转发到动态 举报
写回复
用AI写文章
3 条回复
切换为时间正序
请发表友善的回复…
发表回复
xian_wwq 2016-07-14
  • 打赏
  • 举报
回复
1.携带数据长度投递没啥不好,对于每个链路,保证每次只有一个投递就可以; 2.server侧必须使用内存池,否则时间长了会产生大量内存碎片;
好吧你狠 2016-07-10
  • 打赏
  • 举报
回复
以下为服务器代码: #include <WinSock2.h> #include <Windows.h> #include <vector> #include <iostream> using namespace std; #pragma comment(lib, "Ws2_32.lib") // Socket编程需用的动态链接库 #pragma comment(lib, "Kernel32.lib") // IOCP需要用到的动态链接库 /** * 结构体名称:PER_IO_DATA * 结构体功能:重叠I/O需要用到的结构体,临时记录IO数据 **/ const int DataBuffSize = 2 * 1024; typedef struct { OVERLAPPED overlapped; WSABUF databuff; char buffer[ DataBuffSize ]; int BufferLen; int operationType; }PER_IO_OPERATEION_DATA, *LPPER_IO_OPERATION_DATA, *LPPER_IO_DATA, PER_IO_DATA; /** * 结构体名称:PER_HANDLE_DATA * 结构体存储:记录单个套接字的数据,包括了套接字的变量及套接字的对应的客户端的地址。 * 结构体作用:当服务器连接上客户端时,信息存储到该结构体中,知道客户端的地址以便于回访。 **/ typedef struct { SOCKET socket; SOCKADDR_STORAGE ClientAddr; }PER_HANDLE_DATA, *LPPER_HANDLE_DATA; enum { OVERLAPPED_RECV = 0, OVERLAPPED_RECVZERO = 1, OVERLAPPED_SEND = 2, OVERLAPPED_SENDZERO = 3, } // 定义全局变量 const int DefaultPort = 6000; vector < PER_HANDLE_DATA* > clientGroup; // 记录客户端的向量组 HANDLE hMutex = CreateMutex(NULL, FALSE, NULL); DWORD WINAPI ServerWorkThread(LPVOID CompletionPortID); DWORD WINAPI ServerSendThread(LPVOID IpParam); // 开始主函数 int main() { // 加载socket动态链接库 WORD wVersionRequested = MAKEWORD(2, 2); // 请求2.2版本的WinSock库 WSADATA wsaData; // 接收Windows Socket的结构信息 DWORD err = WSAStartup(wVersionRequested, &wsaData); if (0 != err) { // 检查套接字库是否申请成功 cerr << "Request Windows Socket Library Error!\n"; system("pause"); return -1; } if(LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {// 检查是否申请了所需版本的套接字库 WSACleanup(); cerr << "Request Windows Socket Version 2.2 Error!\n"; system("pause"); return -1; } // 创建IOCP的内核对象 /** * 需要用到的函数的原型: * HANDLE WINAPI CreateIoCompletionPort( * __in HANDLE FileHandle, // 已经打开的文件句柄或者空句柄,一般是客户端的句柄 * __in HANDLE ExistingCompletionPort, // 已经存在的IOCP句柄 * __in ULONG_PTR CompletionKey, // 完成键,包含了指定I/O完成包的指定文件 * __in DWORD NumberOfConcurrentThreads // 真正并发同时执行最大线程数,一般推介是CPU核心数*2 * ); **/ HANDLE completionPort = CreateIoCompletionPort( INVALID_HANDLE_VALUE, NULL, 0, 0); if (NULL == completionPort) { // 创建IO内核对象失败 cerr << "CreateIoCompletionPort failed. Error:" << GetLastError() << endl; system("pause"); return -1; } // 创建IOCP线程--线程里面创建线程池 // 确定处理器的核心数量 SYSTEM_INFO mySysInfo; GetSystemInfo(&mySysInfo); // 基于处理器的核心数量创建线程 for(DWORD i = 0; i < (mySysInfo.dwNumberOfProcessors * 2); ++i ){ // 创建服务器工作器线程,并将完成端口传递到该线程 HANDLE ThreadHandle = CreateThread(NULL, 0, ServerWorkThread, completionPort, 0, NULL); if(NULL == ThreadHandle) { cerr << "Create Thread Handle failed. Error:" << GetLastError() << endl; return -1; } CloseHandle(ThreadHandle); } // 建立流式套接字 SOCKET srvSocket = socket(AF_INET, SOCK_STREAM, 0); // 绑定SOCKET到本机 SOCKADDR_IN srvAddr; srvAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY); srvAddr.sin_family = AF_INET; srvAddr.sin_port = htons(DefaultPort); int bindResult = bind(srvSocket, (SOCKADDR*)&srvAddr, sizeof(SOCKADDR)); if(SOCKET_ERROR == bindResult) { cerr << "Bind failed. Error:" << GetLastError() << endl; system("pause"); return -1; } // 将SOCKET设置为监听模式 int listenResult = listen(srvSocket, 10); if(SOCKET_ERROR == listenResult) { cerr << "Listen failed. Error: " << GetLastError() << endl; system("pause"); return -1; } // 开始处理IO数据 cout << "本服务器已准备就绪,正在等待客户端的接入...\n"; // 创建用于发送数据的线程 // HANDLE sendThread = CreateThread(NULL, 0, ServerSendThread, 0, 0, NULL); while(true) { PER_HANDLE_DATA * PerHandleData = NULL; SOCKADDR_IN saRemote; int RemoteLen; SOCKET acceptSocket; // 接收连接,并分配完成端,这儿可以用AcceptEx() RemoteLen = sizeof(saRemote); acceptSocket = accept(srvSocket, (SOCKADDR*)&saRemote, &RemoteLen); if(SOCKET_ERROR == acceptSocket) { // 接收客户端失败 cerr << "Accept Socket Error: " << GetLastError() << endl; system("pause"); return -1; } // 创建用来和套接字关联的单句柄数据信息结构 PerHandleData = (LPPER_HANDLE_DATA)GlobalAlloc(GPTR, sizeof(PER_HANDLE_DATA)); // 在堆中为这个PerHandleData申请指定大小的内存 PerHandleData -> socket = acceptSocket; memcpy (&PerHandleData -> ClientAddr, &saRemote, RemoteLen); clientGroup.push_back(PerHandleData); // 将单个客户端数据指针放到客户端组中 // 将接受套接字和完成端口关联 CreateIoCompletionPort((HANDLE)(PerHandleData -> socket), completionPort, (DWORD)PerHandleData, 0); // 开始在接受套接字上处理I/O使用重叠I/O机制 // 在新建的套接字上投递一个或多个异步 // WSARecv或WSASend请求,这些I/O请求完成后,工作者线程会为I/O请求提供服务 // 单I/O操作数据(I/O重叠) LPPER_IO_OPERATION_DATA PerIoData = NULL; PerIoData = (LPPER_IO_OPERATION_DATA)GlobalAlloc(GPTR, sizeof(PER_IO_OPERATEION_DATA)); ZeroMemory(&(PerIoData -> overlapped), sizeof(OVERLAPPED)); PerIoData->databuff.len = 0; PerIoData->databuff.buf = NULL; PerIoData->operationType = OVERLAPPED_RECVZERO; // read DWORD RecvBytes; DWORD Flags = 0; WSARecv(PerHandleData->socket, &(PerIoData->databuff), 1, &RecvBytes, &Flags, &(PerIoData->overlapped), NULL); } system("pause"); return 0; } // 开始服务工作线程函数 DWORD WINAPI ServerWorkThread(LPVOID IpParam) { HANDLE CompletionPort = (HANDLE)IpParam; DWORD BytesTransferred; LPOVERLAPPED IpOverlapped; LPPER_HANDLE_DATA PerHandleData = NULL; LPPER_IO_DATA PerIoData = NULL; DWORD RecvBytes; DWORD Flags = 0; BOOL bRet = false; while(true) { bRet = GetQueuedCompletionStatus(CompletionPort, &BytesTransferred, (PULONG_PTR)&PerHandleData, (LPOVERLAPPED*)&IpOverlapped, INFINITE); if(bRet == 0) { cerr << "GetQueuedCompletionStatus Error: " << GetLastError() << endl; return -1; } // 检查在套接字上是否有错误发生 if(0 == BytesTransferred) { closesocket(PerHandleData->socket); GlobalFree(PerHandleData); GlobalFree(PerIoData); continue; } // 处理方式2:该方法使用会导致锁非分页内存,该如何解决? PerIoData = (LPPER_IO_DATA)CONTAINING_RECORD(IpOverlapped, PER_IO_DATA, overlapped); switch(PerIoData->operationType) { case OVERLAPPED_RECVZERO: { // 此时 PerIoData->databuff.buf = PerIoData->buffer; 缓冲区中有数据,接收该缓冲区数据 char szBuffer[DataSize] = {0}; //memcpy(szBuffer, PerIoData->databuff.buf, strlen(PerIoData->databuff.buf)); //------------------------------------------- int nRecvLen = recv(PerIoData->m_hSocket, szBuffer, DataSize, 0); // 使用的是非阻塞的recv(), 使用该方法就会锁内存 //------------------------------------------- // 接收到0长度缓冲区的overlapped,重新投递 ZeroMemory(&(PerIoData->overlapped), sizeof(OVERLAPPED)); // 清空内存 PerIoData->databuff.len = 0; PerIoData->databuff.buf = NULL; PerIoData->operationType = OVERLAPPED_RECVZERO; // read WSARecv(PerHandleData->socket, &(PerIoData->databuff), 1, &RecvBytes, &Flags, &(PerIoData->overlapped), NULL); break; } default: break; } } return 0; } 客户端测试用例: #include <iostream> #include <cstdio> #include <string> #include <cstring> #include <winsock2.h> #include <Windows.h> using namespace std; #pragma comment(lib, "Ws2_32.lib") // Socket编程需用的动态链接库 SOCKET sockClient; // 连接成功后的套接字 HANDLE bufferMutex; // 令其能互斥成功正常通信的信号量句柄 const int DefaultPort = 6000; #defien SENDBUFFER (1024) int main() { // 加载socket动态链接库(dll) WORD wVersionRequested; WSADATA wsaData; // 这结构是用于接收Wjndows Socket的结构信息的 wVersionRequested = MAKEWORD( 2, 2 ); // 请求2.2版本的WinSock库 int err = WSAStartup( wVersionRequested, &wsaData ); if ( err != 0 ) { // 返回值为零的时候是表示成功申请WSAStartup return -1; } if ( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 2 ) { // 检查版本号是否正确 WSACleanup( ); return -1; } // 创建socket操作,建立流式套接字,返回套接字号sockClient sockClient = socket(AF_INET, SOCK_STREAM, 0); if(sockClient == INVALID_SOCKET) { printf("Error at socket():%ld\n", WSAGetLastError()); WSACleanup(); return -1; } // 将套接字sockClient与远程主机相连 // int connect( SOCKET s, const struct sockaddr* name, int namelen); // 第一个参数:需要进行连接操作的套接字 // 第二个参数:设定所需要连接的地址信息 // 第三个参数:地址的长度 SOCKADDR_IN addrSrv; addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); // 本地回路地址是127.0.0.1; addrSrv.sin_family = AF_INET; addrSrv.sin_port = htons(DefaultPort); if(SOCKET_ERROR == connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR))) { return -1; } // 为测试,此时可以使用循环来代替,即一个客户端定时向服务器发送消息 while(true) { char szSendBuferr[SENDBUFFER] = {"\nAttention: A Client has enter...\n"}; send(sockClient, szSendBuffer, SENDBUFFER, 0); // 通过该时间可以快速查看内存泄漏问题 sleep(100); } system("pause"); return 0; }

18,357

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC 网络编程
c++c语言开发语言 技术论坛(原bbs)
社区管理员
  • 网络编程
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧