Windows 非阻塞或异步 socket
2019獨角獸企業重金招聘Python工程師標準>>>
異步與非阻塞區別見我的另外一篇文章Socket 同步/異步與阻塞/非阻塞區別
select
WSAAsyncSelect
WSAEventSelect
重疊(Overlapped)I/O
IOCP:完成端口
Select
首先要使用ioctlsocket設置為非阻塞模式。
然后啟動線程,線程中不停select。
WSAAsyncSelect
WSAAsyncSelect模型是Windows下最簡單易用的一種Socket I/O模型。使用這種模型時,Windows會把網絡事件以消息的形勢通知應用程序。此模型提供了讀寫數據能力的異步通知,但不提供異步數據傳送。需要在消息響應函數里send(一般為resend)和receive。由于該模型基于Windows消息機制,必須在應用程序中創建窗口。雖然可以在開發中,確定是否顯示該窗口。?
WSAEventSelect
通常與WSACreateEvent、WSAResetEvent、WSACloseEvent、WSAWaitForMultileEvents和WSAEnumNetworkEvents一起使用,無需創建窗口。WSAWaitForMultileEvents檢查是否有Event,WSAEnumNetworkEvents枚舉事件類型,FD_READ、FD_WRITE等。
函數最多可以支持WSA_MAXIMUM_WAIT_EVENTS(64)個對象.該函數會等待網絡事件的發生,如果過了指定了時間(dwTimeOut)則返回WSA_WAIT_TIMEOUT,如果在規定的時間內有事件發生,則返回該事件對象的索引(注意:在程序中要想得到發生的事件的真正索引需得用返回值減去WSA_WAIT_EVENT_0),調用失敗返回WSA_WAIT_FAILED.如果將參數fWaitAll設置成false如果有多個網絡事件發生該函數也只返回一個事件對象索引,并且該事件是在事件句柄數組中最前面的一個.解決方法是循環調用該函數處理后面的受信事件.?
重疊(Overlapped)I/O
它和之前模型不同的是,使用重疊模型的應用程序通知緩沖區收發系統直接使用數據,也就是說,如果應用程序投遞了一個10KB大小的緩沖區來接收數據,且數據已經到達套接字,則該數據將直接被拷貝到投遞的緩沖區。之前的模型都是在套接字的緩沖區中,當通知應用程序接收后,在把數據拷貝到程序的緩沖區。
——摘自http://zhoumf1214.blog.163.com/blog/static/5241940201211705318496/
以receive為例,之前的模型,需要自己從套接字的緩沖區拷貝至程序緩沖區,而重疊IO則是操作系統直接將數據拷貝至程序緩沖區。
重疊模型的核心是一個重疊數據結構。若想以重疊方式使用文件,必須用 FILE_FLAG_OVERLAPPED標志打開它。
有2種方式實現:
1. 事件
先WaitForSingleObject/WaitForMultipleObjects或WSAWaitForMultipleEvents函數?,然后調用(WSA)GetOverlappedResult()函數,最后,使用指針偏移定位就可以準確操作接受到的數據了。
與其他事件類似,最大個數為64.
2.?完成例程(非完成端口)
ReadFileEx(),傳遞回調函數指針。
完成端口
所謂“完成端口",實際是Win32 、Windows NT以及Windows 2000采用的一種I / O構造機制,除套接字句柄之外,實際上還可接受其他東西(重疊IO好像也可以)。
使用這種模型之前,首先要創建一個 I / O 完成端口對象(CreateIoCompletionPort),
NumberOfConcurrentThread參數的特殊之處在于,它定義了在一個完成端口上,同時允許執行的線程數量。理想情況下,我們希望每個處理器各自負責一個線程的運行,為完成端口提供服務,避免過于頻繁的線程“場景”切換。若將該參數設為 0,表明系統內安裝了多少個處理器,便允許同時運行多少個線程!
1) ?創建一個完成端口。第四個參數保持為 0,指定在完成端口上,每個處理器一次只允許執行一個工作者線程。
2) ?判斷系統內到底安裝了多少個處理器。
3) ?創建工作者線程,根據步驟 2 )得到的處理器信息,在完成端口上,為已完成的 I / O請求提供服務。在這個簡單的例子中,我們為每個處理器都只創建一個工作者線程。這是由于事先已預計到,到時不會有任何線程進入“掛起”狀態,造成由于線程數量的不足,而使處理器空閑的局面(沒有足夠的線程可供執行) 。調用C r e a t e T h r e a d函數時,必須同時提供一個工作者例程,由線程在創建好執行。本節稍后還會詳細討論線程的職責。
4) ?準備好一個監聽套接字,在端口 5 1 5 0上監聽進入的連接請求。
5) 使用a c c e p t函數,接受進入的連接請求。
6) ?創建一個數據結構,用于容納“單句柄數據” ,同時在結構中存入接受的套接字句柄。
7) ?調用C r e a t e I o C o m p l e t i o n P o r t,將自a c c e p t返回的新套接字句柄同完成端口關聯到一起。通過完成鍵(C o m p l e t i o n K e y)參數,將單句柄數據結構傳遞給 C r e a t e I o C o m p l e t i o n P o r t。
8) ?開始在已接受的連接上進行 I / O 操作。在此,我們希望通過重疊 I / O機制,在新建的套接字上投遞一個或多個異步 W S A R e c v或W S A S e n d請求。這些 I / O 請求完成后,一個工作者線程會為I / O請求提供服務,同時繼續處理未來的 I / O 請求,稍后便會在步驟 3 )指定的工作者例程中,體驗到這一點。
9) ?重復步驟5 ) ~ 8 ),直至服務器中止。
//?IOCP_TCPIP_Socket_Server.cpp#include?<WinSock2.h> #include?<Windows.h> #include?<vector> #include?<iostream>using?namespace?std;#pragma?comment(lib,?"Ws2_32.lib") //?Socket編程需用的動態鏈接庫 #pragma?comment(lib,?"Kernel32.lib") //?IOCP需要用到的動態鏈接庫/***?結構體名稱:PER_IO_DATA*?結構體功能:重疊I/O需要用到的結構體,臨時記錄IO數據**/ const?int?DataBuffSize??=?2?*?1024; typedef?struct {OVERLAPPED?overlapped;WSABUF?databuff;char?buffer[?DataBuffSize?];int?BufferLen;int?operationType; }PER_IO_OPERATEION_DATA,?*LPPER_IO_OPERATION_DATA,?*LPPER_IO_DATA,?PER_IO_DATA;/***?結構體名稱:PER_HANDLE_DATA*?結構體存儲:記錄單個套接字的數據,包括了套接字的變量及套接字的對應的客戶端的地址。*?結構體作用:當服務器連接上客戶端時,信息存儲到該結構體中,知道客戶端的地址以便于回訪。**/ typedef?struct {SOCKET?socket;SOCKADDR_STORAGE?ClientAddr; }PER_HANDLE_DATA,?*LPPER_HANDLE_DATA;//?定義全局變量 const?int?DefaultPort?=?6000; vector?<?PER_HANDLE_DATA*?>?clientGroup; //?記錄客戶端的向量組HANDLE?hMutex?=?CreateMutex(NULL,?FALSE,?NULL); DWORD?WINAPI?ServerWorkThread(LPVOID?CompletionPortID); DWORD?WINAPI?ServerSendThread(LPVOID?IpParam);//?開始主函數 int?main() { //?加載socket動態鏈接庫WORD?wVersionRequested?=?MAKEWORD(2,?2);?//?請求2.2版本的WinSock庫WSADATA?wsaData; //?接收Windows?Socket的結構信息DWORD?err?=?WSAStartup(wVersionRequested,?&wsaData);if?(0?!=?err){ //?檢查套接字庫是否申請成功cerr?<<?"Request?Windows?Socket?Library?Error!\n";system("pause");return?-1;}if(LOBYTE(wsaData.wVersion)?!=?2?||?HIBYTE(wsaData.wVersion)?!=?2){//?檢查是否申請了所需版本的套接字庫WSACleanup();cerr?<<?"Request?Windows?Socket?Version?2.2?Error!\n";system("pause");return?-1;}//?創建IOCP的內核對象/***?需要用到的函數的原型:*?HANDLE?WINAPI?CreateIoCompletionPort(*????__in???HANDLE?FileHandle, //?已經打開的文件句柄或者空句柄,一般是客戶端的句柄*????__in???HANDLE?ExistingCompletionPort, //?已經存在的IOCP句柄*????__in???ULONG_PTR?CompletionKey, //?完成鍵,包含了指定I/O完成包的指定文件*????__in???DWORD?NumberOfConcurrentThreads?//?真正并發同時執行最大線程數,一般推介是CPU核心數*2*?);**/HANDLE?completionPort?=?CreateIoCompletionPort(?INVALID_HANDLE_VALUE,?NULL,?0,?0);if?(NULL?==?completionPort){ //?創建IO內核對象失敗cerr?<<?"CreateIoCompletionPort?failed.?Error:"?<<?GetLastError()?<<?endl;system("pause");return?-1;}//?創建IOCP線程--線程里面創建線程池//?確定處理器的核心數量SYSTEM_INFO?mySysInfo;GetSystemInfo(&mySysInfo);//?基于處理器的核心數量創建線程for(DWORD?i?=?0;?i?<?(mySysInfo.dwNumberOfProcessors?*?2);?++i){//?創建服務器工作器線程,并將完成端口傳遞到該線程HANDLE?ThreadHandle?=?CreateThread(NULL,?0,?ServerWorkThread,?completionPort,?0,?NULL);if(NULL?==?ThreadHandle){cerr?<<?"Create?Thread?Handle?failed.?Error:"?<<?GetLastError()?<<?endl;system("pause");return?-1;}CloseHandle(ThreadHandle);}//?建立流式套接字SOCKET?srvSocket?=?socket(AF_INET,?SOCK_STREAM,?0);//?綁定SOCKET到本機SOCKADDR_IN?srvAddr;srvAddr.sin_addr.S_un.S_addr?=?htonl(INADDR_ANY);srvAddr.sin_family?=?AF_INET;srvAddr.sin_port?=?htons(DefaultPort);int?bindResult?=?bind(srvSocket,?(SOCKADDR*)&srvAddr,?sizeof(SOCKADDR));if(SOCKET_ERROR?==?bindResult){cerr?<<?"Bind?failed.?Error:"?<<?GetLastError()?<<?endl;system("pause");return?-1;}//?將SOCKET設置為監聽模式int?listenResult?=?listen(srvSocket,?10);if(SOCKET_ERROR?==?listenResult){cerr?<<?"Listen?failed.?Error:?"?<<?GetLastError()?<<?endl;system("pause");return?-1;}//?開始處理IO數據cout?<<?"本服務器已準備就緒,正在等待客戶端的接入...\n";//?創建用于發送數據的線程HANDLE?sendThread?=?CreateThread(NULL,?0,?ServerSendThread,?0,?0,?NULL);while(true){PER_HANDLE_DATA?*?PerHandleData?=?NULL;SOCKADDR_IN?saRemote;int?RemoteLen;SOCKET?acceptSocket;//?接收連接,并分配完成端,這兒可以用AcceptEx()RemoteLen?=?sizeof(saRemote);acceptSocket?=?accept(srvSocket,?(SOCKADDR*)&saRemote,?&RemoteLen);if(SOCKET_ERROR?==?acceptSocket){ //?接收客戶端失敗cerr?<<?"Accept?Socket?Error:?"?<<?GetLastError()?<<?endl;system("pause");return?-1;}//?創建用來和套接字關聯的單句柄數據信息結構PerHandleData?=?(LPPER_HANDLE_DATA)GlobalAlloc(GPTR,?sizeof(PER_HANDLE_DATA)); //?在堆中為這個PerHandleData申請指定大小的內存PerHandleData?->?socket?=?acceptSocket;memcpy?(&PerHandleData?->?ClientAddr,?&saRemote,?RemoteLen);clientGroup.push_back(PerHandleData); //?將單個客戶端數據指針放到客戶端組中//?將接受套接字和完成端口關聯CreateIoCompletionPort((HANDLE)(PerHandleData?->?socket),?completionPort,?(DWORD)PerHandleData,?0);//?開始在接受套接字上處理I/O使用重疊I/O機制//?在新建的套接字上投遞一個或多個異步//?WSARecv或WSASend請求,這些I/O請求完成后,工作者線程會為I/O請求提供服務 //?單I/O操作數據(I/O重疊)LPPER_IO_OPERATION_DATA?PerIoData?=?NULL;PerIoData?=?(LPPER_IO_OPERATION_DATA)GlobalAlloc(GPTR,?sizeof(PER_IO_OPERATEION_DATA));ZeroMemory(&(PerIoData?->?overlapped),?sizeof(OVERLAPPED));PerIoData->databuff.len?=?1024;PerIoData->databuff.buf?=?PerIoData->buffer;PerIoData->operationType?=?0; //?readDWORD?RecvBytes;DWORD?Flags?=?0;WSARecv(PerHandleData->socket,?&(PerIoData->databuff),?1,?&RecvBytes,?&Flags,?&(PerIoData->overlapped),?NULL);}system("pause");return?0; }//?開始服務工作線程函數 DWORD?WINAPI?ServerWorkThread(LPVOID?IpParam) {HANDLE?CompletionPort?=?(HANDLE)IpParam;DWORD?BytesTransferred;LPOVERLAPPED?IpOverlapped;LPPER_HANDLE_DATA?PerHandleData?=?NULL;LPPER_IO_DATA?PerIoData?=?NULL;DWORD?RecvBytes;DWORD?Flags?=?0;BOOL?bRet?=?false;while(true){bRet?=?GetQueuedCompletionStatus(CompletionPort,?&BytesTransferred,?(PULONG_PTR)&PerHandleData,?(LPOVERLAPPED*)&IpOverlapped,?INFINITE);if(bRet?==?0){cerr?<<?"GetQueuedCompletionStatus?Error:?"?<<?GetLastError()?<<?endl;return?-1;}PerIoData?=?(LPPER_IO_DATA)CONTAINING_RECORD(IpOverlapped,?PER_IO_DATA,?overlapped);//?檢查在套接字上是否有錯誤發生if(0?==?BytesTransferred){closesocket(PerHandleData->socket);GlobalFree(PerHandleData);GlobalFree(PerIoData);continue;}//?開始數據處理,接收來自客戶端的數據WaitForSingleObject(hMutex,INFINITE);cout?<<?"A?Client?says:?"?<<?PerIoData->databuff.buf?<<?endl;ReleaseMutex(hMutex);//?為下一個重疊調用建立單I/O操作數據ZeroMemory(&(PerIoData->overlapped),?sizeof(OVERLAPPED));?//?清空內存PerIoData->databuff.len?=?1024;PerIoData->databuff.buf?=?PerIoData->buffer;PerIoData->operationType?=?0; //?readWSARecv(PerHandleData->socket,?&(PerIoData->databuff),?1,?&RecvBytes,?&Flags,?&(PerIoData->overlapped),?NULL);}return?0; }//?發送信息的線程執行函數 DWORD?WINAPI?ServerSendThread(LPVOID?IpParam) {while(1){char?talk[200];gets(talk);int?len;for?(len?=?0;?talk[len]?!=?'\0';?++len){//?找出這個字符組的長度}talk[len]?=?'\n';talk[++len]?=?'\0';printf("I?Say:");cout?<<?talk;WaitForSingleObject(hMutex,INFINITE);for(int?i?=?0;?i?<?clientGroup.size();?++i){send(clientGroup[i]->socket,?talk,?200,?0); //?發送信息}ReleaseMutex(hMutex);?}return?0; }轉載于:https://my.oschina.net/shanlilaideyu/blog/485349
總結
以上是生活随笔為你收集整理的Windows 非阻塞或异步 socket的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: PartialView 加载Js
- 下一篇: windows phone开发第一步:搭