【网络编程】之十一、重叠IO Overlapped IO 完成例程

本文详细介绍了WinSockets中的完成例程机制,包括其函数原型、如何使用完成例程管理重叠I/O操作、相关函数如WSAWaitForMultipleEvents和SleepEx的用法,以及一个完整的开发步骤实例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

完成例程是Win Sockets提供的另一种管理完成的重叠I/O方法,完成例程是一个函数,当发起重叠操作时,将该函数传递给发起操作的函数,当重叠IO操作完成时由系统调用。

下面来看一下例程必须拥有下面的函数原型:

void CALLBACK CompletionROUTINE{
        IN DWORD dwError,  //重叠操作的完成状态。
        IN DWORD cbTransferred,  //实际传输的字节数。
        IN LPWSAOVERLAPPED lpOVerlapped,   //初始化重叠操作的那个重叠结构
        IN DWORD dwFlags  //一个标志参数
};


  OK ,在应用程序中使用完成例程管理重叠IO操作时,你必须给调用函数制定一个完成例程,一个WSAOVERLAPPED结构,在这里WSAOVERLAPPED的hEvent字段不会被使用,因为你使用完成例程来管理重叠操作的最终结果。

   当我们的调用写成在重叠操作完成后必须为完成例程提供服务,将调用线程置为“可警告的县城等待状态”。在这种状态下,重叠IO操作完成时,完成例程也被调用。

下面来看两个函数,他们可以设置线程为一种可警告的等待状态:

WSAWatiForMultipleEvents

 DWORD WSAAPI WSAWaitForMultipleEvents( 
  DWORD cEvents,//The number of event object handles in the array pointed to by lphEvents. WSA_MAXIMUM_WAIT_EVENTS是句柄最大值
  const WSAEVENT FAR * lphEvents, //A pointer to an array of event object handles.
    BOOL fWaitAll,// the wait type!TURE:当lphEvents的所有对象有信号的时候函数返回。FALSE:当任意一个事件对象有信号时函数返回
  DWORD dwTimeout,//The time-out interval,秒为单位
  BOOL fAlertable );//指定当系统将一个输入/输出完成例程放入队列以供执行时,函数是否返回。TRUE:函数返回并执行完成例程FALSE:函数不返回也不执行完成例程。

如果函数成功,返回值指出造成函数返回的事件对象。   如果函数失败,返回值为WSA_WAIT_FAILED。

来说一下最后一个参数,当为TRUE时,说明该函数返回时完成例程已经被执行。  如果该参数为FALSE,那么说明该函数返回时完成例程还没有执行。

                                                   通过设置TRUE,将调用线程置于可警告的等待状态,当该函数返回的时候,返回值为:WAIT_IO_COMPLETION。

在这里,我们为了使用WSAWaitForMultipleEvents,在应用程序中可以定义一个不和任何对相关联的事件对象。  然后我们用这个事件对象为参数来调用该函数。

函数返回的时候,我们要判断一下返回值是否是:WAIT_IO_COMPLETION,如果返回这个值,那么就说明这时候完成例程已经被调用了。如果不是,那么就说明发生了错误。

SleepEx

DWORD SleepEx{
      DWORD dwMilliseconds,//等待时间,ms为单位,   INFINITE说明函数将无限等待
      BOOL bAlertable//函数的返回方式1、FALSE,在不调用超时的情况下是不返回的。2、TRUE 调用超时或者                     //发生IO回调,那么就返回。
};

这里存在一个调用超时的问题,如果超时了,那么返回值就是0,  如果发生了IO完成回调,那么函数就会返回,返回值是:WAIT_IO_COMPLETION。


重叠io完成例程要求我们讲一个瓦按成例程指定给发起IO操作的函数,  当io操作完成后,完成例程就被调用。    下面来看一下开发win  socket的步骤:

1、创建具有WSAOVERLAPPED标志的套接字。

2、定义完成例程。

3、调用输入或者输出函数,初始化重叠IO操作,调用函数的最后一个参数为应用从程序定义的完成例程。

4、调用WSAWaitForMultipleEvents()函数或者SleepEx()函数,使线程处于可警告的等待状态。如果调用WSAWaitForMultipleEvents()函数  我们需要一个应用程序定义的事件对象,以便该函数等待事件的发生。这两个函数的最后一个参数要设置为TRUE。

5、当发生重叠IO回调时,完成例程被调用。



下面来看一下WSAOVERLAPPED结构:

typedef struct _WSAOVERLAPPED {
  ULONG_PTR Internal;//os保留,指出一个和系统相关的状态
  ULONG_PTR InternalHigh;//发送,接收数据的长度
  union {
    struct {
      DWORD Offset;//文件的初始位置
      DWORD OffsetHigh;//文件的偏移量
    };
    PVOID  Pointer;//指向文件传送位置,do not use after initialization to zero.
  };
  HANDLE    hEvent;//指定一个IO操作完成后触发的事件
} WSAOVERLAPPED, *LPWSAOVERLAPPED;

