学习笔记(07):C++网络编程进阶-IO模型之重叠Overlapped IO(基于事件通知)

立即学习:https://edu.csdn.net/course/play/6082/113760?utm_source=blogtoedu

重叠Overlapped IO模型

重叠模型让应用程序使用重叠数据(WSAOVERLAPPED) , OVERLAPPED 是WIN32的一项技术,你可以要求操作系统为你传送数据, 并且在传送完毕时通知你,这项技术使你的程序在IO进行过程中仍然能够继续处理事务,支持重叠IO操作的系统对象处理文件,还有管道,串口,SOCKET

重叠Overlapped IO模型的优点

比起select WSAAsyncSelect以及WSAEventSelect等模型,重叠IO模型使应用程序能达到更加的系统性能,因为使用重叠模型的应用程序通知缓冲区收发系统直接使用数据,也就是说,如果应用程序投递了一个10kb大小的缓冲区来接收数据,且数据已经到达套接字,则该数据将直接拷贝到投递的缓冲区,而这4中模型需要调用接收函数之后,数据才从单套接字缓冲区拷贝到应用程序的缓冲区,差别就体现出来了。

重叠IO请求的通知的方式

1、事件对象通知(event object notification)

2、完成例程(completion routines)

重叠IO基于事件通知的流程

将windows事件对象与WSAOVERLAPPED结构关联在一起,(WSAOVERLAPPED结构中专门有对应的参数),使用重叠结构,我们常用的send,sendto,recv,recvfrom都要被WSASend,WSAWendto,WSARecv,WSARecvFrom替换,它们的参数中都有一个Overlapped参数,我们可以假设把我们的WSARecv这样的操作“绑定”到这个重叠结构上,提交一个请求,其他的事情就交个重叠结构去操作,而其中重叠结构又要与windows的事件对象(最多64个事件)“绑定”在一起,这样我们调用完WSARecv以后,就可以坐享其成,邓傲重叠结构操作完成以后,自然就有与之对应的事件来通知我们操作完成,然后我们就可以根据重叠操作的结构去的我们想要的数据。

WSARecv(

SOCKET s,

LPWSABUF lpBuffers,

DWORD dwBufferCount,

LPDWORD lpNumberOfBytesRecvd,

LPDWORD lpFlags,

LPWSAOVERLAPPED lpOverlapped,

LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine

);

参数:

第一个参数:投递这个操作的套接字

第二个参数:接收缓冲区,是WSABUF结构数组

第三个参数:数组中WSABUG结构的数量

第四个参数:如果接收操作立即完成,返回函数所收到的字节数

第五个参数:设置为0即可

第六个参数:“绑定重叠结构”

第七个参数:完成例程中的参数,我们这里设置为NULL

返回值:

成功返回0 , 完成例程将在调用线程处于alertable状态时被调用

失败返回SOCKET_ERROR的值,并通过WSAGetLastError错误代码,错误代码WSA_IO_PENDING表示重叠操作已经成功启动,并将在稍后的时间内表示完成,任何其他错误代码表名重叠操作为成功启动。

WSASend(

SOCKET s,

LPWSABUF lpBuffers,

DWORD dwBufferCount,

LPDWORD lpNumberOfBytesSent,

DWORD dwFlags,

LPWSAOVERLAPPED lpOverlapped,

LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine

);

参数:

第一个参数:标识一个已经连接套接字的描述字

第二个参数:一个指向WSABUF结构数组的指针

第三个参数:lpBuffers数组中WSABUF结构的数组

第四个参数:如果发送操作立即完成,则为一个指向所发送数据字节的指针

第五个参数:标志位

第六个参数:指向WSAOERLAPPED结构的指针,对于非重叠接口则忽略。

第七个参数:一个指向发送操作完成后调用的完成例程的指针(对于非重叠则忽略)

返回值:

成功返回0 , 完成例程将在调用线程处于alertable状态时被调用

否则返回SOCKET_ERROR的值,并通过调用WSAGetLastError()来检索特定的错误代码,错误代码WSA_IO_PENDING表名重叠操作已经成功启动,并将在稍后的事件内表示完成,任何其他的错误代码表名重叠操作为成功启动,也不会出现指示。

typedef struct _WSABUF {

ULONG len; /* the length of the buffer */

_Field_size_bytes_(len) CHAR FAR *buf; /* the pointer to the buffer */

} WSABUF, FAR * LPWSABUF;

 

WSAGetOverlappedResult函数:

WSAGetOverlappedResult(

SOCKET s,

LPWSAOVERLAPPED lpOverlapped,

LPDWORD lpcbTransfer,

BOOL fWait,

LPDWORD lpdwFlags

);

参数:

第一个参数:套接字s

第二个参数想要查询结构的重叠结构的指针

第三个参数:本次重叠操作的实际接收(或发送)的字节数

第五个参数:设置为TURE,除非重叠操作完成,否则函数不会返回,设置为Fasle,而且操作仍处于挂起状态,那么函数就会返回false,错误为WSA_IO_INCOMPLETE,应为我们是等待事件来通知我们操作完成。

第六个参数:接收结果标志

范返回值:

成功则返回true,重叠操作已经成功完成,并更新lpcbTransfer所指向的值,失败返回false,通过WSAGetLastError来获取错误代码

基于事件通知的重叠IO编程步骤

1、创建套接字在指定端口监听(socket会默认设置WSA_FLAG_OOVERLAPPED标志),并接受客户端连接请求

2、为接受的套接字为新建一个WSAOVERLAPPED结构,并为该结构分配一个事件对象句柄,同时将该事件对象句柄分配给一个事件数组,以便稍后有WSAWaitForMultipleEvent函数使用

