IOCP 服务端案例

来源:互联网 发布:办公室网络限制视频 编辑:程序博客网 时间:2024/05/19 13:28

IOCP

Epoll 和 IOCP性能比较

    每种操作系统(内核级)都会提供特有的I/O模型以提高性能。其中Linux的Epoll、BSD的Kqueue、Windows的IOCP。它们都是在操作系统级别上提供支持并且完成相关功能。那么问题来了:到底是Epoll优于IOCP 还是后者优于前者呢? 个人认为至少对于目前的我来说,这两种模型都是非常优秀的(因为我也不知道谁更优秀)。个人认为不管是Eopll还是IOCP都具有各自独特的优点,但是这并非左右了服务器性能的因素。至少他们的工作机制不同,所以对于Epoll或者IOCP,各位自行判断。

知识补给

    IOCP不仅是负责I/O工作,也还有至少创建1个线程并使其负责全部I/O的前后处理。理解IOCP的时候不要把目光着重集中到线程上,而是要注意一下两点:
    1、I/O是否是以非阻塞模式工作?
    2、怎样确定非阻塞模式的I/O是否完成了工作?

首先介绍相关函数

#include <windows.h>HANDLE CreateIoCompletionPort(HANDLE FileHandle,HANDLE ExistingCompletionPort,ULONG_PTR CompletionKey,DWORD NumberOfConcurrentThreads);

    FileHandle:创建CP对象时传递 INVALID_HANDLE_VALUE。
    ExistingCompletionPort:创建CP对象时传递NULL。
    CompletionKey:创建CP对象时传递0。
    NumberOfConcurrentThreads:分配给CP对象的用于处理I/O的线程数。如该参数为2时,则分配给CP对象的可以同时运行的线程数最多为2个。如果为0,则系统中的CPU个数就是可同时运行的最大线程数。

ps:该函数可以用于创建也可以用于连接。连接参数说明在下面

    FileHandle:要连接到的CP对象套接字句柄
    ExistingCompletionPort:要连接套接字的CP对象句柄
    CompletionKey:传递已完成I/O相关信息
    NumberOfConcurrentThreads:无论传递何值,只要该函数的第二个参数非NULL就会自动忽略。

确认完成端口完成的I/O和线程的I/O处理;

#include <windows.h>BOOL GetQueuedCompletionStatus(HANDLE CompletionPort,LPDWORD lpNumberOfBytes,PULONG_PTR lpCompletionKey,LPOVERLAPPED * lpOverlapped,DWORD dwMilliseconds);

    CompletionPort:注册有已经完成I/O信息的CP对象句柄
    lpNumberOfBytes:用于保存I/O过程中传输的数据大小的变量地址值
    lpCompletionKey:用于保存CreateIoCompletionPort函数的第三个参数的变量地址值。
    lpOverlapped:用于保存调用WSASend、WSARecv函数时传递的OVERLAPPED结构体地址的变量地址值。
    dwMilliseconds:超时信息,超过该指定时间后返回FALSE并跳出函数。传递INFINITE时,程序将阻塞,知道已经有完成I/O信息写入CP对象。

*下面上主题菜,注意两个结构体的定义,并且留意结构体是何时被分配空间、何时被传递、如何被使用的。

