Windows支持的各种Socket I/O模型


    一:select模型
    二:WSAAsyncSelect模型
    三:WSAEventSelect模型
    四:Overlapped I/O 事件通知模型
    五:Overlapped I/O 完成例程模型
    六:IOCP模型

    老陈有一个在外地工作的女儿,不能经常回来,老陈和她通过信件联系。他们的信会被邮递员投递到他们的信箱里。
    这和Socket模型非常类似。下面我就以老陈接收信件为例讲解Socket I/O模型~~~

一:select模型


Code
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模型


Code
private
  procedure WMSocket(var Msg: TMessage); message WM_SOCKET;
{然后就可以使用WSAAsyncSelect了:}


Code
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产生了网络事件以及事件类型:

 

Code
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模型

同样要使用线程:

Code
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
  socketbind
  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 事件通知模型

Code
Code
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 完成例程模型

Code
procedure WorkerRoutine( const dwError, cbTransferred : DWORD; const
          lpOverlapped : LPWSAOVERLAPPED; const dwFlags : DWORD ); stdcall;
然后告诉系统用WorkerRoutine函数处理接收到的数据:
WSARecv( m_socket, @FBuf, 1, dwTemp, dwFlag, @m_overlap, WorkerRoutine );
然后......没有什么然后了,系统什么都给你做了!微软真实体贴!

Code
Code
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模型

Code
Code
{创建一个完成端口}
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线程都干些什么:

Code
Code
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。这一切都是由系统自动调配的,我们无需过问。

你可能感兴趣的:(Windows支持的各种Socket I/O模型)