最近看了完成端口,很迷惑,GetQueuedCompletionStatus(CompletionPort, &BytesTransferred, (LPDWORD)&PerHandleData, (LPOVERLAPPED*)&PerIoData, INFINITE)),其中PerHandleData是在  CreateIoCompletionPort((HANDLE)sClient, CompletionPort, (DWORD)PerHandleData, 0);种关联的,但是PerIoData却没有关联,我看到唯一可能有点线索的大概就是投递WSARecv 参数里面 PerIOData里的WSAOVERLAPPED了,难道完成端口是通过WSAOVERLAPPED找到PerIOData的指针,再返回,这也太智能了,感觉上也很难做到。谢谢解答我是参考这个网址上的程序。http://hi.baidu.com/winnyang/blog/item/d9948a33acf21afe1b4cffd6.html /*    完成端口服务器    接收到客户端的信息,直接显示出来*/#include "winerror.h"
#include "Winsock2.h"
#pragma comment(lib, "ws2_32")#include "windows.h"
#include <iostream>
using namespace std;
/// 宏定义
#define PORT 5050
#define DATA_BUFSIZE 8192#define OutErr(a) cout << (a) << endl \
       << "出错代码:" << WSAGetLastError() << endl \
       << "出错文件:" << __FILE__ << endl   \
       << "出错行数:" << __LINE__ << endl \#define OutMsg(a) cout << (a) << endl;
/// 全局函数定义
///////////////////////////////////////////////////////////////////////
//
// 函数名        : InitWinsock
// 功能描述      : 初始化WINSOCK
// 返回值        : void 
//
///////////////////////////////////////////////////////////////////////
void InitWinsock()
{
// 初始化WINSOCK
WSADATA wsd;
if( WSAStartup(MAKEWORD(2, 2), &wsd) != 0)
{
   OutErr("WSAStartup()");
}
}///////////////////////////////////////////////////////////////////////
//
// 函数名        : BindServerOverlapped
// 功能描述      : 绑定端口,并返回一个 Overlapped 的Listen Socket
// 参数          : int nPort
// 返回值        : SOCKET 
//
///////////////////////////////////////////////////////////////////////
SOCKET BindServerOverlapped(int nPort)
{
// 创建socket 
SOCKET sServer = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);// 绑定端口
struct sockaddr_in servAddr;
servAddr.sin_family = AF_INET;
servAddr.sin_port = htons(nPort);
servAddr.sin_addr.s_addr = htonl(INADDR_ANY);if(bind(sServer, (struct sockaddr *)&servAddr, sizeof(servAddr)) < 0)
{
   OutErr("bind Failed!");
   return NULL;
}// 设置监听队列为200
if(listen(sServer, 200) != 0)
{
   OutErr("listen Failed!");
   return NULL;
}
return sServer;
}
/// 结构体定义
typedef struct
{
    OVERLAPPED Overlapped;
    WSABUF DataBuf;
    CHAR Buffer[DATA_BUFSIZE];
} PER_IO_OPERATION_DATA, * LPPER_IO_OPERATION_DATA;
typedef struct 
{
    SOCKET Socket;
} PER_HANDLE_DATA, * LPPER_HANDLE_DATA;
DWORD WINAPI ProcessIO(LPVOID lpParam)
{
HANDLE CompletionPort = (HANDLE)lpParam;
     DWORD BytesTransferred;
     LPPER_HANDLE_DATA PerHandleData;
     LPPER_IO_OPERATION_DATA PerIoData;while(true)
{   if(0 == GetQueuedCompletionStatus(CompletionPort, &BytesTransferred, (LPDWORD)&PerHandleData, (LPOVERLAPPED*)&PerIoData, INFINITE))
   {
    if( (GetLastError() == WAIT_TIMEOUT) || (GetLastError() == ERROR_NETNAME_DELETED) )
    {
     cout << "closing socket" << PerHandleData->Socket << endl;
    
     closesocket(PerHandleData->Socket);
    
     delete PerIoData;
     delete PerHandleData;
     continue;
    }
    else
    {
     OutErr("GetQueuedCompletionStatus failed!");
    }
    return 0;
   }
  
   // 说明客户端已经退出
   if(BytesTransferred == 0)
   {
    cout << "closing socket" << PerHandleData->Socket << endl;
    closesocket(PerHandleData->Socket);
    delete PerIoData;
    delete PerHandleData;
    continue;
   }   // 取得数据并处理
   cout << PerHandleData->Socket << "发送过来的消息:" << PerIoData->Buffer << endl;   // 继续向 socket 投递WSARecv操作
   DWORD Flags = 0;
   DWORD dwRecv = 0;
   ZeroMemory(PerIoData, sizeof(PER_IO_OPERATION_DATA));
   PerIoData->DataBuf.buf = PerIoData->Buffer;
   PerIoData->DataBuf.len = DATA_BUFSIZE;
   WSARecv(PerHandleData->Socket, &PerIoData->DataBuf, 1, &dwRecv, &Flags, &PerIoData->Overlapped, NULL); 
}return 0;
}void main()
{
InitWinsock();HANDLE CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);// 根据系统的CPU来创建工作者线程
SYSTEM_INFO SystemInfo;
GetSystemInfo(&SystemInfo);for(int i = 0; i < SystemInfo.dwNumberOfProcessors * 2; i++)
{
   HANDLE hProcessIO = CreateThread(NULL, 0, ProcessIO, CompletionPort, 0, NULL);
   if(hProcessIO)
   {
    CloseHandle(hProcessIO);
   }
}// 创建侦听SOCKET
SOCKET sListen = BindServerOverlapped(PORT);
SOCKET sClient;
LPPER_HANDLE_DATA PerHandleData;
     LPPER_IO_OPERATION_DATA PerIoData;