#include <stdio.h>#include <stdlib.h>#include <process.h>#include <WinSock2.h>#include <Windows.h>;#define BUF_SIZE 100#define READ 3#define WRITE 5/*注意这两个结构体是何时分配空间,如何被传递,如何被使用*/typedef struct {                            //  *保存与客户端相连接套接字的结构体。    SOCKET nClientSock;    SOCKADDR_IN pClientAdr;}PER_HANDLE_DATA, *LPPER_HANDLE_DATA;typedef struct {                            //  *将I/O中使用的缓冲和重叠I/O中需要的OVERLAPPED结构体变量封装到同一结构体中进行定义。    OVERLAPPED overlapped_;    WSABUF wsabuf_;    char buffer[BUF_SIZE];    int rwMode;                             //  Read or Write;}PER_IO_DATA, *LPPER_IO_DATA;/*在观察着两个结构时,请明确:结构体变量地址值与第一个成员的地址值相同*///  本应该为 DWORD 的类型,应该是API更新了,使用 unsignedunsigned WINAPI EchoThreadMain (LPVOID lpComPort);void Error_Handing (char *message);int main () {    WSADATA wsaDATA;    HANDLE pComPort;    SYSTEM_INFO sysInfo;    LPPER_IO_DATA ioInfo;    LPPER_HANDLE_DATA handleInfo;    SOCKET nServSock;    SOCKADDR_IN servAdr;    int recvByte, i, flags = 0;    if ( WSAStartup (MAKEWORD (2, 2), &wsaDATA) != 0 )        Error_Handing ("Server Verision Fail.");    //  创建CP对象,最后一个参数为0,取系统最大线程数(相当于CPU的核数)    pComPort = CreateIoCompletionPort (INVALID_HANDLE_VALUE, NULL, 0, 0);    GetSystemInfo (&sysInfo);   //  获取当前系统信息,没有太大意义,记住就好    for ( i = 0; i < sysInfo.dwNumberOfProcessors; i++ ) {        //  dwNumberOfProcessors 写入CPU个数 双核为2 四核为4,这个for循环创建了与CPU个数相当的线程,另外创建线程时传递前面的CP对象句柄,线程将通过该句柄访问CP对象。白话就是 CP对象将通过该句柄分配到线程。        _beginthreadex (NULL, 0, EchoThreadMain, (LPVOID) pComPort, 0, NULL);    }    nServSock = WSASocket (AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);    memset (&servAdr, 0, sizeof (servAdr));    servAdr.sin_family = AF_INET;    servAdr.sin_addr.s_addr = inet_addr ("192.168.1.101");    servAdr.sin_port = htons (9130);    bind (nServSock, (SOCKADDR*) &servAdr, sizeof (servAdr));    listen (nServSock, 5);    while ( 1 ) {        SOCKET nClientSock;        SOCKADDR_IN pClientAdr;        int addrLen = sizeof (pClientAdr);        nClientSock = accept (nServSock, (SOCKADDR*) &pClientAdr, &addrLen);        /*动态分配PER_HANDLE_DATA 结构体,并且写入客户端套接字核客户端地址信息*/        handleInfo = (LPPER_HANDLE_DATA) malloc (sizeof (PER_HANDLE_DATA));        handleInfo->nClientSock = nClientSock;        memcpy (&( handleInfo->pClientAdr ), &pClientAdr, addrLen);        /*连接前者创建的CP对象和前者创建的SOCKET套接字。针对套接字的重叠I/O完成时,已完成信息会写入连接的CP对象,这会将引起GetQueue..函数的返回。注意第三参数的值。其值时前者声明并且初始化的结构体变量地址值,它同样是在GetQueued...函数返回时得到*/        CreateIoCompletionPort ((HANDLE) nClientSock, pComPort, (DWORD) handleInfo, 0);        /*动态分配PER_IO_DATA结构体变量空间。相当于同时准备了WSARecv函数中需要的OVERLAPPED结构体变量、WSABUF变量以及缓冲*/        ioInfo = (LPPER_IO_DATA) malloc (sizeof (PER_IO_DATA));        memset (&( ioInfo->overlapped_ ), 0, sizeof (OVERLAPPED));        ioInfo->wsabuf_.buf = ioInfo->buffer;        ioInfo->wsabuf_.len = BUF_SIZE;        ioInfo->rwMode = READ;  //  IOCP不会帮我们区分完成输入和完成输出状态。只通知I/O完成状态,因此需要额外的变量来记录这两种状态。PER_IO_DATA结构体中的rwMode就是用于该功能记录        /*WSARecv 第六个参数为OVERLAPPED变量地址值,该值可以在GetQueued...函数返回时得到。但是该结构体地址值与第一个成员的地址值相同,相当于传入了PER_IO_DATA变量地址值*/        WSARecv (handleInfo->nClientSock, &( ioInfo->wsabuf_ ), 1, (unsigned long*) &recvByte, (unsigned long*) &flags, &( ioInfo->overlapped_ ), NULL);    }    return 0;}//  由线程运行的函数unsigned WINAPI EchoThreadMain (LPVOID lpComPort) {    HANDLE hComPort = (HANDLE) lpComPort;    SOCKET sock;    DWORD byteTrans;    LPPER_HANDLE_DATA handleInfo;    LPPER_IO_DATA ioInfo;    DWORD flags;    while ( 1 ) {        //  在I/O完成且已经注册相关信息时返回(最后一个参数为INFINITE)。另外返回时可以通过第三和第四个参数得到之前的2个信息。        GetQueuedCompletionStatus (hComPort, &byteTrans, (LPDWORD) &handleInfo, (LPOVERLAPPED*) &ioInfo, INFINITE);        sock = handleInfo->nClientSock;        /*指针ioInfo中保存的既是OVERLAPPED变量地址值,也是PER_IO_DATA变量地址值。因此可以检查rwMode成员中的值判断是输入完成还是输出完成*/        if ( ioInfo->rwMode == READ ) {            puts ("Message received!");            if ( byteTrans == 0 ) {                closesocket (sock);                free (handleInfo);                free (ioInfo);                continue;            }            /*将服务器收到的消息发送给客户端*/            memset (&( ioInfo->overlapped_ ), 0, sizeof (OVERLAPPED));            ioInfo->wsabuf_.len = byteTrans;            ioInfo->rwMode = WRITE;            WSASend (sock, &( ioInfo->wsabuf_ ), 1, NULL, 0, ( &ioInfo->overlapped_ ), NULL);            /*再次发送消息后接受客户端消息*/            ioInfo = (LPPER_IO_DATA) malloc (sizeof (PER_IO_DATA));            memset (&ioInfo->overlapped_, 0, sizeof (OVERLAPPED));            ioInfo->wsabuf_.len = BUF_SIZE;            ioInfo->wsabuf_.buf = ioInfo->buffer;            ioInfo->rwMode = READ;            WSARecv (sock, &( ioInfo->wsabuf_ ), 1, NULL, &flags, &( ioInfo->overlapped_ ), NULL);        } else {    //  完成的I/O为输出时执行的else区域            puts ("message sent!");            free (ioInfo);        }    }    return 0;}void Error_Handing (char *message) {    fputs (message, stderr);    fputc ('\n', stderr);    exit (1);}

客户端就不提供了,前一章已经实现了客户端,虽然是不是回声,但是做测试时可以的了,也可以百度下一些TCP/UDP连接测试工具进行测试。IOCP多看看代码,多理解。

0 0
原创粉丝点击