【FD_WRITE 事件】
(1)呼叫 WSAAsyncSelect() 来设定 FD_WRITE 事件时,Socket 已经可以传送资料(TCP scoket 已经和对方连接成功了,或 UDP socket 已建立完成),且目前 output buffer 仍有空间可写入资料。
(2)呼叫 WSAAsyncSelect() 来设定 FD_WRITE 事件时,Socket 尚不能传送资料,不过一旦 Socket 与对方连接成功,马上就会收到 FD_WRITE 的通知。
(3)呼叫 send() 或 sendto() 传送资料时,系统告知错误,且错误码为10035 WSAEWOULDBLOCK (呼叫 WSAGetLastError() 得知这项错误),这时表示 output buffer 已经满了,无法再写入任何资料(此时即令呼叫再多次的 send() 也都一定失败);一旦系统将部份资料成功送抵对方,空出 output buffer 后,便会送一个 FD_WRITE 给使用者,告知可继续传送资料了。换句话说,读者在呼叫 send() 传送资料时,只要不是返回错误 10035 的话,便可一直继续呼叫 send() 来传送资料;一旦 send() 回返错误 10035,那麽便不要再呼叫 send() 传送资料,而须等收到 FD_WRITE 后,再继续传送资料。
如果只是发送很少的数据,可能只出现第二种情况,所以在发送少量数据的时候,不要使用FD_WRITE机制。
【FD_READ 事件】
(1)呼叫 WSAAsyncSelect 函式来对此 Socket 设定 FD_READ 事件时,input buffer 中已有资料。
(2)原先系统的 input buffer 是空的,当系统再收到资料时,会通知我们。
(3)使用者呼叫 recv 或 recvfrom 函式,从 input buffer 读取资料,但是并没有一次将资料读光,此时会再驱动一个 FD_READ 事件,表示仍有资料在 input buffer 中。
读者必须注意:如果我们收到 FD_READ 事件通知的讯息,但是我们故意不呼叫 recv 或 recvfrom 来读取资料的话,尔後系统又收到资料时,并不会再次通知我们,一定要等我们呼叫了 recv 或 recvfrom 後,才有可能再收到FD_READ 的事件通知。
本文简单介绍了当前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 <= 64,也就是说select只能同时管理最多64个连接 begin len := sizeof(addr); ASock := accept( MainSock, addr, len ); if ASock <> 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;