while(true)
{
   // 等待客户端接入
   //sClient = WSAAccept(sListen, NULL, NULL, NULL, 0);
   sClient = accept(sListen, 0, 0);
  
   cout << "Socket " << sClient << "连接进来" << endl;
  
   PerHandleData = new PER_HANDLE_DATA();
   PerHandleData->Socket = sClient;   // 将接入的客户端和完成端口联系起来
   CreateIoCompletionPort((HANDLE)sClient, CompletionPort, (DWORD)PerHandleData, 0);   // 建立一个Overlapped,并使用这个Overlapped结构对socket投递操作
   PerIoData = new PER_IO_OPERATION_DATA();
  
   ZeroMemory(PerIoData, sizeof(PER_IO_OPERATION_DATA));
   PerIoData->DataBuf.buf = PerIoData->Buffer;
   PerIoData->DataBuf.len = DATA_BUFSIZE;   // 投递一个WSARecv操作
   DWORD Flags = 0;
   DWORD dwRecv = 0;
   WSARecv(sClient, &PerIoData->DataBuf, 1, &dwRecv, &Flags, &PerIoData->Overlapped, NULL);
}DWORD dwByteTrans;
PostQueuedCompletionStatus(CompletionPort, dwByteTrans, 0, 0);
closesocket(sListen);
}

