dlutyuanhongl(乱石铺街)的BLOG

VC知识库BLOG 首页 新随笔 联系 聚合 登录
  11 Posts :: 1 Stories :: 72 Comments :: 2 Trackbacks

留言簿(8)

随笔分类

随笔档案

文章分类

文章档案

相册

搜索

最新评论

阅读排行榜

评论排行榜

    最近要做一个网络方面的小东东,基于C/S模式的。都说IOCP可以使系统达到最佳的性能,因此我就比划了两下,献丑了。抄书开始。
    从本质上说,完成端口模型要求创建一个windows完成端口对象,该对象通过指定数量的线程,对重叠I/O请求进行管理,以便为已经完成的重叠I/O请求提供服务。
    首先要创建一个I/O完成端口对象,用它面向任意数量的套接字句柄,管理多个I/O请求。调用以下函数创建完成端口对象:

HANDLE CreateIoCompletionPort(
  HANDLE FileHandle,// 同IOCP关联在一起的套接字句柄
  HANDLE ExistingCompletionPort,// IOCP句柄
  ULONG_PTR CompletionKey,        // 完成健
  DWORD NumberOfConcurrentThreads // 在IOCP上,同时允许执行的线程数量
);

    该函数有两个作用:
    (1)创建一个完成端口对象
    (2)将一个句柄同完成端口关联到一起
    
    然后就要创建一定数量的工作者线程,以便在套接字的I/O请求投递给完成端口后,为完成端口提供服务。写文字描述很烦,还是看代码吧:

// NetServer3.cpp : Defines the entry point for the console application.
//

#include 
"stdafx.h"
#include 
"NetServer3.h"

#include 
<winsock2.h>
#pragma comment(lib, 
"ws2_32.lib")

#include 
<iostream>
using namespace std;

//////////////////////////////////////////////////////////////////////////

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

//////////////////////////////////////////////////////////////////////////

// 单句柄数据
typedef struct tagPER_HANDLE_DATA
{
    SOCKET Socket;
    SOCKADDR_STORAGE ClientAddr;
    
// 将和这个句柄关联的其他有用信息,尽管放在这里面吧
}
PER_HANDLE_DATA, *LPPER_HANDLE_DATA;

// 但I/O操作数据
typedef struct tagPER_IO_DATA
{
    OVERLAPPED Overlapped;
    WSABUF DataBuf;
    
char buffer[1024];
    
int BufferLen;
    
int OperationType;   // 可以作为读写的标志,为简单,我忽略了
}
PER_IO_DATA, *LPPER_IO_DATA;

DWORD WINAPI ServerWorkerThread(LPVOID lpParam);

/////////////////////////////////////////////////////////////////////////////
// The one and only application object

CWinApp theApp;

using namespace std;

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
    
int nRetCode = 0;

    
// initialize MFC and print and error on failure
    if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
    
{
        
// TODO: change error code to suit your needs
        cerr << _T("Fatal Error: MFC initialization failed"<< endl;
        nRetCode 
= 1;
    }

    
else
    
{
        
// TODO: code your application's behavior here.
        CString strHello;
        strHello.LoadString(IDS_HELLO);
        cout 
<< (LPCTSTR)strHello << endl;
    }


//////////////////////////////////////////////////////////////////////////

    HANDLE CompletionPort;
    WSADATA wsd;
    SYSTEM_INFO SystemInfo;
    SOCKADDR_IN InternetAddr;
    SOCKET Listen;

    
// 加载WinSock2.2
    WSAStartup(MAKEWORD(22), &wsd);

    
// 1.创建一个I/O完成端口
    CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE,
                                            NULL,
                                            
0,
                                            
0);

    
// 2.确定系统中有多少个处理器
    GetSystemInfo(&SystemInfo);

    
// 3.基于系统中可用的处理器数量创建工作器线程
    for (int i = 0; i < SystemInfo.dwNumberOfProcessors; ++i)
    
{
        HANDLE ThreadHandle;

        
// 创建一个服务器的工作器线程,并将完成端口传递到该线程
        ThreadHandle = CreateThread(NULL,
                                    
0,
                                    ServerWorkerThread,
                                    CompletionPort,
                                    
0,
                                    NULL);

        CloseHandle(ThreadHandle);
    }


    
// 4.创建一个监听套接字,以下的套路都是固定的。
    Listen = WSASocket(AF_INET,
                       SOCK_STREAM,
                       
0,
                       NULL,
                       
0,
                       WSA_FLAG_OVERLAPPED);

    InternetAddr.sin_family 
= PF_INET;
    InternetAddr.sin_port 
= htons(5000);
    InternetAddr.sin_addr.s_addr 
