Connect mit Timeout, Beispiel für Win32

Dieses Programm zeigt, wie man unter Windows einen Verbindungsaufruf nach einer definierten Wartezeit abbrechen kann, indem select() eingesetzt wird. Der Socket wird dabei zeitweise nicht-blockierend gemacht. Prinzipiell kann dies auch unter UNIX gemacht werden.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <winsock.h>

#define BUFFER_SIZE 1024

#define USE_SELECT      1
#define CONNECT_TIMEOUT 5

int main(int argc, char *argv[])
{
   WSADATA wsa;
   SOCKET sock;
   const char *hostname;
   const char *portnumber;
   struct hostent *host;
   struct sockaddr_in addr;
   char request[BUFFER_SIZE];
   char response[BUFFER_SIZE];
   time_t t_start;
   int ret;

#if (defined USE_SELECT) && (USE_SELECT == 1)
   /* Variablen die nur für die select-Variante benötigt werden */
   unsigned long opt;
   fd_set fds;
   struct timeval timeout;
#endif

   if (argc != 3)
   {
      fprintf(stderr, "try %s <hostname> <port>\n", argv[0]);
      return EXIT_FAILURE;
   }
   hostname = argv[1];
   portnumber = argv[2];

   /* Winsock-System starten */
   if (WSAStartup(MAKEWORD(1, 1), &wsa) != 0)
   {
      fprintf(stderr, "Starting winsock failed\n");
      return EXIT_FAILURE;
   }

   /* Hostnamen in IP-Adresse auflösen */
   host = gethostbyname(hostname);
   if (host == NULL)
   {
      fprintf(stderr, "Can't lookup %s: %lu\n",
         hostname, WSAGetLastError());
      return EXIT_FAILURE;
   }
   addr.sin_addr = *((struct in_addr*) (host->h_addr_list[0]));

   /* Socket anlegen */
   sock = socket(PF_INET, SOCK_STREAM, 0);
   if (sock == INVALID_SOCKET)
   {
      fprintf(stderr, "Can't create socket: %lu\n", WSAGetLastError());
      return EXIT_FAILURE;
   }

   /* Verbindungsdaten zusammenstellen */
   addr.sin_family = AF_INET;
   addr.sin_port = htons((unsigned short)atoi(portnumber));
   t_start = time(NULL);

#if (defined USE_SELECT) && (USE_SELECT == 1)
   /* Die Variante mit select erfordert, daß der Socket nicht-blockierend
    * gemacht wird. Dies kann (und wird in diesem Beispiel) nach dem Verbinden
    * wieder rückgängig gemacht, sodaß man wie gewohnt mit dem Socket arbeiten
    * kann.
    */
   opt = 1;
   ioctlsocket(sock, FIONBIO, &opt);

   /* Den Verbindungsaufbau anstossen */
   if (connect(sock, (struct sockaddr*) &addr, sizeof(addr)) == SOCKET_ERROR)
   {
      /* Das schlägt normalerweise fehl, wobei der Fehler WSAEWOULDBLOCK
       * darauf hinweist, daß der Verbindungsaufbau durchaus noch Erfolgreich
       * sein kann, und der Aufruf nur fehlgeschlagen ist, weil er andernfalls
       * blockieren würde, was ja absichtlich deaktiviert wurde.
       */
      if (WSAGetLastError() != WSAEWOULDBLOCK)
      {
         printf("*** connect took %u seconds ***\n", time(NULL) - t_start);
         fprintf(stderr, "Can't connect to %s:%u: %lu\n",
            inet_ntoa(addr.sin_addr), ntohs(addr.sin_port), WSAGetLastError());
         return EXIT_FAILURE;
      }
   }

   /* Deskriptor-Set zurücksetzen und mit dem zu verbindenden Socket belegen */
   FD_ZERO(&fds);
   FD_SET(sock, &fds);

   /* Den gewählte timeout-Wert einsetzen */
   timeout.tv_sec = CONNECT_TIMEOUT;
   timeout.tv_usec = 0;

   /* Nun select aufrufen; dieses kehrt entweder nach Ablauf des Timeouts
    * zurück, oder wenn der Socket zum Schreiben bereit ist, was genau dann
    * passiert, wenn er erfolgreich verbunden wurde.
    */
   ret = select(sock + 1, NULL, &fds, NULL, &timeout);
   if (ret == SOCKET_ERROR)
   {
      fprintf(stderr, "Call to select failed: %lu\n", WSAGetLastError());
      return EXIT_FAILURE;
   }

   /* Falls select zurückgekehrt ist, aber der zu verbindende Socket nicht
    * im Deskriptor-Set vorhanden ist, war das Verbinden in der gegebenen
    * Zeit nicht erfolgreich.
    */
   if (FD_ISSET(sock, &fds) == 0)
   {
      printf("*** connect took %u seconds ***\n", time(NULL) - t_start);
      fprintf(stderr, "Can't connect to %s:%u, timed out\n",
         inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
      return EXIT_FAILURE;
   }

   /* Der Socket kann nun wieder blockierend gemacht werden */
   opt = 0;
   ioctlsocket(sock, FIONBIO, &opt);
#else
   /* Die Lösung ohne select ruft connect ganz normal auf */
   if (connect(sock, (struct sockaddr*) &addr, sizeof(addr)) == SOCKET_ERROR)
   {
      printf("*** connect took %u seconds ***\n", time(NULL) - t_start);
      fprintf(stderr, "Can't connect to %s:%u: %lu\n",
         inet_ntoa(addr.sin_addr), ntohs(addr.sin_port), WSAGetLastError());
      return EXIT_FAILURE;
   }
#endif

   printf("connected to %s:%u!\n",
      inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));

   /* Als Test wird ein kleines HTTP-Request abgesetzt, und dessen Antwort
    * auf die Konsole ausgegeben.
    */
   sprintf(request, "HEAD / HTTP/1.1\r\n"
                    "Host: %s\r\n"
                    "Connection: Close\r\n"
                    "\r\n", hostname);

   ret = send(sock, request, strlen(request), 0);
   if (ret == SOCKET_ERROR)
   {
      fprintf(stderr, "Sending request failed: %lu\n", WSAGetLastError());
      return EXIT_FAILURE;
   }
   printf("request sent... (%i octets)\n");

   ret = recv(sock, response, sizeof(response), 0);
   if (ret == SOCKET_ERROR)
   {
      fprintf(stderr, "Receiving response failed: %lu\n", WSAGetLastError());
      return EXIT_FAILURE;
   }
   printf("response read, %i octets:\n\n", ret);

   puts("==================================================");
   fwrite(response, 1, ret, stdout);
   puts("==================================================");

   closesocket(sock);

   WSACleanup();

   return EXIT_SUCCESS;
}