下面来看一下WSAOVERLAPPED结构的实例:

#include <winsock2.h>
#include <ws2tcpip.h>

#include <stdio.h>
#include <stdlib.h>

// Need to link with Ws2_32.lib
#pragma comment(lib, "ws2_32.lib")

int __cdecl main()
{

    //---------------------------------------------
    // Declare and initialize variables
    WSADATA wsaData;
    WSABUF DataBuf;

    WSAOVERLAPPED Overlapped;
    SOCKET SendToSocket = INVALID_SOCKET;

    struct sockaddr_in RecvAddr;
    struct sockaddr_in LocalAddr;
    int RecvAddrSize = sizeof (RecvAddr);
    int LocalAddrSize = sizeof (LocalAddr);

    u_short Port = 27015;
    struct hostent *localHost;
    char *ip;

    char SendBuf[1024] = "Data buffer to send";
    int BufLen = 1024;
    DWORD BytesSent = 0;
    DWORD Flags = 0;

    int rc, err;
    int retval = 0;

    //---------------------------------------------
    // Initialize Winsock
    // Load Winsock
    rc = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (rc != 0) {
        printf("Unable to load Winsock: %d\n", rc);
        return 1;
    }

    // Make sure the SendOverlapped struct is zeroed out
    SecureZeroMemory((PVOID)&Overlapped, sizeof(WSAOVERLAPPED));

    // Create an event handle and setup the overlapped structure.
    Overlapped.hEvent = WSACreateEvent();
    if (Overlapped.hEvent == NULL) {
        printf("WSACreateEvent failed with error: %d\n", WSAGetLastError());
        WSACleanup();
        return 1;
    }
    //---------------------------------------------
    // Create a socket for sending data
    SendToSocket =
        WSASocket(AF_INET, SOCK_DGRAM, IPPROTO_UDP, NULL, 0,
                  WSA_FLAG_OVERLAPPED);
    if (SendToSocket == INVALID_SOCKET) {
        printf("socket failed with error: %d\n", WSAGetLastError());
        WSACloseEvent(Overlapped.hEvent);
        WSACleanup();
        return 1;
    }
    //---------------------------------------------
    // Set up the RecvAddr structure with the IP address of
    // the receiver (in this example case "127.0.0.1")
    // and the specified port number.
    RecvAddr.sin_family = AF_INET;
    RecvAddr.sin_port = htons(Port);
    RecvAddr.sin_addr.s_addr = inet_addr("127.0.0.1");

    //---------------------------------------------
    // Set up the LocalAddr structure with the local IP address
    // and the specified port number.
    localHost = gethostbyname("");//返回本地主机的标准主机名
    ip = inet_ntoa(*(struct in_addr *) *localHost->h_addr_list);

    LocalAddr.sin_family = AF_INET;
    LocalAddr.sin_addr.s_addr = inet_addr(ip);
    LocalAddr.sin_port = htons(Port);

    //---------------------------------------------
    // Bind the sending socket to the LocalAddr structure
    // that has the internet address family, local IP address
    // and specified port number.  
    rc = bind(SendToSocket, (struct sockaddr *) &LocalAddr, LocalAddrSize);
    if (rc == SOCKET_ERROR) {
        printf("bind failed with error: %d\n", WSAGetLastError());
        WSACloseEvent(Overlapped.hEvent);
        closesocket(SendToSocket);
        WSACleanup();
        return 1;
    }
    //---------------------------------------------
    // Send a datagram to the receiver
    printf("Sending a datagram...\n");
    DataBuf.len = BufLen;
    DataBuf.buf = SendBuf;
    rc = WSASendTo(SendToSocket, &DataBuf, 1,
                   &BytesSent, Flags, (SOCKADDR *) & RecvAddr,
                   RecvAddrSize, &Overlapped, NULL);

    if ((rc == SOCKET_ERROR) && (WSA_IO_PENDING != (err = WSAGetLastError()))) {
        printf("WSASendTo failed with error: %d\n", err);
        WSACloseEvent(Overlapped.hEvent);
        closesocket(SendToSocket);
        WSACleanup();
        return 1;
    }

    rc = WSAWaitForMultipleEvents(1, &Overlapped.hEvent, TRUE, INFINITE, TRUE);
    if (rc == WSA_WAIT_FAILED) {
        printf("WSAWaitForMultipleEvents failed with error: %d\n",
                WSAGetLastError());
        retval = 1;
    }

    rc = WSAGetOverlappedResult(SendToSocket, &Overlapped, &BytesSent,
                                FALSE, &Flags);//返回指定套接字口上一个重叠操作结果。
    if (rc == FALSE) {
        printf("WSASendTo failed with error: %d\n", WSAGetLastError());
        retval = 1;
    }

    //---------------------------------------------
    // When the application is finished sending, close the socket.
    printf("Finished sending. Closing socket.\n");
    WSACloseEvent(Overlapped.hEvent);
    closesocket(SendToSocket);
    printf("Exiting.\n");

    //---------------------------------------------
    // Clean up and quit.
    WSACleanup();
    return (retval);
}

