1. 程式人生 > >網路程式設計(55)—— Windows下使用WSASocket基於Completion Routine進行IO重疊

網路程式設計(55)—— Windows下使用WSASocket基於Completion Routine進行IO重疊

一、引言

        上一文中我們介紹了使用基於事件進行IO重疊的方法,本文主要介紹另外一種,基於回撥函式void CALLBACK CompletionRoutine(DWORD dwError,DWORDszRecvBytes,LPWSAOVERLAPPED lpOverlapped,DWORD flags)進行IO重疊。首先,我們先介紹一種執行緒的狀態——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狀態後才會被作業系統呼叫。

二、定義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
);

        在上一文中,採用基於事件的IO重疊時,將WSARecv最後一個引數設定成了NULL。而實際上他就是我們定義的回撥函式CompletionRoutine的函式指標。而CompletionRoutine函式的後三個引數分別來自WSARecv的第4~6個引數。當執行緒進入alertable wait狀態後,作業系統就會執行CompletionRoutine函式。

三、應用例項

        上述是使用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函式傳給其最後一個引數。

       下面是使用WSASend函式的一個例子,由於上上面的例子原理上相同,這裡就不在贅述。

// 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

相關推薦

網路程式設計55—— Windows使用WSASocket基於Completion Routine進行IO重疊