3、在套接字上投递一个异步WSARecv请求,指定参数为WSAOVERLAPPED结构,让WSAOVERLAPPED结构来替我们请求IO请求,注意函数通常会以失败告终,返回SOCKET_ERROR错误状态WSA_IO_PENDING(IO操作尚未完成)

4、使用步骤2的事件数组,调用WSAWaitForMultipleEvents函数,并等待与重叠调用关联在一起的事件进入“已传信”状态,根据WSAWaitForMultipleEvents函数的返回值来确定究竟事件数组中的哪一个事件被触发了。

5、WSAWaitForMultipleEvents函数返回后,针对“已传信”状态的事件,调用WSAResetEvent函数,事件已经被触发了之后,它对于我们来讲已经没有利用价值了,所以要将它重置准备下一次使用。

6、使用WSAGetOverlappedResult函数取的重叠的返回状态,需要检查堆放的Socket连接是否关闭,如果操作完成,WSABUF结构里面就存有我们WSARecv来的数据。

7、在套接字上继续投递WSARecv请求,即重复步骤3·6

服务端代码

#include<stdlib.h>
#include<WinSock2.h>
#include <iostream>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
SOCKET socketArray[WSA_MAXIMUM_WAIT_EVENTS];
WSAEVENT eventArray[WSA_MAXIMUM_WAIT_EVENTS];
WSAOVERLAPPED* overArray[WSA_MAXIMUM_WAIT_EVENTS];
WSABUF bufArray[WSA_MAXIMUM_WAIT_EVENTS];
int N = 0;
DWORD WINAPI ThreadProc(LPVOID lpRarameter);
int main()
{
    WSADATA wd;
    if (WSAStartup(MAKEWORD(2, 2), &wd) != 0)
    {
        cout << "WSAStartup error " << WSAGetLastError() << endl;
        exit(EXIT_FAILURE);
    }
    //
    SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (s == INVALID_SOCKET)
    {
        cout << "socket error " << WSAGetLastError() << endl;
        exit(EXIT_FAILURE);
    }
    sockaddr_in addr;
    addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8000);
    int bindRet = bind(s, (sockaddr*)&addr, sizeof sockaddr);
    if (bindRet == SOCKET_ERROR)
    {
        cout << " bind error " << WSAGetLastError() << endl;
        exit(EXIT_FAILURE);
    }
    int listenRet = listen(s, 0);
    if (listenRet == SOCKET_ERROR)
    {
        cout << "listen error " << WSAGetLastError() << endl;
        exit(EXIT_FAILURE);
    }

    HANDLE hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
    CloseHandle(hThread);
    while (true)
    {
        SOCKET sClient = accept(s, NULL, NULL);
        if (sClient == INVALID_SOCKET)
        {
            cout << "accept error" << WSAGetLastError() << endl;
            continue;
        }
        cout << sClient << " 进入聊天室" << endl;
        char temp[30] = {'\0'};
        sprintf_s(temp, "欢迎%d进入聊天室", sClient);
        send(sClient, temp, strlen(temp), 0);
        WSAEVENT event = WSACreateEvent();
        WSAOVERLAPPED* pOver = new WSAOVERLAPPED;
        pOver->hEvent = event;
        WSABUF wsabuf;
        wsabuf.buf = new char[100]{ '\0' };
        wsabuf.len = 100;
        socketArray[N] = sClient;
        eventArray[N] = event;
        bufArray[N] = wsabuf;
        overArray[N++] = pOver;
        DWORD flags = 0;
        DWORD numberOfByRecvd = 0;
        int wsaRecvRet = WSARecv(sClient, &wsabuf, 1, &numberOfByRecvd, &flags, pOver, NULL);
        if (wsaRecvRet == SOCKET_ERROR)
        {
            int err = WSAGetLastError();
            if (err != WSA_IO_PENDING)
            {
                cout << "WSARecv 失败" << endl;
            }
        }
    }
}
DWORD WINAPI ThreadProc(LPVOID lpRarameter)
{
    while (true)
    {
        int waitRet = WSAWaitForMultipleEvents(N, eventArray, false, 1000, false);
        if (waitRet == WSA_WAIT_FAILED)
        {
            continue;
        }
        int index = waitRet - WSA_WAIT_EVENT_0;

        WSAResetEvent(eventArray[index]);
        WSAOVERLAPPED* pOver = overArray[index];
        DWORD lpcbTransfer = 0;
        DWORD lpdwFlags = 0;
        int ret2 = WSAGetOverlappedResult(socketArray[index], pOver, &lpcbTransfer, true, &lpdwFlags);
        if (ret2 && lpcbTransfer > 0)
        {
            cout << socketArray[index] << "说:" << bufArray[index].buf << endl;
            ZeroMemory(bufArray[index].buf, 100);
            DWORD numberOfRecv = 0;
            DWORD flags = 0;
            WSARecv(socketArray[index], &bufArray[index], 1, &numberOfRecv, &flags, pOver, NULL);
        }
        else
        {
            cout << socketArray[index] << "离开了" << endl;
            closesocket(socketArray[index]);
            WSACloseEvent(eventArray[index]);
            delete overArray[index];
            delete[]bufArray[index].buf;
            for (int i = index; i < N - 1; i++)
            {
                socketArray[i] = socketArray[i + 1];
                eventArray[i] = eventArray[i + 1];
                overArray[i] = overArray[i + 1];
                bufArray[i] = bufArray[i + 1];
            }
        }
    }
}

 

相关推荐
©️2020 CSDN 皮肤主题: 深蓝海洋 设计师:CSDN官方博客 返回首页