WSAOVERLAPPED   和  WSAGetOverlappedResult在上一节中都给出过: http://blog.youkuaiyun.com/jofranks/article/details/7895316


 ----2012/9/20

----jofranks 于南昌


转载于:https://www.cnblogs.com/java20130723/archive/2012/09/20/3211402.html

重叠IO模型之OverLapped完成例程模型WSACompletionRoutineServer VS2010 基础入门 客户端与服务器端 客户端向服务器端发送数据 可接收多个客户端 #include #include #pragma comment (lib, "ws2_32.lib") #define PORT 8088 #define MSG_SIZE 1024 SOCKET g_sConnect; bool g_bConnect = false; typedef struct { WSAOVERLAPPED overLap; WSABUF wsaBuf; char chMsg[MSG_SIZE]; DWORD nRecvNum; DWORD nFlags; SOCKET sClient; }PRE_IO_OPERATION_DATA, *LP_PER_IO_OPERATION_DATA; void CALLBACK CompletionRoutine(DWORD dwError, DWORD dwTrans, LPWSAOVERLAPPED lpOverlap, DWORD nFlags); DWORD WINAPI workThread(LPVOID lp) { LP_PER_IO_OPERATION_DATA lpData; while(TRUE) { if (g_bConnect) // 有新的连接 { // 为lpData分配空间并初始化 lpData = (LP_PER_IO_OPERATION_DATA)HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(PRE_IO_OPERATION_DATA)); lpData->wsaBuf.len = MSG_SIZE; lpData->wsaBuf.buf = lpData->chMsg; lpData->sClient = g_sConnect; WSARecv(lpData->sClient, &lpData->wsaBuf, 1, &lpData->nRecvNum, &lpData->nFlags, &lpData->overLap, CompletionRoutine); g_bConnect = false; // 处理完毕 } SleepEx(1000, TRUE); } return 0; } // 系统在WSARecv收到信息后,自动调用此函数,并传入参数--回调函数 void CALLBACK CompletionRoutine(DWORD dwError, DWORD dwTrans, LPWSAOVERLAPPED lpOverlap, DWORD nFlags) { LP_PER_IO_OPERATION_DATA lpData = (LP_PER_IO_OPERATION_DATA)lpOverlap; if (0 != dwError) // 接收失败 { printf("Socket %d Close!\n", lpData->sClient); closesocket(lpData->sClient); HeapFree(GetProcessHeap(), 0, lpData); } else // 接收成功 { lpData->chMsg[dwTrans] = '\0'; send(lpData->sClient, lpData->chMsg, dwTrans, 0); printf("Socket:%d MSG: %s \n", lpData->sClient, lpData->chMsg); memset(&lpData->overLap, 0, sizeof(WSAOVERLAPPED)); lpData->wsaBuf.len = MSG_SIZE; lpData->wsaBuf.buf = lpData->chMsg; // 继续接收来自客户端的数据 实现 WSARecv与CompletionRoutine循环 WSARecv(lpData->sClient, &lpData->wsaBuf,1, &lpData->nRecvNum, &lpData->nFlags, &lpData->overLap, CompletionRoutine); } } int main() { WSADATA wsaData; WSAStartup(0x0202, &wsaData); SOCKET sListen; sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); sockaddr_in addrListen; addrListen.sin_family = AF_INET; addrListen.sin_port = htons(PORT); addrListen.sin_addr.S_un.S_addr = htonl(ADDR_ANY); int nErrorCode = 0; nErrorCode = bind(sListen, (sockaddr*)&addrListen, sizeof(sockaddr)); nErrorCode = listen(sListen, 5); DWORD nThreadID; CreateThread(NULL, 0, workThread, NULL, 0, &nThreadID); sockaddr_in addrConnect; int nAddrLen = sizeof(sockaddr_in); printf("Server Started!\n"); while(TRUE) { g_sConnect= accept(sListen, (sockaddr*)&addrConnect, &nAddrLen); if (INVALID_SOCKET == g_sConnect) { return -1; } g_bConnect = true; // 连接成功 printf("Accept Client :%s -- PORT:%d\n", inet_ntoa(addrConnect.sin_addr), htons(addrConnect.sin_port)); } return 0; }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值