Server/Client TCP asynchronous (winsock) // FD_WRI

2019-05-30 01:49发布

问题:

I need your help because I have to make two console application in C++ : a client able to send as many string as possible to a server (in order to send coordinates). I succeeded to make a blocking socket but as I have to integrate it after in a development platform (3D VIA Virtools) which call my script at each frame, I have no other solution than using asynchronous sockets.

*My problem is that I can only send string once, and after I don't receive FD_WRITE anymore...*

This begin to drive me crazy so any help will be highly appreciated (I'm a beginner in programming), thanks in advance to everyone who will feel a little bit concerned by my problem

Here is my code,

Server

#include <winsock2.h> 
#include <Windows.h> 
#include <conio.h> 

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

#define   SOCKET_ERRNO   WSAGetLastError() 
#define ADDRESS "127.0.0.1" 
#define PORT 1234 

static SOCKET ListenFirstFreePort() 
{ 
   struct sockaddr_in addr; 
   int len = sizeof(addr);    
   SOCKET hSocket; 

   // Create socket 
   hSocket = socket( PF_INET, SOCK_STREAM, 0 ); 
   if( hSocket == INVALID_SOCKET ) 
   { 
      printf( "socket() error %d\n", SOCKET_ERRNO ); 
      exit(1); 
   } 

   // Connexion setting for local connexion 
   addr.sin_family = AF_INET ; 
   addr.sin_addr.s_addr = inet_addr(ADDRESS); 
   addr.sin_port = htons (PORT); 

   // bind socket 
   if ( bind( hSocket, (struct sockaddr *)&addr, sizeof(addr)) == SOCKET_ERROR ) 
   { 
      printf( "bind() error %d\n", SOCKET_ERRNO ); 
      exit(1); 
   } 

   // listen 
   if ( listen( hSocket, 100) == SOCKET_ERROR ) 
   { 
      printf( "listen() error %d\n", SOCKET_ERRNO ); 
      exit(1); 
   } 

   return hSocket; 
} 



void main() 
{ 
   WSADATA stack_info; 
   SOCKET ahSocket[2]; 
   WSAEVENT ahEvents[2]; 
   DWORD dwEvent; 
   WSANETWORKEVENTS NetworkEvents; 
   int rc; 

   // Initialize Winsock 
   WSAStartup(MAKEWORD(2,0), &stack_info) ; 

   // Create events 
   ahEvents[0] = WSACreateEvent(); 
   ahEvents[1] = WSACreateEvent(); 


   // Create listening socket 
   ahSocket[0] = ListenFirstFreePort(); 
   rc = WSAEventSelect(ahSocket[0], ahEvents[0], FD_ACCEPT ); 
   if( rc == SOCKET_ERROR ) 
   { 
      printf( "WSAEventSelect() error %d\n", SOCKET_ERRNO ); 
      exit(1); 
   } 

   while (TRUE) 
   { 
      // Waiting for so;ething to happen 
      // Basically we'll firstly receive the connexion of the client socket 
      // and then we'll be notificated when there will be some data to read 

      // look for events 
      dwEvent = WSAWaitForMultipleEvents( 2, ahEvents, FALSE, WSA_INFINITE, FALSE); 

      switch (dwEvent) 
      { 
      case WSA_WAIT_FAILED: 
         printf("WSAEventSelect: %d\n", WSAGetLastError()); 
         break; 
      case WAIT_IO_COMPLETION: 
      case WSA_WAIT_TIMEOUT: 
         break; 

      default: 

         //if there is one dwEvent-WSA_WAIT_EVENT_0 has to be substracted so as to dwEvent correspond to the index of the concerned socket 
         dwEvent -= WSA_WAIT_EVENT_0; 

         // enumeration of the events on the socket[dwEvent] 
         if (SOCKET_ERROR == WSAEnumNetworkEvents(ahSocket[dwEvent], ahEvents[dwEvent], &NetworkEvents)) 
         { 
            printf("WSAEnumNetworkEvent: %d lNetworkEvent %X\n",  
               WSAGetLastError(), NetworkEvents.lNetworkEvents); 
            NetworkEvents.lNetworkEvents = 0; 
         } 
         else  
         { 

            if (FD_CLOSE   & NetworkEvents.lNetworkEvents) 
            { 

               printf( "FD_CLOSE ok (dwEvent=%d)\n", dwEvent ); 
               printf( "press a key to exit\n" ); 
               getch(); // require conio.h 

               WSACloseEvent( ahEvents[0] ); 
               WSACloseEvent( ahEvents[1] ); 
               exit(0); 
            } 
            if (FD_READ & NetworkEvents.lNetworkEvents) 
            { 
               char szBuffer[256]; int cbRecv; 

               // Only the second socket expect to receive data 
               printf( "FD_READ ok (dwEvent=%d)\n", dwEvent ); 

               // read data 
               cbRecv = recv( ahSocket[dwEvent], szBuffer, sizeof(szBuffer) - 1, 0 ); 
               if( cbRecv <= 0 ) 
               { 
                  printf( "recv() error %d\n", SOCKET_ERRNO ); 
                  exit(1); 
               } 

               // On ecrit ce paquet (On remet le 0 au cas ou le paquet 
               // ait ete coupe en 2 - je sais, ca n'arrivera jamais en local) 
               // we put the 0 in case it has been cut - unlikey to happen on local network 
               szBuffer[cbRecv] = 0; 

               // write data in console window 
               printf( "socket %d : '%s'\n", dwEvent, szBuffer ); 
            } 
         } 
         if (FD_ACCEPT & NetworkEvents.lNetworkEvents) 
         { 
            struct sockaddr_in addrAccept; 
            int lenAccept; 
            lenAccept = sizeof( addrAccept ); 

            // we should have dwEvent=0 
            printf( "accept ok (dwEvent=%d)\n", dwEvent ); 

            // we create another socket to accept the connexion with the client socket 
            ahSocket[1] = accept(ahSocket[dwEvent], (struct sockaddr *)&addrAccept, &lenAccept); 

            // we want to be informed on when we'll be able read data from it 
            rc = WSAEventSelect(ahSocket[1], ahEvents[1], FD_READ|FD_CLOSE  ); 
            if( rc == SOCKET_ERROR ) 
            { 
               printf( "WSAEventSelect() error %d\n", SOCKET_ERRNO ); 
               exit(1); 
            } 
         } 
      } 
   } 
} 

Client

#include <winsock2.h> 
#include <conio.h> 
#include <time.h> 

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

#define   SOCKET_ERRNO   WSAGetLastError() 
#define ADDRESS "127.0.0.1" 
#define PORT 1234 

SOCKET ConnectToPort() 
{ 
   struct sockaddr_in addr; 
   SOCKET hSocket; 
   u_long arg; int err; 

   // Create socket 
   hSocket = socket( PF_INET, SOCK_STREAM, 0 ); 
   if( hSocket == INVALID_SOCKET ) 
   { 
      printf( "socket() error %d\n", SOCKET_ERRNO ); 
      exit(1); 
   } 

   // Connexion setting for local connexion 
   addr.sin_family = AF_INET ; 
   addr.sin_addr.s_addr = inet_addr(ADDRESS); 
   addr.sin_port = htons (PORT); 

   // Connect 
   if( connect( hSocket, (struct sockaddr *)&addr, sizeof(addr) ) == SOCKET_ERROR ) 
   { 
      // As we are in non-blocking mode we'll always have the error 
      // WSAEWOULDBLOCK whichis actually not one 
      if( SOCKET_ERRNO != WSAEWOULDBLOCK ) 
      { 
         printf( "connect() error (%d)\n", SOCKET_ERRNO ); 
         exit(1); 
      } 
   } 

   return hSocket; 
} 



void main() 
{ 
   int initClockTime; 
   WSADATA stack_info; 
   SOCKET ahSocket[1]; 
   WSAEVENT ahEvents[1]; 
   DWORD dwEvent; 
   WSANETWORKEVENTS NetworkEvents; 
   int rc; 

   // Initialize Winsock 
   WSAStartup(MAKEWORD(2,0), &stack_info) ; 

   // Create event 
   ahEvents[0] = WSACreateEvent(); 

   // Create and connect a socket on the server socket 
   ahSocket[0]= ConnectToPort(); 

   // not sure if I have to use or not 
   /*u_long arg = 1; 
   ioctlsocket( ahSocket[0] , FIONBIO, &arg );*/ 

   // the application wants to receive notification of a completed connection 
   rc = WSAEventSelect(ahSocket[0], ahEvents[0], FD_CONNECT  ); 
   if( rc == SOCKET_ERROR ) 
   { 
      printf( "WSAEventSelect() error %d\n", SOCKET_ERRNO ); 
      exit(1); 
   } 

   while (TRUE) 
   { 
      // look for events 
      dwEvent = WSAWaitForMultipleEvents( 1, ahEvents, FALSE, 1000, FALSE); 

      switch (dwEvent) 
      { 
      case WSA_WAIT_FAILED: 
         printf("WSAEventSelect: %d\n", WSAGetLastError()); 
         break; 
      case WAIT_IO_COMPLETION: 
      case WSA_WAIT_TIMEOUT: 
         break; 

      default: 

         printf("while\n"); 

         //if there is one dwEvent-WSA_WAIT_EVENT_0 has to be substracted so as to dwEvent correspond to the index of the concerned socket 
         dwEvent -= WSA_WAIT_EVENT_0; 

         // enumeration of the events on the socket[dwEvent] 
         if (SOCKET_ERROR == WSAEnumNetworkEvents(ahSocket[dwEvent], ahEvents[dwEvent], &NetworkEvents)) 
         { 
            printf("WSAEnumNetworkEvent: %d lNetworkEvent %X\n", WSAGetLastError(), NetworkEvents.lNetworkEvents); 
            NetworkEvents.lNetworkEvents = 0; 
         } 
         else  
         { 


            if (FD_CONNECT & NetworkEvents.lNetworkEvents) 
            { 
               //connexion is OK 
               printf( "FD_CONNECT ok (dwEvent=%d)\n", dwEvent ); 

               // now that we are connected we want to send data or be aware when the other socket is disconnected 
               rc = WSAEventSelect(ahSocket[dwEvent], ahEvents[dwEvent], FD_CLOSE | FD_WRITE ); 
               if( rc == SOCKET_ERROR ) 
               { 
                  printf( "WSAEventSelect() error %d\n", SOCKET_ERRNO ); 
                  exit(1); 
               } 
            } 
            if (FD_CLOSE   & NetworkEvents.lNetworkEvents) 
            { 
               printf( "FD_CLOSE ok (dwEvent=%d)\n", dwEvent ); 
               printf( "press a key to exit\n" ); 
               getch(); 

               WSACloseEvent( ahEvents[0] ); 
               exit(0); 
            } 

            if (FD_WRITE & NetworkEvents.lNetworkEvents) 
            { 
               char szBuffer[256]; int cbBuffer; 

               printf( "FD_WRITE ok (dwEvent=%d)\n", dwEvent ); 

               // create string and return the size 
               cbBuffer = sprintf( szBuffer, "Coucou", dwEvent ); 


               // send the string with 0 at the end 
               rc = send( ahSocket[dwEvent], szBuffer, cbBuffer + 1, 0 ); 
               if (SOCKET_ERROR ==  rc) 
               { 
                  printf("WSAEnumNetworkEvent: %d lNetworkEvent %X\n",  WSAGetLastError(), NetworkEvents.lNetworkEvents); 
               } 

               // not sure if I have to use it 
               //WSAResetEvent(ahEvents[0]); 

            } 

         } 
      } 
   } 
} 

download .cpp files : https://www.dropbox.com/s/pjuipz7v4iwr5ea/Clientserver%20TCP.zip

回答1:

You are not getting FD_WRITE notifications after the first one because you are not taking into account the following paragraph from the documentation:

The FD_WRITE network event is handled slightly differently. An FD_WRITE network event is recorded when a socket is first connected with a call to the connect, ConnectEx, WSAConnect, WSAConnectByList, or WSAConnectByName function or when a socket is accepted with accept, AcceptEx, or WSAAccept function and then after a send fails with WSAEWOULDBLOCK and buffer space becomes available. Therefore, an application can assume that sends are possible starting from the first FD_WRITE network event setting and lasting until a send returns WSAEWOULDBLOCK. After such a failure the application will find out that sends are again possible when an FD_WRITE network event is recorded and the associated event object is set.

After your first call to send(), the socket is still writable since its outbound buffer is not full. As long as you still have data to send, keep calling send() until it reports an WSAWOULDBLOCK error indicating the buffer is full. At that point, you have to keep track of your remaining data until you get an FD_WRITE notification indicating the socket is writable again so you can continue sending your remaining data from where you left off.



回答2:

I would recommend to look at regular select() for non-blocking I/O first. Here's couple of links for you to get started:

  • Non-blocking I/O and select() (the link is broken)
  • Non-Blocking Sockets in TCP/IP