winsock2 中引入了重疊I/O(Overlapped I/O)的概念并且要求所有的傳輸協(xié)議提供者都支持這一功能。 ?他的功能高于前面我們提過的三種,但是最強(qiáng)悍的還是我們后面要說的完成端口。
基本原理:讓應(yīng)用程序使用一個(gè)重疊的數(shù)據(jù)結(jié)構(gòu),一次投遞一個(gè)活多個(gè)winsock I/O請求,針對那些提交的清酒,在他們完成之后,應(yīng)用程序可為他們提供服務(wù)。 ?應(yīng)用程序可通過ReadFile和WriteFile兩個(gè)函數(shù)執(zhí)行I/O操作。 要注意:重疊I/O僅能在由WSASocket函數(shù)打開的套接字上使用。要想在一個(gè)套接字上使用重疊I/O,首先必須使用 WSA_FLAG_OVERLAPPED 這個(gè)標(biāo)志。
[cpp] ?view plaincopy
SOCKET?s?=?WSASocket(AF_INET,?SOCK_STREAM,?0,?NULL,?0,?WSA_FLAG_OVERLAPPED);??
當(dāng)你創(chuàng)建套接字的時(shí)候,你使用socket函數(shù),她會(huì)默認(rèn)設(shè)置WSA_FLAG_OVERLAPPED 標(biāo)志。
ok,現(xiàn)在我們已經(jīng)成功的建立了一個(gè)套接字,同時(shí)將其他與一個(gè)本地接口綁定到一起后就可以開始進(jìn)行重疊I/O操作了。在這里,我們以前用的send,recv等函數(shù)將被改變成為WSASend,WSARecv。
來看一下這些函數(shù):
?WSASend ? ? ? ? ? ? //?sends data on a connected socket. 提供一個(gè)指向已填充的數(shù)據(jù)緩沖區(qū)的指針。 ?WSASendTo ? ? ? ?//?sends data to a specific destination, using overlapped I/O where applicable. ?WSARecv ? ? ? ?//?receives data from a connected socket. ?WSARecvFrom ? ? ?//receives data on a socket and stores the source address. ?提供存放接收數(shù)據(jù)的緩沖區(qū) ?WSAIoctl ? ? ?//allows for miscellaneous control of a socket. ? 還可以使用重疊I/O操作的延遲完成特性。 ?AcceptEx ? //accepts a new connection, returns the local and remote address, and receives the first block of data sent by the client application. ?TrnasmitFile ? //transmits file data over a connected socket handle.?uses the operating system's cache manager to retrieve the file data, and provides high-performance file data transfer over sockets.
這里面WS_IO_PENDING 是最常見的返回值,這是說明我們的重疊函數(shù)調(diào)用成功了,但是I/O操作還沒有完成。
如果和一個(gè)WSAOVERLAPPED結(jié)構(gòu)一起來調(diào)用這些函數(shù),那么函數(shù)會(huì)立即完成并返回,無論套接字是否是阻塞模式。判斷I/O請求是否成功的方法有兩個(gè),分別是:
1、等待 ? 事件對象通知。
2、通過 完成例程。
我們談第一個(gè),事件通知:
在重疊函數(shù)的參數(shù)中都有一個(gè)參數(shù)是:Overlapped,我們可以假設(shè)是把我們的WSARecv這樣的操作“綁定”到這個(gè)重疊結(jié)構(gòu)上,提交一個(gè)請求,而不是將錯(cuò)做立即完成,其他的事情交給重疊結(jié)構(gòu)去做,而其中的重疊結(jié)構(gòu)又要與windows的事件對象“綁定”到一起,這樣我們調(diào)用玩WSARecv以后就OK了,等到重疊操作完成后自然會(huì)有與之對應(yīng)的時(shí)間來通知我們操作完成了,然后我們就可以來根據(jù)重疊操作的結(jié)果取得我們想要的數(shù)據(jù)了。
重疊I/O的事件通知方法要求將win32事件對象與WSOVERLAPPED結(jié)構(gòu)關(guān)聯(lián)在一起,當(dāng)I/O操作完成后,時(shí)間的狀態(tài)會(huì)變成“已傳信”狀態(tài),也就是激發(fā)狀態(tài)。
來看一下WSAOVERLAPPED結(jié)構(gòu):
[cpp] ?view plaincopy
typedef ? struct ?_WSAOVERLAPPED?{?? ????DWORD ????Internal;?? ????DWORD ????InternalHigh;?? ????DWORD ????Offset;?? ????DWORD ????OffsetHigh; ?? ????WSAEVENT?hEvent;?? }?WSAOVERLAPPED,?FAR?*?LPWSAOVERLAPPED;?? ?? typedef ? struct ?_WSAOVERLAPPED?{?? ??ULONG_PTR ?Internal;?? ??ULONG_PTR ?InternalHigh;?? ??union ?{?? ?????struct ?{?? ???????DWORD ?Offset;?? ???????DWORD ?OffsetHigh;?? ?????};????PVOID ?Pointer;?? ??};?? ??HANDLE ?hEvent;?? }?WSAOVERLAPPED,??*LPWSAOVERLAPPED;??
當(dāng)重疊I/O請求完成后,應(yīng)用程序要負(fù)責(zé)取回重疊I/O操作的結(jié)果,一個(gè)重疊請求操作最終完成后,在事件通知方法中,winsock會(huì)更改與一個(gè)WSAOVERLAPPED結(jié)構(gòu)對應(yīng)的一個(gè)事件對象的事件傳信狀態(tài), 將它從“未傳信”變?yōu)椤耙褌餍拧薄?? ?由于一個(gè)事件對象已分配給WSAOVERLAPPED結(jié)構(gòu),所以只需要簡單滴調(diào)用WSAWaitForMultipleEvents函數(shù),從而判斷出一個(gè)重疊I/O調(diào)用在什么時(shí)候完成。
還有一個(gè)函數(shù)是取得重疊結(jié)構(gòu),他是WSAGetOverlappedResult函數(shù),他是在發(fā)現(xiàn)一次重疊請求完成后才可以執(zhí)行。用來判斷這個(gè)重疊調(diào)用到底是成功還是失敗。
[cpp] ?view plaincopy
BOOL ?WSAAPI?WSAGetOverlappedResult(?? ??__in??????????SOCKET?s,?? ??__in??????????LPWSAOVERLAPPED?lpOverlapped,?? ??__out?????????LPDWORD ?lpcbTransfer, ?? ??__in??????????BOOL ?fWait, ?? ??__out?????????LPDWORD ?lpdwFlags ?? );??
當(dāng)函數(shù)調(diào)用成功那么就會(huì)返回TRUE, 表示重疊操作成功,并且lpcbTransfer參數(shù)指向的值已進(jìn)行了更新。 ?
如果反回了FALSE,那么可能是由于某種原因造成了錯(cuò)誤:
1、重疊I/O已經(jīng)完成,但是又錯(cuò)誤;
2、重疊I/O操作仍處于待解決狀態(tài);
3、重疊操作的完成狀態(tài)不能判斷,因?yàn)楹瘮?shù)的參數(shù)存在錯(cuò)誤。
當(dāng)函數(shù)失敗后,lpcbTransfer參數(shù)指向的值不會(huì)進(jìn)行更新,而且我們的應(yīng)用程序應(yīng)調(diào)用WSAGerLastError函數(shù)來調(diào)查到底是什么原因造成的。
OK,下面來看一下重疊I/O的編程步驟:
1、創(chuàng)建套接字,開始在指定的端口上監(jiān)聽連接請求。
[cpp] ?view plaincopy
SOCKET ?ListenSocket, ? ?? AcceptSocket; ??? WSAOVERLAPPED ?AcceptOverlapped; ??? WSABUF ?DataBuf[DATA_BUFSIZE]?; ? ??? WSADATA?wsaData;?? WSAStartup(MAKEWORD(2,2),&wsaData);?? ?? ListenSocket?=?socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); ??? ?? SOCKADDR_IN?ServerAddr; ??? ServerAddr.sin_family=AF_INET; ??? ServerAddr.sin_addr.S_un.S_addr ?=htonl(INADDR_ANY); ??? ServerAddr.sin_port=htons(8888);?? ?? bind(ListenSocket,(LPSOCKADDR)&ServerAddr,?sizeof (ServerAddr));? ?? ?? listen(ListenSocket,?5); ??
2、接受一個(gè)客戶端進(jìn)入的連接請求。
[cpp] ?view plaincopy
AcceptSocket?=?accept?(ListenSocket,?NULL,NULL)?;??? 當(dāng)然,這里是我偷懶,如果想要獲得連入客戶端的信息(記得論壇上也常有人問到),accept的后兩個(gè)參數(shù)就不要用NULL,而是這樣?? SOCKADDR_IN?ClientAddr; ??? int ?addr_length= sizeof (ClientAddr);?? AcceptSocket?=?accept(ListenSocket,(SOCKADDR*)&ClientAddr,?&addr_length);?? ?? LPCTSTR ?lpIP?= ?inet_ntoa(ClientAddr.sin_addr); ? ?? UINT ?nPort?=?ClientAddr.sin_port; ? ??
3、為接受的套接字創(chuàng)建一個(gè)WSAOVERLAPPED結(jié)構(gòu),并給這個(gè)結(jié)構(gòu)分配一個(gè)事件對象句柄,同時(shí)將該事件對象句柄分配給一個(gè)事件數(shù)組,以便稍后WSAWaitForMultipleEvents函數(shù)使用。
[cpp] ?view plaincopy
WSAEVENT ?EventArray[WSA_MAXIMUM_WAIT_EVENTS]; ??? DWORD ?dwEventTotal?=?0, ? ?? dwRecvBytes?=?0, ??? Flags?=?0; ??? ?? #define?DATA_BUFSIZE ?4096 ?//?接收緩沖區(qū)大小 ??
[cpp] ?view plaincopy
?? ?? EventArray[dwEventTotal]?=?WSACreateEvent(); ??? ?? ZeroMemory(&AcceptOverlapped,?sizeof (WSAOVERLAPPED)); ? ?? AcceptOverlapped.hEvent?=?EventArray[dwEventTotal]; ??? ?? char ?buffer[DATA_BUFSIZE];?? ZeroMemory(buffer,?DATA_BUFSIZE);?? DataBuf.len?=?DATA_BUFSIZE;?? DataBuf.buf?=?buffer; ??? dwEventTotal?++; ???
4、在套接字上投遞一個(gè)異步WSARecv請求,指定參數(shù)為WSAOVERLAPPED結(jié)構(gòu),注意函數(shù)通常會(huì)以失敗告終,返回SOCKET_ERROR錯(cuò)誤狀態(tài)WSA_IO_PENDING(I/O操作沒有完成)。
[cpp] ?view plaincopy
if (WSARecv(AcceptSocket?,&DataBuf,1,&dwRecvBytes,&Flags,?? &?AcceptOverlapped,?NULL)?==?SOCKET_ERROR)?? {??? ?? ?? if (WSAGetLastError()?!=?WSA_IO_PENDING) ??? {?? ?? closesocket(AcceptSocket);?? WSACloseEvent(EventArray[dwEventTotal]);?? }?? }??
5、使用步驟3的事件數(shù)組,調(diào)用WSAWaitForMultipleEvents函數(shù),并等待與重疊調(diào)用關(guān)聯(lián)在一起的事件進(jìn)入“已傳信”狀態(tài)。
6、WSAWaitForMultipleEvents函數(shù)返回后,針對“已傳信”狀態(tài)的事件,調(diào)用WSAResultEvent函數(shù)來重置事件,從而重設(shè)事件對象,并對完成的重疊請求進(jìn)行處理。
[cpp] ?view plaincopy
WSAResetEvent(EventArray[dwIndex]);??
[cpp] ?view plaincopy
DWORD ?dwIndex;?? ?? ?? dwIndex?=?WSAWaitForMultipleEvents(dwEventTotal,??? EventArray?,FALSE?,WSA_INFINITE,FALSE);?? ?? dwIndex?=?dwIndex?–?WSA_WAIT_EVENT_0;??
7、使用WSAGetOverlappedResult函數(shù)來判斷重疊調(diào)用的返回狀態(tài)是什么。
[cpp] ?view plaincopy
DWORD ?dwBytesTransferred;?? WSAGetOverlappedResult(?AcceptSocket,?AcceptOverlapped?,?? &dwBytesTransferred,?FALSE,?&Flags);?? ?? ?? if (dwBytesTransferred?==?0)?? {?? closesocket(AcceptSocket);?? WSACloseEvent(EventArray[dwIndex]); ??? return ;?? }??
8、在套接字上投遞另一個(gè)重疊WSARecv請求。
9、重復(fù)5到8。
在上文中我們提到一個(gè)AcceptEx函數(shù),這個(gè)函數(shù)是在重疊I/O模型中允許以一種重疊方式,事件對客戶端連接。他位于Mswsock.h頭文件以及Mswsock.lib庫文件中。
這個(gè)函數(shù)和accept的區(qū)別是:我們必須提供接受的套接字,而不是讓函數(shù)自動(dòng)為我們創(chuàng)建。
[cpp] ?view plaincopy
BOOL ?AcceptEx(?? ??__in??????????SOCKET?sListenSocket,?? ??__in??????????SOCKET?sAcceptSocket,?? ??__in??????????PVOID ?lpOutputBuffer, ?? ??__in??????????DWORD ?dwReceiveDataLength, ?? ??__in??????????DWORD ?dwLocalAddressLength, ?? ??__in??????????DWORD ?dwRemoteAddressLength, ?? ??__out?????????LPDWORD ?lpdwBytesReceived, ?? ??__in??????????LPOVERLAPPED?lpOverlapped?? );??
要知道AcceptEx函數(shù)只能由這里給大家說的“事件通知”方式獲取異步I/O請求的結(jié)果,在"完成例程”中是無法使用的。
下面給出win32重疊io,來讀取文件:
[cpp] ?view plaincopy
char ?buf[512*10000];?? int ?readdata( void )?? {?? ????BOOL ?bRet;??? ?? ????HANDLE ?hFile;??? ?? ????DWORD ?numRead;??? ?? ????OVERLAPPED?overlapped;????? ?????? ????hFile?=?CreateFile("..\\se.zip" ,?GENERIC_READ,?FILE_SHARE_READ|FILE_SHARE_WRITE,?NULL,?OPEN_EXISTING,??? ???????????????????????FILE_FLAG_OVERLAPPEN,?NULL);?? ?????? ????if ?(INVALID_HANDLE_VALUE?==?hFile)?? ????{?? ????????return ?-1;?? ????}?? ?????? ????memset(buf,?0,?sizeof ( char )?*?512*10000);??? ?? ????memset(&overlapped,?0,?sizeof (overlapped));??? ?? ????overlapped.Offset?=?0;?????? ?????? ????bRet?=?ReadFile(hFile,?buf,?512*10000,?&numRead,?&overlapped);?? ?????? ????if (TRUE?==?bRet)?? ?? ????{?? ?????????? ????}?? ????else ?? ?? ????{?? ????????if ?(ERROR_IO_PENDING?==?GetLastError())?? ?? ????????{?? ????????????WaitForSingleObject(hFile,?INFINITE);????? ?????????????? ????????????bRet?=?GetOverlappedResult(hFile,?&overlapped,?&numRead,?TRUE);?? ????????????if (TRUE?==?bRet)?? ????????????{?? ????????????????? ????????????????? ????????????}?? ????????????else ?? ????????????{?? ?????????????? ????????????}?? ????????}?? ????????else ?? ????????{?? ?????????? ????????}?? ????}?? ????CloseHandle(hFile);?? }?? ?? }??
下面將講述完成端口,那個(gè)更加高效,但是也更加困難;
2012/9/2
jofranks 于南昌
總結(jié)
以上是生活随笔 為你收集整理的【网络编程】之十、重叠IO Overlapped IO 的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔 推薦給好友。