2014-09-17 3 views
0

У меня небольшая проблема с сокетами, когда я не получаю данные, за исключением первого цикла, это время от времени. Если я закрываю и снова открываю сокет каждый цикл, хотя я, кажется, правильно получаю данные. Есть идеи о том, почему?linux C++ socket select loop

Пример цикла без закрытия:

int socketHandle = socket(AF_INET,SOCK_DGRAM,0); 

sockaddr_in serverAddr; 

serverAddr.sin_family = AF_INET; 
serverAddr.sin_addr.s_addr = inet_addr(/*UDP IP ADDRESS*/); 
serverAddr.sin_port = htons(/*UDP PORT*/); 

struct timeval tv; 
fd_set rfds; 

FD_ZERO(&rfds); 
FD_SET(socketHandle, &rfds); 

tv.tv_usec = 0.0; 
int recVal = 0; 
int sockLen = sizeof(serverAddr); 
bind(socketHandle, (struct sockaddr*)&serverAddr, (socklen_t)sockLen); 

bool timePassed = false; 
time_t startListenTime = time(NULL); 

tv.tv_sec = maxUpdateTime; 

while(true) 
{ 
    recVal = select(socketHandle + 1, &rfds, NULL, NULL, &tv); 
    switch(recVal) 
    { 
     case(0): 
     { 
      //Timeout 
      break; 
     } 
     case(-1): 
     { 
      //Error 
      break; 
     } 
     default: 
     { 
      /*Packet Data Type*/ pkt; 
      if(recvfrom(socketHandle, &pkt, sizeof(/*Packet Data Type*/), 0, (sockaddr*)&serverAddr, (socklen_t*)&sockLen) < 0) 
      { 
       //Failed to Recieve Data 
       break; 
      } 
      else 
      { 
       //Recieved Data!! 
      } 
      break; 
     } 
    } 
} 

Пример цикла с закрытием:

while(true) 
{ 
    int socketHandle = socket(AF_INET,SOCK_DGRAM,0); 

    sockaddr_in serverAddr; 

    serverAddr.sin_family = AF_INET; 
    serverAddr.sin_addr.s_addr = inet_addr(/*UDP IP ADDRESS*/); 
    serverAddr.sin_port = htons(/*UDP PORT*/); 

    struct timeval tv; 
    fd_set rfds; 

    FD_ZERO(&rfds); 
    FD_SET(socketHandle, &rfds); 

    tv.tv_usec = 0.0; 
    int recVal = 0; 
    int sockLen = sizeof(serverAddr); 
    bind(socketHandle, (struct sockaddr*)&serverAddr, (socklen_t)sockLen); 


    bool timePassed = false; 
    time_t startListenTime = time(NULL); 

    tv.tv_sec = maxUpdateTime; 

    recVal = select(socketHandle + 1, &rfds, NULL, NULL, &tv); 
    switch(recVal) 
    { 
     case(0): 
     { 
      //Timeout 
      break; 
     } 
     case(-1): 
     { 
      //Error 
      break; 
     } 
     default: 
     { 
      /*Packet Datastructure*/ pkt; 
      if(recvfrom(socketHandle, &pkt, sizeof(/*Packet Datastructure*/), 0, (sockaddr*)&serverAddr, (socklen_t*)&sockLen) < 0) 
      { 
       //Failed to read packet 
       break; 
      } 
      else 
      { 
       //Read Packet!! 
      } 
      break; 
     } 
    } 
    close(socketHandle); 
} 
+2

переместите FD_ZERO() и FD_SET() внутри цикла прямо относительно select(). –

+1

См. Это http://www.mkssoftware.com/docs/man3/select.3.asp, говорящие, что дескрипторы изменены, чтобы показать, какие дескрипторы готовы для ввода-вывода. –

+0

@RichardChambers, вы должны сделать ответ не просто комментарием, поэтому я могу его продвинуть :) –

ответ

2

Функция select() использует указанную маску дескриптора файла, чтобы определить, какие файловые дескрипторы отслеживать событие (чтение, запись и т. Д.). Когда дескриптор файла доступен для операции ввода-вывода (чтение, запись), функция select() изменяет дескрипторы, чтобы указать, какие из файлов готовы для данного действия ввода-вывода.

См. Эту статью на странице select function and the macros/functions used with the file descriptors.

Старые программы типа Unix часто обрабатывали дескриптор файла как битовую маску и просто проверяли биты. Однако фактическая реализация файлового дескриптора может варьироваться в зависимости от компилятора, поэтому лучше всего использовать стандартные макросы/функции дескриптора файла для установки, очистки и тестирования различных файловых дескрипторов.

Так при использовании функции select() вам нужно использовать FD_ZERO() и FD_SET() так, что вы будете устанавливать конкретные дескрипторы файлов, которые вы хотите для этого конкретного вызова функции select(). Когда возвращается select(), он укажет, какие из обозначенных дескрипторов файлов действительно готовы для использования в операции ввода-вывода (чтение, запись и т. Д.).

Так что ваш код будет на самом деле что-то вроде:

while(true) 
{ 
    fd_set rfds; 

    FD_ZERO(&rfds); 
    FD_SET(socketHandle, &rfds); 
    recVal = select(socketHandle + 1, &rfds, NULL, NULL, &tv); 
    switch(recVal) 
    { 
     case(0): 
     { 
      //Timeout 
      break; 
     } 
     case(-1): 
     { 
      //Error 
      break; 
     } 
     default: 
     { 
      /*Packet Data Type*/ pkt; 
      if(recvfrom(socketHandle, &pkt, sizeof(/*Packet Data Type*/), 0, (sockaddr*)&serverAddr, (socklen_t*)&sockLen) < 0) 
      { 
       //Failed to Recieve Data 
       break; 
      } 
      else 
      { 
       //Recieved Data!! 
      } 
      break; 
     } 
    } 

Однако то, что вы действительно должны сделать, это использовать функцию FD_ISSET(), чтобы проверить, какие именно файловые дескрипторы будут готовы к использованию. В вашем случае у вас есть только один, но в ситуации, когда есть несколько дескрипторов, вы хотели бы использовать функцию FD_ISSET().

+0

Aha! Большое спасибо за быстрый ответ и объяснение. – APoster

1

Когда select возвращает ничего, кроме -1 считается успехом и множества FD модифицируются, чтобы сказать, какие из них готовы или имеют ошибку.

От POSIX spec:

После успешного завершения, pselect() или выберите() функция должна изменить объекты, на которую указывает readfds, writefds и errorfds, чтобы указать, какие дескрипторы файлов готовы для чтения, готовы для записи или имеют условие ошибки, ожидающее соответственно, и возвращают общее количество готовых дескрипторов во всех выходных наборах.

Если тайм-аут происходит, то он возвращает нуль, поскольку ни один из дескрипторов не был готов, и никакие биты не будут установлены в наборах fd.

Так что, когда время разговора не установлено в rfds, и т. Д. В следующем цикле, когда вы вызываете select, вы просите его подождать пустого набора, который никогда не вернет положительное значение, потому что если вы ждете для нулевых FD, то вы никогда не получите ненулевое число готовых FD!

Необходимо помнить, что rfds является как входным параметром, так и выходным параметром, поэтому убедитесь, что он установлен правильно перед каждым вызовом до select.

+0

Хороший момент о тайм-ауте вернет дескрипторы файлов. –