解决方案 »

  1.   

    Mark
    正好也在看这个 《windows网络编程技术》第八章中也有介绍
      

  2.   

    这个写法比较怪异,仅仅是把PerIOData当缓冲区使用,投递WSARecv接受数据后,再PostQueuedCompletionStatus进行通知。这样写法完全丢失了完成端口的优势。
      

  3.   

    完成端口采用队列机制,当你使用WSARecv等类似的函数时,参数将会被入队,当GetQueuedCompletionStatus返回后参数出队
      

  4.   

    GetQueuedCompletionStatus会返回OVERLAPPED的指针(WSASend,WSARecv传入的OVERLAPPED),
    而PerIoData包含有一个OVERLAPPED成员,
    所以可以将OVERLAPPED指针转换成PER_IO_OPERATION_DATA的指针PerIoData
    这是设计上的技巧,
    成员结构体指针转换最好是使用CONTAINING_RECORD这只是简单例子,
    很多东西还要扩展
    使用内存池,工作队列
    AcceptEx替换accept
      

  5.   

    谢谢楼上的,
    WSARecv传入的参数都是分开的,都是PER_IO_OPERATION_DATA结构里的值,但是最后却以PER_IO_OPERATION_DATA结构题一起返回,感觉上比较奇怪,完成端口是如何知道怎样填充你的PER_IO_OPERATION_DATA结构的呢
      

  6.   

    WSARecv、WSASend传入的参数类型是OVERLAPPED指针,这是系统内核进行重叠IO操作所需要的,与你的应用无关,你不用去理会GetQueuedCompletionStatus返回这个指针,其设计目的有两条:
    1、告诉你是哪一个IO操作完成了,你可以通过OVERLAPPED指针来判断:哪一个IO操作传入了这个指针,就是哪个IO操作完成了
    2、这个指针我用完了,你可以销毁了至于这个指针所指向的数据内容,与你的应用无关,你不用去理会
    这种机制的适应性很强,对于某些特殊的应用来说,它可以通过维护一个OVERLAPPED指针链表来跟踪每一个IO操作的完成次序等细节
    但对于大多数应用来说,它只关心我收到了什么,发出去了什么,它就没必要去维护一个OVERLAPPED指针链表
    最简单的方法就是将OVERLAPPED指针“扩展”,定义一个包含OVERLAPPED结构的自定义结构CIOCPBuffer
    将IO操作相关的信息,通常包括操作类型、发送或受到的数据的缓冲区等等,一块扔到CIOCPBuffer中去这样,只要每次调用WSARecv、WSASend时创建一个CIOCPBuffer,将操作类型、数据缓冲区设置好,然后将OVERLAPPED成员的指针传入
    而在GetQueuedCompletionStatus得到OVERLAPPED指针后,可以简单地通过OVERLAPPED指针得到CIOCPBuffer指针
    这样就可以简单地获取这次IO的操作类型,以及传输的数据
      

  7.   

    恩,楼上我也是这么想的。今天我测试了一下完成端口,不断的链接 然后关闭  我设置了ReuseAddr 并且关闭了LINGER发现大概连接数到了4000左右的时候会停住 ,不在接受连接,但等待若干秒后又可以继续接受连接 然后又等待又可以接受连接。这正常吗??
      

  8.   

    严重不推荐在IOCP服务器中使用AcceptEx
    ListenSocket放到单独线程中循环WSAAccept即可,不要关联完成端口,完成端口只处理收发包,不处理监听
    IOCP不是为了让你能够响应大量的connect请求——这一点很容易做到,根本不需要使用IOCP机制IOCP的本质是用适量的线程数来处理大量的IO请求,避免因使用大量线程而频繁切换线程上下文数据所导致的系统资源浪费
      

  9.   

    1 只有AcceptEx可以实现套接字重用,当服务器连接数直线上升的时候,套接字开销是很大的。
    2 IOCP本来就是为了完成高并发的连接。它的目的就是为了同时处理成百上千个套接字。
      

  10.   


    一切都要看需求现实的典型需求只有两种:1、无用户状态,短连接应用,例如域名服务器
    2、有用户状态,长连接应用,例如游戏服务器前者需要频繁创建套接字,但IO负担并不大,所以,它适合AcceptEx,但没必要用IOCP
    后者对IO负担很大,但无需频繁创建套接字,所以,它应该使用IOCP,但不必使用AcceptEx所以,偶“严重不推荐在IOCP服务器中使用AcceptEx”
      

  11.   

    感谢楼上两位深夜回帖,”只有AcceptEx可以实现套接字重用 “ ??设置ReUSeADDr不可以吗??我看看书先
      

  12.   

    我觉得不管长连接短连接,只要用户量一大,服务器每次都去new开销都会很大。服务器启动的时候做一个sokcet 池很有必要。至于你说的长连接的问题,我觉得并不影响套接字回收。不管你是长是短,服务器运行稳定后断开的连接数量就很客观了。服务器刚起开阶段连接数本来就会直线上升,运行一段时间连接数将会平稳。至于你用不用AcceptEx那是你的事情了。严重不推荐不代表别人都不用。
      

  13.   

    凡事不要想当然
    你自己用最WSAAccept写个监听程序,再写个客户端开多线程对服务端保持每秒10个以上的连接请求,连续跑几天
    最后统计下连接请求总次数和成功次数,再看看你服务端的内存消耗有多大变化——我做过这样的测试,你有没有???
      

  14.   

    手头上有个10万客户端的服务器,一直运行很正常。你应该测试下new 1万个 SOCKET 和直接从 SOCKET池取 速度和效率的差别。如果你试过就不会这样说了
      

  15.   

    哈哈哈哈,走远点吧偶认真测试过,不是1万个,是100万个,100万次操作速度差距不到10秒
    对于长连接应用而言,使用SOCKET池带来的效率提升非常有限
    相对使用AccpetEx带来的逻辑复杂度而言,可以忽略大概微软对WSAccept的实现,或者对SOCKET资源管理本来就内含了一定的优化措施
    所以,凡事都要试过再下结论!
      

  16.   

    udknight没看明白,PostQueuedCompletionStatus是退出的意思。