服务器/客户端TCP异步(winsock)// FD_WRITE问题

时间:2012-08-30 20:56:59

标签: c++ sockets asynchronous tcp winsock

我需要你的帮助,因为我必须在C ++中创建两个控制台应用程序:一个能够向服务器发送尽可能多的字符串的客户端(为了发送坐标)。我成功地创建了一个阻塞套接字,但由于我必须在每个帧调用我的脚本的开发平台(3D VIA Virtools)之后将其集成,我没有其他解决方案而不是使用异步套接字。

*我的问题是我只能发送一次字符串,并且在我再也没有收到FD_WRITE之后...... *

这开始让我发疯,所以任何帮助都会受到高度赞赏(我是编程的初学者),感谢所有对我的问题有点担心的人

这是我的代码,

服务器

#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); 
            } 
         } 
      } 
   } 
} 

客户端

#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]); 

            } 

         } 
      } 
   } 
} 

下载.cpp文件:https://www.dropbox.com/s/pjuipz7v4iwr5ea/Clientserver%20TCP.zip

2 个答案:

答案 0 :(得分:2)

由于您没有考虑the documentation中的以下段落,因此您未在第一个之后收到FD_WRITE次通知:

  

FD_WRITE网络事件的处理方式略有不同。一个   首次连接套接字时会记录FD_WRITE网络事件   调用connect,ConnectEx,WSAConnect,WSAConnectByList,   或WSAConnectByName函数或当接受套接字时接受,   AcceptEx,或WSAAccept函数,然后发送失败后   WSAEWOULDBLOCK和缓冲区空间变得可用。因此,一个   应用程序可以假设从第一个开始可以发送   FD_WRITE网络事件设置并持续到发送返回   WSAEWOULDBLOCK。在这样的失败后,应用程序会发现   当FD_WRITE网络事件发生时,再次发送   记录并关联事件对象。

第一次调用send()后,套接字仍然可写,因为它的出站缓冲区未满。只要您还有要发送的数据,请继续调用send(),直到它报告WSAWOULDBLOCK错误,指示缓冲区已满。此时,您必须跟踪剩余数据,直到收到FD_WRITE通知,表明套接字再次可写,以便您可以继续从中断的位置发送剩余数据。

答案 1 :(得分:1)

我建议首先查看常规select()的非阻塞I / O.这里有几个链接供您开始使用: