很幽默的講解六種Socket IO模型 Delphi版本(本身Select查看,WM_SOCKET消息通知,WSAEventSelect自動收取,Overlapped I/O 事件通知模型,Overl

很幽默的講解六種Socket IO模型(轉)
本文簡單介紹了當前Windows支持的各類Socket I/O模型,若是你發現其中存在什麼錯誤請務必賜教。 
一:select模型 
二:WSAAsyncSelect模型 
三:WSAEventSelect模型 
四:Overlapped I/O 事件通知模型 
五:Overlapped I/O 完成例程模型 
六:IOCP模型 
老陳有一個在外地工做的女兒,不能常常回來,老陳和她經過信件聯繫。他們的信會被郵遞員投遞到他們的信箱裏。 
這和Socket模型很是相似。下面我就以老陳接收信件爲例講解Socket I/O模型~~~ 
一:select模型 
老陳很是想看到女兒的信。以致於他每隔10分鐘就下樓檢查信箱,看是否有女兒的信~~~~~ 
在這種狀況下,"下樓檢查信箱"而後回到樓上耽誤了老陳太多的時間,以致於老陳沒法作其餘工做。 
select模型和老陳的這種狀況很是類似:周而復始地去檢查......若是有數據......接收/發送....... 
使用線程來select應該是通用的作法: 
procedure TListenThread.Execute; 
var 
addr : TSockAddrIn; 
fd_read : TFDSet; 
timeout : TTimeVal; 
ASock, 
MainSock : TSocket; 
len, i : Integer; 
begin 
MainSock := socket( AF_INET, SOCK_STREAM, IPPROTO_TCP ); 
addr.sin_family := AF_INET; 
addr.sin_port := htons(5678); 
addr.sin_addr.S_addr := htonl(INADDR_ANY); 
bind( MainSock, @addr, sizeof(addr) ); 
listen( MainSock, 5 ); 
while (not Terminated) do 
begin 
FD_ZERO( fd_read ); 
FD_SET( MainSock, fd_read ); 
timeout.tv_sec := 0; 
timeout.tv_usec := 500; 
if select( 0, @fd_read, nil, nil, @timeout ) > 0 then //至少有1個等待Accept的connection 
begin 
if FD_ISSET( MainSock, fd_read ) then 
begin 
for i:=0 to fd_read.fd_count-1 do //注意,fd_count  INVALID_SOCKET then 
....//爲ASock建立一個新的線程,在新的線程中再不停地select 
end; 
end; 
end; 
end; //while (not self.Terminated) 
shutdown( MainSock, SD_BOTH ); 
closesocket( MainSock ); 
end; 
二:WSAAsyncSelect模型 
後來,老陳使用了微軟公司的新式信箱。這種信箱很是先進,一旦信箱裏有新的信件,蓋茨就會給老陳打電話:喂,大爺,你有新的信件了!今後,老陳不再必頻繁上下樓檢查信箱了,牙也不疼了,你瞅準了,藍天......不是,微軟~~~~~~~~ 
微軟提供的WSAAsyncSelect模型就是這個意思。 
WSAAsyncSelect模型是Windows下最簡單易用的一種Socket I/O模型。使用這種模型時,Windows會把網絡事件以消息的形勢通知應用程序。 
首先定義一個消息標示常量: 
const WM_SOCKET = WM_USER + 55; 
再在主Form的private域添加一個處理此消息的函數聲明: 
private 
procedure WMSocket(var Msg: TMessage); message WM_SOCKET; 
而後就可使用WSAAsyncSelect了: 
var 
addr : TSockAddr; 
sock : TSocket; 
sock := socket( AF_INET, SOCK_STREAM, IPPROTO_TCP ); 
addr.sin_family := AF_INET; 
addr.sin_port := htons(5678); 
addr.sin_addr.S_addr := htonl(INADDR_ANY); 
bind( m_sock, @addr, sizeof(SOCKADDR) ); 
WSAAsyncSelect( m_sock, Handle, WM_SOCKET, FD_ACCEPT or FD_CLOSE ); 
listen( m_sock, 5 ); 
.... 
應用程序能夠對收到WM_SOCKET消息進行分析,判斷是哪個socket產生了網絡事件以及事件類型: 
procedure TfmMain.WMSocket(var Msg: TMessage); 
var 
sock : TSocket; 
addr : TSockAddrIn; 
addrlen : Integer; 
buf : Array [0..4095] of Char; 
begin 
//Msg的WParam是產生了網絡事件的socket句柄,LParam則包含了事件類型 
case WSAGetSelectEvent( Msg.LParam ) of 
FD_ACCEPT : 
begin 
addrlen := sizeof(addr); 
sock := accept( Msg.WParam, addr, addrlen ); 
if sock  INVALID_SOCKET then 
WSAAsyncSelect( sock, Handle, WM_SOCKET, FD_READ or FD_WRITE or FD_CLOSE ); 
end; 
FD_CLOSE : closesocket( Msg.WParam ); 
FD_READ : recv( Msg.WParam, buf[0], 4096, 0 ); 
FD_WRITE : ; 
end; 
end; 
三:WSAEventSelect模型 
後來,微軟的信箱很是暢銷,購買微軟信箱的人以百萬計數......以致於蓋茨天天24小時給客戶打電話,累得腰痠背痛,喝蟻力神都很差使~~~~~~ 
微軟改進了他們的信箱:在客戶的家中添加一個附加裝置,這個裝置會監視客戶的信箱,每當新的信件來臨,此裝置會發出"新信件到達"聲,提醒老陳去收信。蓋茨終於能夠睡覺了。 
一樣要使用線程: 
procedure TListenThread.Execute; 
var 
hEvent : WSAEvent; 
ret : Integer; 
ne : TWSANetworkEvents; 
sock : TSocket; 
adr : TSockAddrIn; 
sMsg : String; 
Index, 
EventTotal : DWORD; 
EventArray : Array [0..WSA_MAXIMUM_WAIT_EVENTS-1] of WSAEVENT; 
begin 
...socket...bind... 
hEvent := WSACreateEvent(); 
WSAEventSelect( ListenSock, hEvent, FD_ACCEPT or FD_CLOSE ); 
...listen... 
while ( not Terminated ) do 
begin 
Index := WSAWaitForMultipleEvents( EventTotal, @EventArray[0], FALSE, WSA_INFINITE, FALSE ); 
FillChar( ne, sizeof(ne), 0 ); 
WSAEnumNetworkEvents( SockArray[Index-WSA_WAIT_EVENT_0], EventArray[Index-WSA_WAIT_EVENT_0], @ne ); 
if ( ne.lNetworkEvents and FD_ACCEPT ) > 0 then 
begin 
if ne.iErrorCode[FD_ACCEPT_BIT]  0 then 
continue; 
ret := sizeof(adr); 
sock := accept( SockArray[Index-WSA_WAIT_EVENT_0], adr, ret ); 
if EventTotal > WSA_MAXIMUM_WAIT_EVENTS-1 then//這裏WSA_MAXIMUM_WAIT_EVENTS一樣是64 
begin 
closesocket( sock ); 
continue; 
end; 
hEvent := WSACreateEvent(); 
WSAEventSelect( sock, hEvent, FD_READ or FD_WRITE or FD_CLOSE ); 
SockArray[EventTotal] := sock; 
EventArray[EventTotal] := hEvent; 
Inc( EventTotal ); 
end; 
if ( ne.lNetworkEvents and FD_READ ) > 0 then 
begin 
if ne.iErrorCode[FD_READ_BIT]  0 then 
continue; 
FillChar( RecvBuf[0], PACK_SIZE_RECEIVE, 0 ); 
ret := recv( SockArray[Index-WSA_WAIT_EVENT_0], RecvBuf[0], PACK_SIZE_RECEIVE, 0 ); 
...... 
end; 
end; 
end; 
四:Overlapped I/O 事件通知模型 
後 來,微軟經過調查發現,老陳不喜歡上下樓收發信件,由於上下樓其實很浪費時間。因而微軟再次改進他們的信箱。新式的信箱採用了更爲先進的技術,只要用戶告 訴微軟本身的家在幾樓幾號,新式信箱會把信件直接傳送到用戶的家中,而後告訴用戶,你的信件已經放到你的家中了!老陳很高興,由於他沒必要再親自收發信件 了! 
Overlapped I/O 事件通知模型和WSAEventSelect模型在實現上很是類似,主要區別在"Overlapped",Overlapped模型是讓應用程序使用重疊數據結構(WSAOVERLAPPED),一次投遞一個或多個Winsock I/O請求。這些提交的請求完成後,應用程序會收到通知。什麼意思呢?就是說,若是你想從socket上接收數據,只須要告訴系統,由系統爲你接收數據,而你須要作的只是爲系統提供一個緩衝區~~~~~ 
Listen線程和WSAEventSelect模型如出一轍,Recv/Send線程則徹底不一樣: 
procedure TOverlapThread.Execute; 
var 
dwTemp : DWORD; 
ret : Integer; 
Index : DWORD; 
begin 
...... 
while ( not Terminated ) do 
begin 
Index := WSAWaitForMultipleEvents( FLinks.Count, @FLinks.Events[0], FALSE, RECV_TIME_OUT, FALSE ); 
Dec( Index, WSA_WAIT_EVENT_0 ); 
if Index > WSA_MAXIMUM_WAIT_EVENTS-1 then //超時或者其餘錯誤 
continue; 
WSAResetEvent( FLinks.Events[Index] ); 
WSAGetOverlappedResult( FLinks.Sockets[Index], FLinks.pOverlaps[Index], @dwTemp, FALSE, FLinks.pdwFlags[Index]^ ); 
if dwTemp = 0 then //鏈接已經關閉 
begin 
...... 
continue; 
end else 
begin 
fmMain.ListBox1.Items.Add( FLinks.pBufs[Index]^.buf ); 
end; 
//初始化緩衝區 
FLinks.pdwFlags[Index]^ := 0; 
FillChar( FLinks.pOverlaps[Index]^, sizeof(WSAOVERLAPPED), 0 ); 
FLinks.pOverlaps[Index]^.hEvent := FLinks.Events[Index]; 
FillChar( FLinks.pBufs[Index]^.buf^, BUFFER_SIZE, 0 ); 
//遞一個接收數據請求 
WSARecv( FLinks.Sockets[Index], FLinks.pBufs[Index], 1, FLinks.pdwRecvd[Index]^, FLinks.pdwFlags[Index]^, FLinks.pOverlaps[Index], nil ); 
end; 
end; 
五:Overlapped I/O 完成例程模型 
老陳接收到新的信件後,通常的程序是:打開信封----掏出信紙----閱讀信件----回覆信件......爲了進一步減輕用戶負擔,微軟又開發了一種新的技術:用戶只要告訴微軟對信件的操做步驟,微軟信箱將按照這些步驟去處理信件,再也不須要用戶親自拆信/閱讀/回覆了!老陳終於過上了小資生活! 
Overlapped I/O 完成例程要求用戶提供一個回調函數,發生新的網絡事件的時候系統將執行這個函數: 
procedure WorkerRoutine( const dwError, cbTransferred : DWORD; const 
lpOverlapped : LPWSAOVERLAPPED; const dwFlags : DWORD ); stdcall; 
而後告訴系統用WorkerRoutine函數處理接收到的數據: 
WSARecv( m_socket, @FBuf, 1, dwTemp, dwFlag, @m_overlap, WorkerRoutine ); 
而後......沒有什麼而後了,系統什麼都給你作了!微軟真實體貼! 
while ( not Terminated ) do//這就是一個Recv/Send線程要作的事情......什麼都不用作啊!!! 
begin 
if SleepEx( RECV_TIME_OUT, True ) = WAIT_IO_COMPLETION then // 
begin 

end else 
begin 
continue; 
end; 
end; 
六:IOCP模型 
微軟信箱彷佛很完美,老陳也很滿意。可是在一些大公司狀況卻徹底不一樣!這些大公司有數以萬計的信箱,每秒鐘都有數以百計的信件須要處理,以致於微軟信箱常常因超負荷運轉而崩潰!須要從新啓動!微軟不得不使出殺手鐗...... 
微軟給每一個大公司派了一名名叫"Completion Port"的超級機器人,讓這個機器人去處理那些信件! 
"Windows NT小組注意到這些應用程序的性能沒有預料的那麼高。特別的,處理不少同時的客戶請求意味着不少線程併發地運行在系統中。由於全部這些線程都是可運行的[沒有被掛起和等待發生什麼事],Microsoft意識到NT內核花費了太多的時間來轉換運行線程的上下文[Context],線程就沒有獲得不少CPU時間來作它們的工做。你們可能也都感受到並行模型的瓶頸在於它爲每個客戶請求都建立了一個新線程。建立線程比起建立進程開銷要小,但也遠不是沒有開銷的。咱們不妨設想一下:若是事先開好N個線程,讓它們在那hold[堵塞],而後能夠將全部用戶的請求都投遞到一個消息隊列中去。而後那N個線程逐一從消息隊列中去取出消息並加以處理。就能夠避免針對每個用戶請求都開線程。不只減小了線程的資源,也提升了線程的利用率。理論上很不錯,你想我等泛泛之輩都能想出來的問題,Microsoft又怎會沒有考慮到呢?"-----摘自nonocast的《理解I/O Completion Port》 
先看一下IOCP模型的實現: 
//建立一個完成端口 
FCompletPort := CreateIoCompletionPort( INVALID_HANDLE_VALUE, 0,0,0 ); 
//接受遠程鏈接,並把這個鏈接的socket句柄綁定到剛纔建立的IOCP上 
AConnect := accept( FListenSock, addr, len); 
CreateIoCompletionPort( AConnect, FCompletPort, nil, 0 ); 
//建立CPU數*2 + 2個線程 
for i:=1 to si.dwNumberOfProcessors*2+2 do 
begin 
AThread := TRecvSendThread.Create( false ); 
AThread.CompletPort := FCompletPort;//告訴這個線程,你要去這個IOCP去訪問數據 
end; 
OK,就這麼簡單,咱們要作的就是創建一個IOCP,把遠程鏈接的socket句柄綁定到剛纔建立的IOCP上,最後建立n個線程,並告訴這n個線程到這個IOCP上去訪問數據就能夠了。 
再看一下TRecvSendThread線程都幹些什麼: 
procedure TRecvSendThread.Execute; 
var 
...... 
begin 
while (not self.Terminated) do 
begin 
//查詢IOCP狀態(數據讀寫操做是否完成) 
GetQueuedCompletionStatus( CompletPort, BytesTransd, CompletKey, POVERLAPPED(pPerIoDat), TIME_OUT ); 
if BytesTransd  0 then 
....;//數據讀寫操做完成 
//再投遞一個讀數據請求 
WSARecv( CompletKey, @(pPerIoDat^.BufData), 1, BytesRecv, Flags, @(pPerIoDat^.Overlap), nil ); 
end; 
end; 
讀寫線程只是簡單地檢查IOCP是否完成了咱們投遞的讀寫操做,若是完成了則再投遞一個新的讀寫請求。 
應該注意到,咱們建立的全部TRecvSendThread都在訪問同一個IOCP(由於咱們只建立了一個IOCP),而且咱們沒有使用臨界區!難道不會產生衝突嗎?不用考慮同步問題嗎? 
呵呵,這正是IOCP的奧妙所在。IOCP不是一個普通的對象,不須要考慮線程安全問題。它會自動調配訪問它的線程:若是某個socket上有一個線程A正在訪問,那麼線程B的訪問請求會被分配到另一個socket。這一切都是由系統自動調配的,咱們無需過問。html

 

http://www.cnblogs.com/toosuo/archive/2009/08/16/1547155.html安全

相關文章
相關標籤/搜索