程序员人生 网站导航

网络编程(55)—— Windows下使用WSASocket基于Completion Routine进行IO重叠

栏目:互联网时间:2017-03-07 08:23:49

1、引言

        上1文中我们介绍了使用基于事件进行IO堆叠的方法,本文主要介绍另外1种,基于回调函数void CALLBACK CompletionRoutine(DWORD dwError,DWORDszRecvBytes,LPWSAOVERLAPPED lpOverlapped,DWORD flags)进行IO堆叠。首先,我们先介绍1种线程的状态——alertable wait状态。

        甚么是alertable wait状态?

alertable wait状态是线程等待接收操作系统消息的状态,我们之前接触过的WSAWaitForMultipleEvents函数就能够触发线程这类状态。我们先来回顾下WSAWaitForMultipleEvents的函数原型:

DWORDWSAWaitForMultipleEvents(
  __in DWORD cEvents,
  __in const WSAEVENT *lphEvents,
  __in BOOL fWaitAll,
  __in DWORD dwTimeout,
  __in BOOL fAlertable
);

        它的第5个参数fAlertable就是设置alertablewait状态的开关,当它设置为True时就会激活线程的alertable wait状态。我们定义的回调函数CompletionRoutine,只有在线程进入alertablewait状态后才会被操作系统调用。

2、定义CompletionRoutine回调函数

        我们先来回顾下WSARecv的原型(WSASend类似):

int WSARecv(
  __in     SOCKET s,
  __inout  LPWSABUF lpBuffers,
  __in     DWORD dwBufferCount,
  __out    LPDWORD lpNumberOfBytesRecvd,
  __inout  LPDWORD lpFlags,
  __in     LPWSAOVERLAPPED lpOverlapped,
  __in     LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);

        在上1文中,采取基于事件的IO堆叠时,将WSARecv最后1个参数设置成了NULL。而实际上他就是我们定义的回调函数CompletionRoutine的函数指针。而CompletionRoutine函数的后3个参数分别来自WSARecv的第4~6个参数。当线程进入alertable wait状态后,操作系统就会履行CompletionRoutine函数。

 

3、利用实例

        上述是使用CompletionRoutine的理论部份,下面我们通过实例对CompletionRoutine进行学习。使用CompletionRoutine进行数据接收的例子:

// WSASocketCompletionRoutineServ.cpp : 定¡§义°?控?制?台¬¡§应®|用®?程¨¬序¨°的Ì?入¨?口¨²点Ì?。¡ê
//
 
#include "stdafx.h"
#include "stdio.h"
#include "stdlib.h"
#include <WinSock2.h>
#include <string.h>
 
#pragma comment(lib,"ws2_32.lib")
 
#define BUF_SIZE 30
 
void ErrorHandler(const char* message);
void CALLBACK CompletionRoutine(DWORD dwError,DWORDszRecvBytes,LPWSAOVERLAPPED lpOverlapped,DWORD flags);
 
char buf[BUF_SIZE];
DWORD recvBytes = 0;
WSABUF wsaBuf;
int _tmain(int argc, _TCHAR* argv[])
{
    SOCKETservSock,clntSock;
    SOCKADDR_INservAddr,clntAddr;
    int clntAddrSz;
   
    WSAOVERLAPPEDoverLapped;
    HANDLEhEvent;
   
    DWORDret,flags=0;
 
    WSADATAwsaData;
    WSAStartup(MAKEWORD(2,2),&wsaData);
 
 
 
    //创ä¡ä建¡§支¡ì持?IO复¡ä用®?的Ì?套¬¡Á接¨®字Á?
    servSock=WSASocket(AF_INET,SOCK_STREAM,0,NULL,0,WSA_FLAG_OVERLAPPED);
    if(servSock==INVALID_SOCKET)
        ErrorHandler("WSASocket Error");
 
    memset(&servAddr,0,sizeof(servAddr));
    servAddr.sin_family=AF_INET;
    servAddr.sin_addr.s_addr=htonl(INADDR_ANY);
    servAddr.sin_port=htons(atoi("8888"));
 
    if(bind(servSock,(SOCKADDR*)&servAddr,sizeof(servAddr))==SOCKET_ERROR)
        ErrorHandler("bind error");
 
    if(listen(servSock,5)==SOCKET_ERROR)
        ErrorHandler("listen error");
 
    clntAddrSz=sizeof(clntAddr);
    clntSock=accept(servSock,(SOCKADDR*)&clntAddr,&clntAddrSz);
 
    memset(&overLapped,0,sizeof(overLapped));
    wsaBuf.buf=buf;
    wsaBuf.len=BUF_SIZE;
    hEvent=WSACreateEvent();
    overLapped.hEvent=hEvent;
 
    if(WSARecv(clntSock,&wsaBuf,1,&recvBytes,&flags,&overLapped,CompletionRoutine)==SOCKET_ERROR)
    {
        if(WSAGetLastError()==WSA_IO_PENDING)
            puts("background recieve data");
    }
    ret=WSAWaitForMultipleEvents(1,&hEvent,false,WSA_INFINITE,true);
    if(ret==WAIT_IO_COMPLETION)
        puts("Overlapped I/O Compeleted");
    else
        ErrorHandler("WSARecv Error");
 
    WSACloseEvent(hEvent);
    closesocket(servSock);
    closesocket(clntSock);
    WSACleanup();
    return 0;
}
 
