1. 程式人生 > >網路程式設計(52)—— Windows下使用WSAEventSelect實現非同步通知IO

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

一、同步IO和非同步IO

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

        什麼是非同步IO通知?

        非同步IO通知是指每當發生了IO事件——有資料需要寫或讀,作業系統就會產生一個事件,而我們可以根據這個事件進行相應的處理。

二、使用WSAEventSelect函式監視套接字

        WSAEventSelect函式可以監視一個套接字,當套接字發生IO事件後,它會產生一個非同步事件,並將該事件傳到WSAEventSelect函式的引數中。WSAEventSelect的函式原型如下:

int WSAEventSelect(
  __in SOCKET s,
  __in WSAEVENT hEventObject,
  __in long lNetworkEvents
);

s —— 需要監視的socket檔案描述符。

hEventObject —— 非同步事件控制代碼。當發生lNetworkEvents所指定的的事件時,WSAEventSelect會將該控制代碼所指的核心物件改為signaled狀態。

lNetworkEvents —— 註冊的需要監視的事件,按位表示。支援的事件列表如下:

事件巨集

描述

FD_READ

是否存在需要讀取的資料

FD_WRITE

是否有需要傳遞的資料

FD_ACCEPT

是否有新的連線請求

FD_CLOSE

是否有需要斷開的連線

三、使用WSACreateEvent建立non-signaled事件

        現在,我們需要建立一個man-reset的non-signaled事件,來傳遞給WSAEventSelect的第二個引數。我們可以選擇使用CreateEvent函式,它可以選擇建立的事件是man-reset還是auto-reset、signaled還是non-signaled。但是使用WSACreateEvent函式會更加方便,因為它直接就可以建立一個man-reset的non-signaled事件,而無需任何引數。WSACreateEvent的函式原型如下:

HANDLE WSACreateEvent(void);
如果需要關閉事件,則呼叫WSACloseEvent函式:
BOOL WSACloseEvent(
  __in  WSAEVENT hEvent
);

四、使用WSAWaitForMultipleEvents驗證是否發生事件

        WSAWaitForMultipleEvents用來驗證是否發生了相關的非同步事件,其原型如下:

DWORD WSAWaitForMultipleEvents(
  __in  DWORD cEvents,
  __in  const WSAEVENT *lphEvents,
  __in  BOOL fWaitAll,
  __in  DWORD dwTimeout,
  __in  BOOL fAlertable
);
cEvents —— 需要驗證是否轉變為signaled事件的總的個數。
lphEvents —— 存放事件控制代碼的陣列。
fWaitAll —— 置為True時,所有事件變成Signaled狀態時返回;置為False時,只要發生一個事件變成signaled狀態就返回。
dwTimeout —— 設定等待超時,如果設為WSA_INFINITE則一直等待,直到事件變為signaled狀態。
fAlertable —— 傳遞為True時進入alertable wait狀態。
返回值 —— 返回值減去WSA_WAIT_EVENT_0時,得到是發生變成signaled狀態的事件在lphEvents的索引值(是索引最小的那個事件的索引值)。

五、使用WSAEnumNetworkEvents區分事件型別

        當我們通過使用WSAWaitForMultipleEvents等待到一個事件所指的核心物件變成singnaled狀態之後,我們可以使用WSAEnumNetworkEvents來驗證該事件的型別,是FD_READ事件、FD_WRITE事件還是FD_ACCEPT事件?

        WSAEnumNetworkEvents的原型如下:

int WSAEnumNetworkEvents(
  __in   SOCKET s,
  __in   WSAEVENT hEventObject,
  __out  LPWSANETWORKEVENTS lpNetworkEvents
);

s —— 是監視的socket描述符。

hEventObject—— 是建立的非同步通知IO事件。

lpNetworkEvents —— 是WSANETWORKEVENTS結構體物件,該結構體的定義如下:
typedef struct _WSANETWORKEVENTS {
  long lNetworkEvents;
  int  iErrorCode[FD_MAX_EVENTS];
} WSANETWORKEVENTS, *LPWSANETWORKEVENTS;
    WSANETWORKEVENTS裡的lNetworkEvents用來儲存發生的事件型別,我們可以通過位與操作判斷是否是發生了某事件:
