網路程式設計(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