= htonl(INADDR_ANY);

    bind(Listen, (SOCKADDR
*)&InternetAddr, sizeof(InternetAddr));

    listen(Listen, 
5);

    BOOL b 
= TRUE;

    
while (b)
    
{
        PER_HANDLE_DATA 
* PerHandleData = NULL;
        SOCKADDR_IN saRemote;
        SOCKET Accept;
        
int RemoteLen;

        
// 5.接收连接,并分配完成端口,这儿可以用AcceptEx来代替,以创
         // 建可伸缩的Winsock应用程序。

        RemoteLen = sizeof(saRemote);
        Accept 
= accept(Listen, (SOCKADDR*)&saRemote, &RemoteLen);

        
// 6.创建用来和套接字关联的单句柄数据信息结构
        PerHandleData = (LPPER_HANDLE_DATA)GlobalAlloc(GPTR, 
                                                       
sizeof(PER_HANDLE_DATA));

        cout 
<< "Socket number " << Accept << " connected" << endl;

        PerHandleData
->Socket = Accept;
        memcpy(
&PerHandleData->ClientAddr, &saRemote, RemoteLen);

        
// 7.将接受套接字和完成端口关联起来
        CreateIoCompletionPort((HANDLE)Accept,
                               CompletionPort,
                               (DWORD)PerHandleData,
                               
0);

        
// 开始在接受套接字上处理I/O
        
// 使用重叠I/O机制,在新建的套接字上投递一个或多个异步
         // WSARecv 或 WSASend请求。这些I/O请求完成后,工作者线程
         // 会为I/O请求提供服务,之后就可以坐享其成了

        static int const DATA_BUFSIZE = 4096; //

        DWORD RecvBytes 
= 0;
        DWORD Flags 
= 0;

        
// 单I/O操作数据
        LPPER_IO_DATA PerIoData = NULL;
        PerIoData 
= (LPPER_IO_DATA)GlobalAlloc(GPTR, sizeof(PER_IO_DATA));
        ZeroMemory(
&(PerIoData->Overlapped), sizeof(OVERLAPPED));        

        PerIoData
->DataBuf.len = 1024;
        PerIoData
->DataBuf.buf = PerIoData->buffer;
        PerIoData
->OperationType = 0// read
        WSARecv(PerHandleData->Socket,
                
&(PerIoData->DataBuf),
                
1,
                
&RecvBytes,
                
&Flags,
                
&(PerIoData->Overlapped),
                NULL);
    }


//////////////////////////////////////////////////////////////////////////

    
return nRetCode;
}


//////////////////////////////////////////////////////////////////////////

DWORD WINAPI ServerWorkerThread(LPVOID lpParam)
{
    HANDLE CompletionPort 
= (HANDLE)lpParam;
    DWORD BytesTransferred;
    LPOVERLAPPED lpOverlapped;
    LPPER_HANDLE_DATA PerHandleData 
= NULL;
    LPPER_IO_DATA PerIoData 
= NULL;
    DWORD SendBytes;
    DWORD RecvBytes;
    DWORD Flags;
    BOOL bRet 
= FALSE;

    
while (TRUE)
    
{
        bRet 
= GetQueuedCompletionStatus(CompletionPort,
                                         
&BytesTransferred,
                                         (PULONG_PTR)
  
                                           &
PerHandleData,
                                         (LPOVERLAPPED
*)
                                           &lpOverlapped,
                                         INFINITE);

        
// 检查成功的返回,这儿要注意使用这个宏CONTAINING_RECORD
        PerIoData = (LPPER_IO_DATA)CONTAINING_RECORD(lpOverlapped, 
                                                     PER_IO_DATA, 
                                                     Overlapped);

        
// 先检查一下,看看是否在套接字上已有错误发生
        if (0 == BytesTransferred) 
        
{
            closesocket(PerHandleData
->Socket);
            GlobalFree(PerHandleData);
            GlobalFree(PerIoData);

            
continue;
        }


        
// 数据处理
        
// 成功了!!!这儿就收到了来自客户端的数据
        cout << PerIoData->DataBuf.buf << endl;

        Flags 
= 0;

        
// 为下一个重叠调用建立单I/O操作数据
        ZeroMemory(&(PerIoData->Overlapped), sizeof(OVERLAPPED));

        PerIoData
->DataBuf.len = 1024;
        PerIoData
->DataBuf.buf = PerIoData->buffer;
        PerIoData
->OperationType = 0// read
        WSARecv(PerHandleData->Socket,
                
&(PerIoData->DataBuf),
                
1,
                
&RecvBytes,
                
&Flags,
                
&(PerIoData->Overlapped),
                NULL);
    }


    
return 0;
}