void CALLBACK CompletionRoutine(DWORD dwError,DWORDszRecvBytes,LPWSAOVERLAPPED lpOverlapped,DWORD flags)
{
    if(dwError!=0)
    {
        ErrorHandler("CompletionRoutine Error");
    }
    else
    {
        recvBytes=szRecvBytes;
        printf("Recieve Message:%s\n",buf);
    }
}
 
void ErrorHandler(const char* message)
{
    fputs(message,stderr);
    fputc('/n',stderr);
    exit(1);
}

第79~90行,是对CompletionRoutine函数的定义,在函数中操作系统将实际接收的字节szRecvBytes等信息传递给我们,在函数里对收到的buf进行打印

第17~19行,分别声明了用于接收数据的buf,实际接收的字节数recvBytes,和用于WSARecv调用的wsaBuf,由于在回调函数CompletionRoutine中也会用到这些变量,所以它们被声明成了全局变量。

第55~59行,初始化进行IO堆叠的相干变量,包括wsaBuf、hEvent和overLapped。

第61行,调用WSARecv进行数据的接收,并将定义好的CompletionRoutine函数传给其最后1个参数。

       下面是使用WSASend函数的1个例子,由于上上面的例子原理上相同,这里就不在赘述。

// WSASocketCompletionRoutineClnt.cpp : 定¡§义°?控?制?台¬¡§应®|用®?程¨¬序¨°的Ì?入¨?口¨²点Ì?。¡ê
//
 
#include "stdafx.h"
#include "stdio.h"
#include "stdlib.h"
#include <WinSock2.h>
#include <string.h>
 
#pragma comment(lib,"ws2_32.lib")
 
#define BUF_SIZE 30
 
void ErrorHandler(const char* message);
void CALLBACK CompletionRoutine(DWORD dwError,DWORDszRecvBytes,LPWSAOVERLAPPED lpOverlapped,DWORD flags);
 
char buf[BUF_SIZE]="Hello world";
DWORD recvBytes = 0;
int _tmain(int argc, _TCHAR* argv[])
{
    SOCKETservSock;
    SOCKADDR_INservAddr;
 
    WSAOVERLAPPEDoverLapped;
    HANDLEhEvent;
    WSABUFwsaBuf;
    DWORDret,flags=0;
 
    WSADATAwsaData;
    WSAStartup(MAKEWORD(2,2),&wsaData);
 
 
 
    //创ä¡ä建¡§支¡ì持?IO复¡ä用®?的Ì?套¬¡Á接¨®字Á?
    servSock=WSASocket(AF_INET,SOCK_STREAM,0,NULL,0,WSA_FLAG_OVERLAPPED);
    if(servSock==INVALID_SOCKET)
        ErrorHandler("WSASocket Error");
 
    memset(&servAddr,0,sizeof(servAddr));
    servAddr.sin_family=AF_INET;
    servAddr.sin_addr.s_addr=inet_addr("127.0.0.1");
    servAddr.sin_port=htons(atoi("8888"));
 
    connect(servSock,(SOCKADDR*)&servAddr,sizeof(servAddr));
 
    memset(&overLapped,0,sizeof(overLapped));
    wsaBuf.buf=buf;
    wsaBuf.len=BUF_SIZE;
    hEvent=WSACreateEvent();
    overLapped.hEvent=hEvent;
 
    if(WSASend(servSock,&wsaBuf,1,&recvBytes,flags,&overLapped,CompletionRoutine)==SOCKET_ERROR)
    {
        if(GetLastError()==WSA_IO_PENDING)
            puts("background data send");
    }
    ret=WSAWaitForMultipleEvents(1,&hEvent,false,WSA_INFINITE,true);
    if(ret==WAIT_IO_COMPLETION)
        puts("Overlapped I/O Compeleted");
    else
        ErrorHandler("WSASend Error");
 
    WSACloseEvent(hEvent);
    closesocket(servSock);
    WSACleanup();
    return 0;
}
 
void CALLBACK CompletionRoutine(DWORD dwError,DWORDszRecvBytes,LPWSAOVERLAPPED lpOverlapped,DWORD flags)
{
    if(dwError!=0)
    {
        ErrorHandler("CompletionRoutine Error");
    }
    else
    {
        recvBytes=szRecvBytes;
        printf("Send Message:%s\n",buf);
    }
}
 
void ErrorHandler(const char* message)
{
    fputs(message,stderr);
    fputc('/n',stderr);
    exit(1);
}
 


Github位置:
https://github.com/HymanLiuTS/NetDevelopment
克隆本项目:
git clone git@github.com:HymanLiuTS/NetDevelopment.git
获得本文源代码:
git checkout NL55

 

------分隔线----------------------------
------分隔线----------------------------

最新技术推荐