If((netEvents.lNetworkEvents &FD_ACCEPT)
{
    ……
}
    iErrorCode儲存的是發生的錯誤碼的位陣列,通過陣列成員判斷髮生的錯誤型別:
if(netEvents.iErrorCode[FD_CLOSE_BIT]!=0)
{
    puts("close error");
}

六、程式碼示例

        以下程式碼,是使用非同步IO通知事件實現的服務端程式碼:

// WSAEventSelectServ.cpp : 定¡§義°?控?制?臺¬¡§應®|用®?程¨¬序¨°的Ì?入¨?口¨²點Ì?。¡ê
//
 
#include "stdafx.h"
#include<stdio.h>
#include<stdlib.h>
#include<winsock2.h>
 
#pragma comment(lib,"ws2_32.lib")
 
#define BUF_SIZE 30
#define EVENT_SIZE 64
 
void ErrorHandler(const char* message);
void CompressEvents(HANDLE* events,int pos,int size);
void CompressSocks(SOCKET* socks,int pos,int size);
 
int _tmain(int argc, _TCHAR* argv[])
{
    WSADATAwsaData;
 
    SOCKETservSock,clntSock;
    SOCKADDR_INservAddr,clntAddr;
    int clntAddrSz;
    SOCKETsocks[EVENT_SIZE];
    int strLen;
    int eventNum = 0;
 
    char buf[BUF_SIZE];
 
    HANDLEhEvent;
    HANDLEevents[EVENT_SIZE];
    int minPos;
    WSANETWORKEVENTSnetEvents;
   
 
    if(WSAStartup(MAKEWORD(2,2),&wsaData)==SOCKET_ERROR)
        ErrorHandler("WSAStartUp Error");
 
    servSock=socket(AF_INET,SOCK_STREAM,0);
    if(servSock==INVALID_SOCKET)
        ErrorHandler("socket error");
 
    memset(&servAddr,0,sizeof(servAddr));
    servAddr.sin_family=AF_INET;
    servAddr.sin_addr.s_addr=INADDR_ANY;
    servAddr.sin_port=htons(atoi("8888"));
 
    if(bind(servSock,(constsockaddr*)&servAddr,sizeof(servAddr))==SOCKET_ERROR)
        ErrorHandler("bind error");
 
    if(listen(servSock,5)==SOCKET_ERROR)
        ErrorHandler("listen error");
 
    hEvent=WSACreateEvent();
    //將?用®?來¤¡ä監¨¤視º¨®servSock的Ì?事º?件t放¤?到Ì?第̨²一°?個?位?置?
    events[eventNum]=hEvent;
    socks[eventNum]=servSock;
 
    //注Á¡é冊¨¢servSock
    WSAEventSelect(servSock,hEvent,FD_ACCEPT);
 
    while(1)
    {
        //調Ì¡Â用®?WSAWaitForMultipleEvents等̨¨待äy事º?件t變À?成¨¦signaled狀Á¡ä態¬?,ê?設¦¨¨置?等̨¨待äy一°?個?事º?件t即¡ä返¤¦Ì回?
        //且¨°不?設¦¨¨置?超?時º¡À,ê?永®¨¤遠?等̨¨待äy
        minPos=WSAWaitForMultipleEvents(eventNum+ 1,events,false,WSA_INFINITE,false);
        //得Ì?到Ì?索¡Â引°y值¦Ì
        minPos=minPos-WSA_WAIT_EVENT_0;
        //遍À¨¦歷¤¨²數ºy組Á¨¦其?他?元a素?,ê?調Ì¡Â用®?WSAWaitForMultipleEvents,ê?驗¨¦證¡è其?他?元a素?對?應®|的Ì?內¨²核?對?象¨®是º?否¤?進?入¨?
        //signaled狀Á¡ä態¬?
        for (inti=minPos;i<eventNum + 1;i++)
        {
            int otherPos = minPos;
            if(i!=minPos)
                otherPos=WSAWaitForMultipleEvents(1,events,true,0,false);
            //排?除y未¡ä編À¨¤程¨¬signaled狀Á¡ä態¬?的Ì?事º?件t
            if(otherPos==WSA_WAIT_FAILED||otherPos==WSA_WAIT_TIMEOUT)
                continue;
 
            WSAEnumNetworkEvents(socks[i],events[i],&netEvents);
            if(netEvents.lNetworkEvents & FD_ACCEPT)
            {
                if(netEvents.iErrorCode[FD_ACCEPT_BIT]!=0)
                {
                    puts("accept error");
                    break;
                }
                clntAddrSz=sizeof(clntAddr);
                clntSock=accept(socks[i],(SOCKADDR*)&clntAddr,&clntAddrSz);
                eventNum++;
                socks[eventNum]=clntSock;
                events[eventNum]=WSACreateEvent();
                WSAEventSelect(socks[eventNum],events[eventNum],FD_READ|FD_CLOSE);
                puts("New Client Connected ...");
            }
 
            if(netEvents.lNetworkEvents & FD_READ)
            {
                if(netEvents.iErrorCode[FD_READ_BIT]!=0)
                {
                    puts("recv error");
                    break;
                }
                strLen=recv(socks[i],buf,BUF_SIZE,0);
                send(socks[i],buf,strLen,0);
            }
 
            if(netEvents.lNetworkEvents & FD_CLOSE)
            {
                if(netEvents.iErrorCode[FD_CLOSE_BIT]!=0)
                {
                    puts("close error");
                    break;
                }
                WSACloseEvent(events[i]);
                closesocket(socks[i]);
                CompressEvents(events,eventNum,EVENT_SIZE);
                CompressSocks(socks,eventNum,EVENT_SIZE);
                eventNum--;
            }
 
        }
    }
    WSACleanup();
    return 0;
}
 
void ErrorHandler(const char* message)
{
    fputs(message,stderr);
    fputc('\n',stderr);
    exit(1);
}
 
void CompressEvents(HANDLE* events,int pos,int size)
{
    while(pos<size-1)
    {
        events[pos]=events[pos+1];
    }
}
 
void CompressSocks(SOCKET* socks,int pos,int size)
{
    while(pos<size-1)
    {
        socks[pos]=socks[pos+1];
    }
}


 Github位置:https://github.com/HymanLiuTS/NetDevelopment克隆本專案:git clone git@github.com:HymanLiuTS/NetDevelopment.git獲取本文原始碼:
git checkout NL52

相關推薦

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

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

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

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

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

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

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

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

遊戲網路程式設計——WebSocket入門及實現自己的WebSocket協議

(一)WebSocket簡介 短連線:在傳統的Http協議中,客戶端和伺服器端的通訊方式是短連線的方式,也就是伺服器端並不會保持一個和客戶端的連線,在訊息傳送後,會斷開這個連線,客戶端下次通訊時,必須再建立和伺服器的新連線,這就是短連線。在短連結的情況下,客戶

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