//////////////////////////////////////////////////////////////////////////




   当然为了测试,各种异常处理都没有写,大家不要学我哦。
                                                                     
posted on 2005-04-19 13:26 乱石铺街 阅读(8185) 评论(11)  编辑 收藏

Feedback

# re: 学习笔记之I/O完成端口(IOCP) 2005-04-27 13:54 飞天
这里的写的代码还可以折叠起来。这么酷

# re: 学习笔记之I/O完成端口(IOCP) 2005-04-27 14:51 dlutyuanhongl
嘿嘿,这是BLOG提供的功能!

# re: 学习笔记之I/O完成端口(IOCP) 2005-11-15 10:37 songs8467
写的不错,佩服!

# re: 学习笔记之I/O完成端口(IOCP) 2006-01-23 15:29 杀手K
不错,简单明了

# re: 学习笔记之I/O完成端口(IOCP) 2006-07-29 15:10 boli

谢谢啊~~ 正是有楼主这样的人,才能让我们 复制,粘贴~ 不用一块砖一块砖的码了~ :P

# re: 学习笔记之I/O完成端口(IOCP) 2007-03-05 17:11 murongtianfeng

WSARecv(...)在主程序的循环里已经投递了 为什么还要在  ServerWorkerThread()里最后的位置进行 wsarecv(...)投递?? 本人感觉没有必要吧 只要在主程序的循环里投递就可以达到不断接收用户请求的目的了

# re: 学习笔记之I/O完成端口(IOCP) 2007-04-27 11:17 waini12
因为,这段代码是抄袭的,所以他不知道为什么

# re: 学习笔记之I/O完成端口(IOCP) 2007-05-16 10:10 楠楠

回复murongtianfeng:

      因为WSASend/WSARecv是异步的 IO操作,具体的IO处理过程由WINDOWS系统完成. 主程序循环投递了I/O操作之后,就会立即返回. 但是这个动作(接收到数据如何得知呢?)
      是由WINDOWS系统完成的, 在系统实际的IO处理后,把结果送到完成端口上.(操作系统已经完成了, 但我们程序如何得知呢?)
     
      假如, 如果有多个IO都完成了,那么就会在完成端口那里排成一个队列。 我们需要从队列中取出数据, 所以在ServerWorkerThread线程中用 GetQueuedCompletionStatus 函数可以得到这些队列化的完成包.
得到数据. 但是不是完了呢. 我们又投递WSARecv. 让操作系统来替我们接收你的包. 然后再次通知我们..... 
      依次连接不断...............
      有什么时间想和我讨论:  QQ: 11718111
  

# re: 学习笔记之I/O完成端口(IOCP) 2007-06-05 17:56 kilobird
回复murongtianfeng:

      因为WSASend/WSARecv是异步的 IO操作,具体的IO处理过程由WINDOWS系统完成. 主程序循环投递了I/O操作之后,就会立即返回. 但是这个动作(接收到数据如何得知呢?)
      是由WINDOWS系统完成的, 在系统实际的IO处理后,把结果送到完成端口上.(操作系统已经完成了, 但我们程序如何得知呢?)
     
      假如, 如果有多个IO都完成了,那么就会在完成端口那里排成一个队列。 我们需要从队列中取出数据, 所以在ServerWorkerThread线程中用 GetQueuedCompletionStatus 函数可以得到这些队列化的完成包.
得到数据. 但是不是完了呢. 我们又投递WSARecv. 让操作系统来替我们接收你的包. 然后再次通知我们..... 
      依次连接不断...............
      有什么时间想和我讨论:  QQ: 11718111 

我是这样理解的,希望对大家有些帮组,WSARecv主要用来socket 和 overlappet 捆绑。希望多交通行好友 QQ: 308806913

# re: 学习笔记之I/O完成端口(IOCP) 2007-07-03 19:52 rong
想问下下,在主线程的tmain函数里一直在接受客户端的连接,那如果有界面的程序,界面能刷新吗,是否还要另创建一个线程,来维护界面的更新

# re: 学习笔记之I/O完成端口(IOCP) 2008-03-29 22:01 hurryboylqs
想问下下,在主线程的tmain函数里一直在接受客户端的连接,那如果有界面的程序,界面能刷新吗,是否还要另创建一个线程,来维护界面的更新 
-------------------------------------------
可以投递ACCEPT 事件的呀,如果不想让IO完成端口接收ACCEPT 事件 可以开个线程 来accept!!

标题  
姓名  
主页
验证码 *
内容   
  登录  使用高级评论  Top
[使用Ctrl+Enter键可以直接提交]