天天看点

Socket I/O 模型 学习

原文地址 

我们为什么要使用Socket I/O模型呢?还得从Socket的阻塞和非阻塞说起。

在网上看过一篇讲解I/O模型的文章,它举过一个例子觉得挺好,那就是收信的例子。

比如:老周在等待他女儿从美国寄过来的信件,老周住三楼,信箱在一楼。有以下几种情况:

第一:老周一直守在信箱旁边,直到收到信件为止。这样太费精力。这就好比是阻塞套接字。

第二:老周到信箱那里看一下,发现还没有来,就马上回家了。这就好比非阻塞套接字。

第三:老周先打个电话到一楼管理员问一下自己的信件是否到了,如果到了才下楼去取信件。当然这样浪费电话费,但是值得的。这可以比作Socket的Select I/O模型。

    1  #include  " stdafx.h "

  2  #include  < iostream >

  3  #include  < winsock2.h >

  4  #include  < windows.h >

  5  

  6   #define  TRACE ATLTrace  // 必须要加上这个宏定义,否则在WIN32的控制台程序中是不能直接用的

  7  

  8   #define  InternetAddr "127.0.0.1"

  9   #define  iPort 5055

10  

11   #pragma  comment(lib, "ws2_32.lib")

12  

13   int  _tmain( int  argc, _TCHAR *  argv[])

14  {

15      WSADATA wsa;

16      WORD wVersionRequested;

17       int  err;

18  

19     wVersionRequested  =  MAKEWORD(  2 ,  2  );

20      err  =  WSAStartup( wVersionRequested,  & wsa);

21       if  ( err  !=   0  ) {

22       // Tell the user that we could not find a usable 

23       // WinSock DLL.     

24      TRACE( " 你忘记添加WinSock DLL了/n " );

25      WSACleanup();

26       return   1 ;

27       }

28  

29      //  Create a SOCKET for listening for  incoming connection requests

30      SOCKET fdServer  =  socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

31     

32      sockaddr_in server;

33  

34   server.sin_family  =  AF_INET;

35   server.sin_addr.s_addr  =  inet_addr(InternetAddr);

36   server.sin_port  =  htons(iPort);

37    // Bind the socket.

38       int  ret  =  bind(fdServer, (sockaddr * ) & server,  sizeof (server));

39      ret  =  listen(fdServer,  4 );

40  

41      SOCKET AcceptSocket;

42      fd_set     fdread;

43   timeval    tv;

44    int  nSize;

45      // 其实也算是轮训,那么对阻塞socket用select和对使用非阻塞socket的优点在哪?

46     // 可能的优点就是避免在非阻塞套接字里重复检查WSAEWOULDBLOCK错误。

47       while ( 1 )

48    {

49                   

50           FD_ZERO( & fdread); // 初始化fd_set

51           FD_SET(fdServer,  & fdread); // 分配套接字句柄到相应的fd_set

52                               

53          tv.tv_sec  =   2 ; // 这里我们打算让select等待两秒后返回,避免被锁死,也避免马上返回

54          tv.tv_usec  =   0 ;

55                                                   

56          select( 0 ,  & fdread, NULL, NULL,  & tv);

57                                                           

58          nSize  =   sizeof (server);

59           // 先判断fdServer是否还在fd_set内来判断是否可以读,这样就避免因为 accept在等待

60           // 时造成的阻塞

61           if  (FD_ISSET(fdServer,  & fdread))

62               // 如果套接字句柄还在fd_set里,说明客户端已经有connect的请求发过来了,

63               // 马上可以accept成功

64           {

65               AcceptSocket  =  accept(fdServer,( sockaddr * )  & server,  & nSize);

66                break ;

67             }                                             

68           else

69           // 还没有客户端的connect请求,我们可以去做别的事,避免像没有用select方式

70           // 的阻塞套接字程序被锁死的情况,如果没用select,当程序运行到accept的时候客户

71           // 端恰好没有connect请求,那么程序就会被锁死,做不了任何事情

72              {

73               // do something

74                 MessageBox(NULL,  " waiting " ,  " recv " , MB_ICONINFORMATION);

75           // 别的事做完后,继续去检查是否有客户端连接请求

76              }

77     }

78  

79      char  buffer[ 128 ];

80        ZeroMemory(buffer,  128 );

81  

82           ret  =  recv(AcceptSocket,buffer, 128 , 0 ); // 这里同样可以用select,用法和上面一样

83  

84           MessageBox(NULL, buffer,  " recv " , MB_ICONINFORMATION);

85  

86          closesocket(AcceptSocket);

87          WSACleanup();

88           return   0 ;

89  }

