Определить, разорвалось ли соединение с удалённой стороной

Тема в разделе "LANGS.C", создана пользователем AlannY, 9 янв 2009.

  1. AlannY

    AlannY New Member

    Публикаций:
    0
    Регистрация:
    24 окт 2008
    Сообщения:
    41
    Есть прога, фрагмент котороый представлен ниже. Нужно в указанном месте определить, разорвалось ли подключение с сервером. В общем, читайте комментарии.

    В инете читал, что нужно попытаться отправить send() и если оно вернёт -1, то соединения нет. НО! В моём случаи используются не блокируемые сокеты, так что send() всегда возвращает -1 (мол, подождите, щас отправлю).

    Код (Text):
    1. while (1)
    2.   {
    3.     tv.tv_sec = 1;
    4.     tv.tv_usec = 0;
    5.  
    6.     /* ждём, до появления данных в сокете */
    7.  
    8.     FD_ZERO (&readfds);
    9.     FD_SET (priv->socket, &readfds);
    10.     rc = select (priv->socket+1, &readfds, NULL, NULL, &tv);
    11.  
    12.     if (rc<0)
    13.       {
    14.          /* какая-то ошибка в select() */
    15.       }
    16.     else if (rc==0)
    17.       {
    18.          /* тайм аут, в моей реализации - ждать дальше с новым select() */
    19.         continue;
    20.       }
    21.     else
    22.       {
    23.          /* какие-то данные пришли, можно обрабатывать */
    24.       }
    25.  
    26.      /* А здесь я хочу проверить, живо ли ещё соединение. Вызвать send() не предлагать. */
    27.   }
     
  2. barton

    barton New Member

    Публикаций:
    0
    Регистрация:
    19 июл 2008
    Сообщения:
    164
    Адрес:
    Czechoslovakia
    Это потому что нужно в select'е проверить сокет на writeable'ность.
     
  3. perez

    perez Member

    Публикаций:
    0
    Регистрация:
    25 апр 2005
    Сообщения:
    502
    Адрес:
    Moscow city
    man send

    Код (Text):
    1. RETURN VALUES
    2.      The call returns the number of characters sent, or -1 if an error
    3.      occurred.
    4.  
    5. ERRORS
    6.      The send() function and sendto() and sendmsg() system calls fail if:
    7.  
    8.      [EBADF]            An invalid descriptor was specified.
    9.  
    10.      [EACCES]           The destination address is a broadcast address, and
    11.                         SO_BROADCAST has not been set on the socket.
    12.  
    13.      [ENOTSOCK]         The argument s is not a socket.
    14.  
    15.      [EFAULT]           An invalid user space address was specified for an
    16.                         argument.
    17.  
    18.      [EMSGSIZE]         The socket requires that message be sent atomically,
    19.                         and the size of the message to be sent made this
    20.                         impossible.
    21.  
    22.      [EAGAIN]           The socket is marked non-blocking and the requested
    23.                         operation would block.
    24.  
    25.      [ENOBUFS]          The system was unable to allocate an internal buffer.
    26.                         The operation may succeed when buffers become avail-
    27.                         able.
    28.  
    29.      [ENOBUFS]          The output queue for a network interface was full.
    30.                         This generally indicates that the interface has
    31.                         stopped sending, but may be caused by transient con-
    32.                         gestion.
    33.  
    34.      [EHOSTUNREACH]     The remote host was unreachable.
    35.  
    36.      [EISCONN]          A destination address was specified and the socket is
    37.                         already connected.
    38.  
    39.      [ECONNREFUSED]     The socket received an ICMP destination unreachable
    40.                         message from the last message sent.  This typically
    41.                         means that the receiver is not listening on the remote
    42.                         port.
    43.  
    44.     [EHOSTDOWN]        The remote host was down.
    45.  
    46.      [ENETDOWN]         The remote network was down.
    47.  
    48.      [EPERM]            The process using a SOCK_RAW socket was jailed and the
    49.                         source address specified in the IP header did not
    50.                         match the IP address bound to the prison.
    51.  
    52.      [EPIPE]            The socket is unable to send anymore data
    53.                         (SBS_CANTSENDMORE has been set on the socket).  This
    54.                         typically means that the socket is not connected.
    Эти значения в errno попадают.

    А вообще твоя прога обязана следить за разрывом соединения. И потом не будет необходимости проверять соединение
    Обычно происходит так: когда рвется соединение, select сообщает, что на сокете есть данные, ты вызываешь read:

    Код (Text):
    1.    ssize_t ret = recv(sclient, buffer, buf_size, 0);
    2.    if((!ret) || (ret < 0 && errno != EAGAIN))
    3.    {
    4.     // обработка закрытия сокета, удаление его из массива например
    5.     ...
    6.    }
    7.    else
    8.    {
    9.    // данные пришли, сокет живой
    10.    }
     
  4. perez

    perez Member

    Публикаций:
    0
    Регистрация:
    25 апр 2005
    Сообщения:
    502
    Адрес:
    Moscow city
    -------- Удалите плз, нечаянно отослал не туда -------------
     
  5. AlannY

    AlannY New Member

    Публикаций:
    0
    Регистрация:
    24 окт 2008
    Сообщения:
    41
    perez
    Это не выход. У меня recv получает данные, и если возвращает -1, то, мол, данные в сокете закончились (обрабатывай и жди дальше появления новых). Хотя, можно подправить немного алгоритм и сделать проверку `первых' данных :derisive: Подумаю на досуге :)
     
  6. AlannY

    AlannY New Member

    Публикаций:
    0
    Регистрация:
    24 окт 2008
    Сообщения:
    41
    А вообще по теме обнаружения разрыва соединения:

    В Windows'е есть понятие FD_CLOSE, которое можно перехватить, и если оно перехвачено, то соединение разорорвалось. Так это всё работает в мой Windows программе. Не понимаю, почему такое событие трудно было принять в POSIX ;-( *ужас...
     
  7. Aspire

    Aspire New Member

    Публикаций:
    0
    Регистрация:
    19 май 2007
    Сообщения:
    1.028
    Какую-то ты фигню написал.. Подумай на досуге над этим и помедитируй над тем, что написал perez.
     
  8. perez

    perez Member

    Публикаций:
    0
    Регистрация:
    25 апр 2005
    Сообщения:
    502
    Адрес:
    Moscow city
    AlannY

    Вот написал tcp сервер с использованием select. На суперправильность не претендую, однако все работает =)
    Код (Text):
    1. #include <stdio.h>
    2.  
    3. #include <sys/param.h>
    4. #include <sys/socket.h>
    5. #include <sys/sockio.h>
    6. #include <sys/sysctl.h>
    7. #include <sys/time.h>
    8. #include <sys/wait.h>
    9. #include <ctype.h>
    10. #include <err.h>
    11. #include <errno.h>
    12. #include <signal.h>
    13. #include <stdio.h>
    14. #include <stdlib.h>
    15. #include <stdarg.h>
    16. #include <string.h>
    17. #include <unistd.h>
    18. #include <sysexits.h>
    19. #include <signal.h>
    20. #include <stdarg.h>
    21.  
    22. #include <net/if.h>
    23. #include <netinet/in.h>
    24. #include <netinet/in_systm.h>
    25. #include <netinet/ip.h>
    26. #include <netinet/ip_icmp.h>
    27. #include <netinet/ip_fw.h>
    28. #include <netinet/ip_dummynet.h>
    29. #include <netinet/tcp.h>
    30. #include <arpa/inet.h>
    31. #include <sys/un.h>
    32.  
    33. #include <fcntl.h>
    34. #include <sys/select.h>
    35. #include <time.h>
    36.  
    37. #include <vector>
    38. #include <map>
    39. using namespace std;
    40.  
    41. class client_info
    42. {
    43.     int some_param;
    44. };
    45.  
    46. int main()
    47. {
    48.     int sock = socket(AF_INET, SOCK_STREAM, 0);
    49.     if (socket < 0)
    50.     {
    51.         printf("socket() failed: %d\n", errno);
    52.         return 0;
    53.     }
    54.  
    55.  
    56.         sockaddr_in sin;
    57.  
    58.         sock= socket(AF_INET, SOCK_STREAM, 0);
    59.         bzero(&sin, sizeof(sin));
    60.         sin.sin_family=         AF_INET;
    61.         sin.sin_port=           htons(6666);
    62.         sin.sin_addr.s_addr=    htonl(INADDR_ANY);
    63.  
    64.     int ss_n = 1;
    65.     setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&ss_n, sizeof(ss_n));
    66.  
    67.     if(bind(sock, (struct sockaddr*)&sin, sizeof(sin))== -1)
    68.     {
    69.         printf("No bind!\n");
    70.         return 0;
    71.     }
    72.  
    73.     if(listen(sock, 1) != 0){
    74.         printf("No listen!");
    75.         return 0;
    76.     }
    77.  
    78.     fcntl(sock, F_SETFL, O_NONBLOCK);
    79.  
    80.     fd_set fdset_r, fdset_w;
    81.     map<int, client_info> map_clients;
    82.  
    83.     while(true)
    84.     {
    85.         FD_ZERO(&fdset_r);      // обнуляем списки дескрипторов
    86.         FD_ZERO(&fdset_w);
    87.  
    88.         FD_SET(sock, &fdset_r);     // загоняем главный сокет (ждущий соединений) в списки дескрипторов для вызова select
    89.         FD_SET(sock, &fdset_w);
    90.  
    91.         int max_fd = sock;
    92.  
    93.         for(map<int, client_info> ::iterator Iter = map_clients.begin(), End = map_clients.end(); Iter != End; Iter++)
    94.         {
    95.             int sclient = Iter->first;
    96.             FD_SET(sclient, &fdset_r);  // загоняем в списки дескрипторов все клиентские сокеты
    97.             if(sclient > max_fd) max_fd = sclient;
    98.         }
    99.  
    100.         int ret = select(max_fd + 1, &fdset_r, &fdset_w, NULL, NULL);
    101.  
    102.         if(ret <= 0) continue;
    103.  
    104.         if(FD_ISSET(sock, &fdset_r) || FD_ISSET(sock, &fdset_w))    // значит пришло новое соединение на главный сокет
    105.         {
    106.             int sclient = accept(sock, NULL, NULL);         // принимаем соединение
    107.             //fcntl(sclient, F_SETFL, O_NONBLOCK);
    108.             if(sclient <= 0) break;
    109.             map_clients[sclient];                   // загоняем новый сокет в std::map
    110.             printf("Socket #%02d connected!\n", sclient);
    111.         }
    112.  
    113.         for(map<int, client_info> ::iterator Iter = map_clients.begin(), End = map_clients.end(); Iter != End; Iter++)
    114.         {
    115.             int sclient = Iter->first;
    116.  
    117.             if(FD_ISSET(sclient, &fdset_r))     // проверяем, есть ли активность на этом сокете
    118.             {
    119.                 FD_CLR(sclient, &fdset_r);
    120.  
    121.                 int buf_size = 1024;
    122.                 char *buffer = new char[buf_size];
    123.                 bzero(buffer, buf_size);
    124.                 ssize_t ret = recv(sclient, buffer, buf_size, 0);
    125.  
    126.                 if((!ret) || (ret < 0 && errno != EAGAIN))
    127.                 {
    128.                     printf("Socket #%02d disconnected!\n", sclient);
    129.  
    130.                     delete[] buffer;
    131.                     map_clients.erase(sclient);
    132.                     close(sclient);
    133.  
    134.                     if(map_clients.empty()) break;
    135.  
    136.                     Iter =  map_clients.begin();
    137.                     End =   map_clients.end();
    138.                     continue;
    139.                 }
    140.  
    141.                 printf("socket #%02d:\t%s\n", sclient, buffer);
    142.                 send(sclient, buffer, strlen(buffer), 0);
    143.                 delete[] buffer;
    144.             }
    145.         }
    146.     }  // end of main while
    147.  
    148.     for(map<int, client_info> ::iterator Iter = map_clients.begin(), End = map_clients.end(); Iter != End; Iter++)
    149.     {
    150.         int sclient = Iter->first;
    151.         close(sclient);
    152.     }
    153.     close(sock);
    154.     return 0;
    155. }
    Компилится в FreeBSD 7.0. В линуксе возможно нужно будет поправить хедеры.

    Далее подклюдаемся с другой машины (консоли):
    Видим, что пишет сервер:

    Потом произвольно вводим с клавы любые символы в телнет - сессиях:

    Код (Text):
    1. socket #06:     a
    2. socket #06:     s
    3. socket #06:     d
    4. socket #06:     a
    5. socket #06:     s
    6. socket #06:     d
    7. socket #06:     f
    8. socket #07:     s
    9. socket #07:     d
    10. socket #07:     f
    11. socket #07:     s
    12. socket #07:     d
    13. socket #07:     f
    14. socket #05:     a
    15. socket #05:     d
    16. socket #05:     f
    17. socket #07:     s
    18. socket #07:     d
    19. socket #07:     f
    20. socket #06:     s
    21. socket #06:     d
    22. socket #06:     f
    23. socket #06:     s
    24. socket #06:     d
    Затем по очереди закрываем телнет сессии:

    Как видишь, все работает.
     
  9. perez

    perez Member

    Публикаций:
    0
    Регистрация:
    25 апр 2005
    Сообщения:
    502
    Адрес:
    Moscow city
    Действительно ужос.
    Ладно, со временем придет понимание =)
     
  10. AlannY

    AlannY New Member

    Публикаций:
    0
    Регистрация:
    24 окт 2008
    Сообщения:
    41
    Всё, мне понятна моя ошибка. Нужно проверять, что возвращает recv, а у меня проверки нет (как я уже писал, мол, если ошибка (или recv возвращает -1), то данные на сокете закончились: обрабатывать и ждать заного).