一、引言         上一文中我們介紹了使用基於事件進行IO重疊的方法,本文主要介紹另外一種,基於回撥函式void CALLBACK CompletionRoutine(DWORD dwError,DWORDszRecvBytes,LPWSAOVERLAPPED lpO

網路程式設計52—— Windows使用WSAEventSelect實現非同步通知IO

一、同步IO和非同步IO         同步IO是指發生IO事件的時間點和相關函式返回的時間點一致。如使用send函式傳送資料時,所有的資料傳送到輸出緩衝區後,send函式才會返回,這種IO方式就是同步IO。非同步IO指函式先於IO事件返回。還是以send函式為例,呼叫s

網路程式設計53—— Windows使用WSAAsyncSelect實現視窗處理socket訊息

一、引言        上一文中我們介紹了使用WSAEventSelect實現非同步通知IO的方法,本文我們主要討論下使用WSAAsyncSelect處理socket的方法。本文的主要目標,是建立一個帶介面的回聲服務端,接收並返回客戶端傳過來的字串,並在介面上顯示該字串。為

網路程式設計46—— windows核心物件的兩種狀態

一、 什麼是核心物件?         我們知道程序、執行緒、檔案、互斥、訊號量這些都是作業系統級別的資源。我們在使用這些資源時,實際上都是由作業系統進行建立和管理的。作業系統為了管理這些資 源,會在其內部建立一個數據塊,也可以理解為一個結構體物件。這個資料塊就是核心物件。

c++ 網路程式設計TCP/IP LINUX/windows 多執行緒超詳細教程 以及 多執行緒實現服務端

#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #include <process.h> #include <winsock2.h> #include <win

c++ 網路程式設計TCP/IP LINUX/windows 多執行緒超詳細教程 以及 多執行緒實現服務端

原文作者:aircraft 原文連結:https://www.cnblogs.com/DOMLX/p/9661012.html  先講Linux下(windows下在後面可以直接跳到後面看): 一.執行緒基本概念 前面我們講過多程序伺服器,但我們知道它開銷很大

Windows網路程式設計:訊息選擇模型

概述 之前介紹過,系統提供了幾種網路模型用於非同步的網路互動,訊息選擇模型就是其中一種。 這種模型的使用需要在呼叫完socket()函式以後呼叫WSAAsyncSelect(),這個函式的宣告如下: int WSAAsyncSelect(SOCKET s,HWND h

Windows網路程式設計:非阻塞模式非同步模式

前面幾篇文章介紹的無論是TCP通訊還是UDP通訊都是阻塞式的,它們在執行recv或recvfrom時會線上程中等待,直到接收到資訊為止,所以在應用的時候一般都需要開闢子執行緒,在子執行緒裡專門做這類事情,不然它會影響主執行緒的執行。  系統提供三種網路模型

Windows網路程式設計:原始套接字開發

在呼叫socket()函式時,如果將第二個引數填為SOCK_RAW,代表建立的是原始套接字型別,第三個引數可以選擇IPPROTO_ICMP、IPPROTO_TCP、IPPROTO和IPPROTO_RAW。 #include <winsock2.h> #pragma co

Windows網路程式設計:IP Helper

IP Helper是Windows系統與IP配置和管理的重要介面,通過IP Helper 可以獲得很多跟網路配置相關的資訊。比如說本機IP、閘道器設定、網絡卡數量和連線資訊。 #include <windows.h> #include "iphlpapi.h" /* 全域

Windows網路程式設計:多執行緒訊息處理

對於服務端來說,呼叫accept()函式同意客戶端連線的請求後,需要處理完與這個客戶端的通訊後回到accept()繼續等待下一個客戶端的連線,如果一個客戶端請求連線時服務端並沒有在accept()處等待,客戶端是無法成功連上服務端的,因此併發客戶端連線的服務端必然是多執行緒的。 服務

Windows網路程式設計:建立UDP連線和收發訊息

UDP訊息的傳送和接收需要UDP連線,所以,上面的TCP連線已經不適用了,具體的區別主要有: 建立Socket時引數不同建立服務端時不需要listen和accept操作建立客戶端時不需要connect操作伺服器需要bind操作,客戶端不需要。 傳送和接收UDP訊息要用到sendt

Windows網路程式設計:建立TCP連線和收發訊息

先看服務端: // ConsoleApplication3.cpp : 定義控制檯應用程式的入口點。 // #include "stdafx.h" #define _WINSOCK_DEPRECATED_NO_WARNINGS //這個宣告要在stdafx.h的後面,但要

Windows網路程式設計:Socket簡介

Socket簡介 Socket被稱為套接字,描述了IP和埠等資訊,是一個通訊鏈的控制代碼。 微軟專門開發了一套支援多種網路協議的網路程式設計介面,叫做Winsock,Winsock是Windos SDK的一部分,全稱Windows Sockets API。它對多種協議做了封裝,S

Windows網路程式設計:TCP/IP協議

概述 這個協議是一個四層協議: 應用層,主要協議有HTTP、FTP等 傳輸層,主要協議有TCP、UDP等 網路層,主要協議有IP等 鏈路層,主要協議有ICMP等 下層中的協議總是為上層中的協議服務的,比如說應用層的HTTP、FTP協議都是基於T

從零開始學習音視訊程式設計技術35 windows編譯並除錯ffmpeg

前面介紹了Linux下編譯ffmpeg的方法,考慮到大部分時候測試ffmpeg功能都是使用的windows系統(至少我是這樣的),因此將戰場重新轉移到windows上。    前面寫了那麼多的程式碼,但都只是簡單的呼叫了ffmpeg的API,並不知道他內部是如何實現的。如果可

Windows安裝RabbitMQ服務

百度網盤 http lang gin 配置 ble localhost 語言 load 一:安裝RabbitMQ需要先安裝Erlang語言開發包,百度網盤地址:http://pan.baidu.com/s/1jH8S2u6。直接下載地址:http://erlang.org/

python學習-網路程式設計

udp的接收和傳送資料程式碼: udp的傳送資料程式碼如下: import socket def main():     #建立套接字     udp_socket = socket.socket(socket.AF_I

python------Socket網路程式設計粘包問題

一.socket網路程式設計  粘包:服務端兩次傳送指令在一起,它會把兩次傳送內容合在一起傳送,稱為粘包,從而出現錯誤。 解決方法:(比較low的方法) 有些需要實時更新的,用sleep有延遲,不能這樣解決問題。 解決方法之高階方法: 客戶端: 二.傳送檔案 ftp s

網路程式設計:埠那些事兒

TCP和UDP協議都存在一個叫做埠的東西,但埠卻不是IP協議的一部分。 埠被設計出來主要是為了給協議棧和應用對應: 協議棧用埠號將資料分配給不同的應用層程式 應用層程式用埠號去區分不同的連線,參見之前提到過的“四元組” TCP和UDP協議都使用了埠號(Port num