第四:老周告诉一楼管理员,如果有他的信件就通知老周。这可以比做Socket的WSAAsynSelect模型 

     1  #include  < winsock.h >

   2  #include  < tchar.h >

   3  

   4   #define  PORT         5150

   5   #define  MSGSIZE      1024

   6   #define  WM_SOCKET WM_USER+0

   7  

   8   # pragma  comment(lib, "ws2_32.lib")

   9  

  10  LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

  11  

  12   int  WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine,  int  iCmdShow)

  13  {

  14        static  TCHAR szAppName[]  =  _T( " AsyncSelect Model " );

  15       HWND            hwnd ;

  16       MSG             msg ;

  17       WNDCLASS        wndclass ;

  18  

  19       wndclass.style             =  CS_HREDRAW  |  CS_VREDRAW ;

  20       wndclass.lpfnWndProc       =  WndProc ;

  21       wndclass.cbClsExtra        =   0  ;

  22       wndclass.cbWndExtra        =   0  ;

  23       wndclass.hInstance         =  hInstance ;

  24       wndclass.hIcon             =  LoadIcon (NULL, IDI_APPLICATION) ;

  25       wndclass.hCursor           =  LoadCursor (NULL, IDC_ARROW) ;

  26       wndclass.hbrBackground  =  (HBRUSH) GetStockObject (WHITE_BRUSH) ;

  27       wndclass.lpszMenuName      =  NULL ;

  28       wndclass.lpszClassName  =  szAppName ;

  29  

  30        if  ( ! RegisterClass( & wndclass))

  31       {

  32         MessageBox (NULL, TEXT ( " This program requires Windows NT! " ), szAppName, MB_ICONERROR) ;

  33          return   0  ;

  34       }

  35  

  36       hwnd  =  CreateWindow (szAppName,                      //  window class name

  37                            TEXT ( " AsyncSelect Model " ),  //  window caption

  38                            WS_OVERLAPPEDWINDOW,            //  window style

  39                            CW_USEDEFAULT,                  //  initial x position

  40                            CW_USEDEFAULT,                  //  initial y position

  41                            CW_USEDEFAULT,                  //  initial x size

  42                            CW_USEDEFAULT,                  //  initial y size

  43                            NULL,                           //  parent window handle

  44                            NULL,                           //  window menu handle

  45                            hInstance,                      //  program instance handle

  46                            NULL) ;                         //  creation parameters

  47  

  48       ShowWindow(hwnd, iCmdShow);

  49       UpdateWindow(hwnd);

  50  

  51        while  (GetMessage( & msg, NULL,  0 ,  0 ))

  52       {

  53         TranslateMessage( & msg) ;

  54         DispatchMessage( & msg) ;

  55       }

  56    

  57        return  msg.wParam;

  58  }

  59  

  60  LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)

  61  {

  62       WSADATA          wsd;

  63        static  SOCKET sListen;

  64       SOCKET           sClient;

  65       SOCKADDR_IN      local, client;

  66        int               ret, iAddrSize  =   sizeof (client);

  67        char              szMessage[MSGSIZE];

  68  

  69        switch  (message)

  70       {

  71   case  WM_CREATE:

  72          //  Initialize Windows Socket library

  73       WSAStartup( 0x0202 ,  & wsd);

  74    

  75        //  Create listening socket

  76         sListen  =  socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

  77      

  78        //  Bind

  79         local.sin_addr.S_un.S_addr  =  htonl(INADDR_ANY);

  80       local.sin_family  =  AF_INET;

  81       local.sin_port  =  htons(PORT);

  82       bind(sListen, ( struct  sockaddr  * ) & local,  sizeof (local));

  83    

  84        //  Listen

  85         listen(sListen,  3 );

  86  

  87          //  Associate listening socket with FD_ACCEPT event

  88       WSAAsyncSelect(sListen, hwnd, WM_SOCKET, FD_ACCEPT);

  89        return   0 ;

  90  

  91        case  WM_DESTROY:

  92         closesocket(sListen);

  93         WSACleanup();

  94         PostQuitMessage( 0 );

  95          return   0 ;

  96    

  97        case  WM_SOCKET:

  98          if  (WSAGETSELECTERROR(lParam)) // lParam的高字节包含了可能出现的任何的错误代码

  99         {

100           closesocket(wParam);

101            break ;

102         }

103      

104          switch  (WSAGETSELECTEVENT(lParam))  // lParam的低字节指定已经发生的网络事件

105         {

106          case  FD_ACCEPT:

107            //  Accept a connection from client

108           sClient  =  accept(wParam, ( struct  sockaddr  * ) & client,  & iAddrSize);

109        

110            //  Associate client socket with FD_READ and FD_CLOSE event

111           WSAAsyncSelect(sClient, hwnd, WM_SOCKET, FD_READ  |  FD_CLOSE);

112            break ;

113  

114          case  FD_READ:

115           ret  =  recv(wParam, szMessage, MSGSIZE,  0 );

116  

117            if  (ret  ==   0   ||  ret  ==  SOCKET_ERROR  &&  WSAGetLastError()  ==  WSAECONNRESET)

118           {

119             closesocket(wParam);

120           }

121            else

122            {

123             szMessage[ret]  =   ' /0 ' ;

124             send(wParam, szMessage, strlen(szMessage),  0 );

125           }

126            break ;

127        

128          case  FD_CLOSE:

129            closesocket(wParam);      

130            break ;

131         }

132          return   0 ;

133       }

134    

135        return  DefWindowProc(hwnd, message, wParam, lParam);

136  }

 第五:老周要一楼管理员发个短信到他们家里去。相当如WSAEventSelect模型,其实WSAEventSelect模型类似WSAAsynSelect模型,但最主要的区别是网络事件发生时会被发送到一个事件对象句柄,而不是发送到一个窗口。这样可能更加的好,对于服务器端的程序来说。

   1  SOCKET       Socket[WSA_MAXIMUM_WAIT_EVENTS];

  2  WSAEVENT   Event[WSA_MAXINUM_WAIT_EVENTS];

  3  SOCKET    Accept, Listen;

  5  DWORD     EventTotal  =   0 ;

  6  DWORD     Index;

  7  

  8   // Set up a TCP socket for listening on port 5150

  9  Listen  =  socket(PF_INET,SOCK_STREAM, 0 );

10  

11  InternetAddr.sin_family       =  AF_INET;

12  InternetAddr.sin_addr.s_addr  =  htonl(INADDR_ANY);

13  InternetAddr.sin_port         =  htons( 5150 );

14  

15  bind(Listen,(PSOCKADDR)  & InternetAddr, sizeof (InternetAddr));

16  

17  NewEvent  =  WSACreateEvent();

18  

19  WSAEventSelect(Listen,NewEvnet,FD_ACCEPT | FD_CLOSE);

20  

21  listen(Listen, 5 );

22  

23  Socket[EventTotal]  =  Listen;

24  Event[EventTotal]  =  NewEvent;

25  EventTotal ++ ;

26  

27   while  (TRUE)

28  {

29       // Wait for network events on all sockets

30      Index  =  WSAWaitForMultipleEvents(EventTotal,EventArray,FALSE,WSA_INFINITE,FALSE);

31  

32      WSAEnumNewWorkEvents(SocketArray[Index - WSA_WAIT_EVENT_0],

33          EventArray[Index - WSA_WAIT_EVENT_0],

34           & NetworkEvents);

35       // Check for FD_ACCEPT messages

36       if  (NetworkEvents.lNetworkEvents  &  FD_ACCEPT)

37      {

38           if  (NetworkEvents.iErrorCode[FD_ACCEPT_BIT]  != 0 )

39          {

40               // Error

41               break ;

42          }

43           // Accept a new connection and add it to the socket and event lists

44          Accept  =  accept(SocketArray[Index - WSA_WAIT_EVENT_0],NULL,NULL);

45  

46           // We cannot process more than WSA_MAXIMUM_WAIT_EVENTS sockets ,

47           // so close the accepted socket

48           if  (EventTotal  >  WSA_MAXIMUM_WAIT_EVENTS)

49          {

50              printf( " .. " );

51              closesocket (Accept);

52               break ;

53          }

54          NewEvent  =  WSACreateEvent();

55  

56          WSAEventSelect(Accept,NewEvent,FD_READ | FD_WRITE | FD_CLOSE);

57  

58          Event[EventTotal]  =  NewEvent;

59          Socket[EventTotal] =  Accept;

60          EventTotal ++ ;

61          prinrt( " Socket %d connect/n " ,Accept);

62      }

63       // Process FD_READ notification

64       if  (NetworkEvents.lNetwoAD)rkEvents  &  FD_RE

65      {

66           if  (NetworkEvents.iErrorCode[FD_READ_BIT  != 0 ])

67          {

68               // Error

69               break ;

70          }

71  

72           // Read data from the socket

73          recv(Socket[Index - WSA_WAIT_EVENT_0],buffer, sizeof (buffer), 0 );

74      }

75       // process FD_WRITE notitication

76       if  (NetworkEvents.lNetworkEvents  &  FD_WRITE)

77      {

78           if  (NetworkEvents.iErrorCode[FD_WRITE_BIT]  != 0 )

79          {

80               // Error

81               break ;

82          }

83          send(Socket[Index - WSA_WAIT_EVENT_0],buffer, sizeof (buffer), 0 );

84      }

85       if  (NetworkEvents.lNetworkEvents  &  FD_CLOSE)

86      {

87           if (NetworkEvents.iErrorCode[FD_CLOSE_BIT]  != 0 )

88          {

89               // Error

90               break ;

91          }

92          closesocket (Socket[Index - WSA_WAIT_EVENT_0]);

93           // Remove socket and associated event from the Socket and Event arrays and

94           // decrement eventTotal

95          CompressArrays(Event,Socket, &  EventTotal);

96      }

97  }

第六:老周可以要求一楼管理员把信件送到他们家去,好比Overlapped I/O 事件通知模型。

第七:老周还可以要求不仅送信件,还可以要求管理员帮他把信封打开,读给老周听(假设老周为文盲,管理员也够累的),这就是Overlapped I/O 完成例程模型 了。

继续阅读