Почему read () блокируется бесконечно при чтении буфера - PullRequest
0 голосов
/ 29 февраля 2020

Я новичок в программировании сокетов и хотел попробовать что-то простое. Эта программа может управлять настройками на моем телевизоре. Все сообщения имеют размер 24 байта. Может быть возвращено одно или несколько сообщений. Я не могу найти хорошее решение, чтобы получить все сообщения без блокировки read (). То, что ниже, будет тем, что я надеялся на простое решение. Кажется, это работает во многих примерах кода, который я нашел. Однако, что происходит после первого l oop, это, кажется, просто блокирует операцию read () бесконечно. Если я удаляю l oop и просто ставлю несколько чтений, происходит то же самое. Пока я не пытаюсь прочитать больше отправленной информации, я в порядке.

Я пробовал пару других вещей, таких как отключение блокировки и добавление таймера. ни работал. На данный момент я могу жить с парой секунд блокировки. Я просто хочу, чтобы программа нормально завершала работу после чтения.

добавление вывода для команды power_on. Он правильно выводит две строки, которые он должен затем блокировать на неопределенный срок.

Dans-MBP:~ mreff555$ ./tvthing 
24: *SAPOWR0000000000000000

24: *SNPOWR0000000000000001

код ниже:

#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <cstdio>
#include <cstdlib>
#include <unistd.h>
#include <cstring>
#include <sys/time.h>

#define PORT 20060

#define POWER_ON        "*SCPOWR0000000000000001\n"
#define POWER_OFF       "*SCPOWR0000000000000000\n"
#define POWER_STATUS    "*SEPOWR################\n"
#define POWER_TOGGLE    "*STPOWR################\n"

int main(int argc, char const * argv[])
{
  struct sockaddr_in tvAddress;
  struct hostent *host = gethostbyname("192.168.1.128");
  memset(&tvAddress, 0,  sizeof(tvAddress));
  tvAddress.sin_family = AF_INET;
  tvAddress.sin_addr.s_addr = htonl(INADDR_ANY);
  tvAddress.sin_addr.s_addr = ((struct in_addr*)(host->h_addr))->s_addr;
  tvAddress.sin_port = htons(PORT);

  char sendBuffer[24] = {0};
  char recBuffer[24] = {0};

  int socket_fd;

  if((socket_fd = socket(AF_INET,SOCK_STREAM, 0)) < 0)
  {
    perror("socket failed");
    exit(EXIT_FAILURE);
  }
  else
  {
    if(connect(socket_fd, (struct sockaddr *)&tvAddress, sizeof(struct sockaddr)))
    {
      perror("connection failed failed");
      exit(EXIT_FAILURE);
    }

   memcpy(&sendBuffer, &POWER_STATUS, sizeof(sendBuffer));
   write(socket_fd, sendBuffer, strlen(sendBuffer));

   int ret;
   while((ret = read(socket_fd, recBuffer, sizeof(recBuffer)) > 0))
   {
     printf("%d: %s\n", ret, recBuffer);
   }

   close(socket_fd); 
  }
}

Ответы [ 2 ]

0 голосов
/ 01 марта 2020

Как оказалось, select предназначен именно для этой цели. Он проверяет указанные файловые дескрипторы в течение указанного промежутка времени, и в случае успеха повторяет процесс. Изменение временного интервала минимизирует блокировку, оставляя достаточно времени для поступления дополнительных сообщений.

#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <cstdio>
#include <cstdlib>
#include <unistd.h>
#include <cstring>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/select.h>

#define PORT 20060

#define POWER_ON        "*SCPOWR0000000000000001\n"
#define POWER_OFF       "*SCPOWR0000000000000000\n"
#define POWER_STATUS    "*SEPOWR################\n"
#define POWER_TOGGLE    "*STPOWR################\n"

int main(int argc, char const * argv[])
{
  struct sockaddr_in tvAddress;
  struct hostent *host = gethostbyname("192.168.1.128");
  memset(&tvAddress, 0,  sizeof(tvAddress));
  tvAddress.sin_family = AF_INET;
  tvAddress.sin_addr.s_addr = htonl(INADDR_ANY);
  tvAddress.sin_addr.s_addr = ((struct in_addr*)(host->h_addr))->s_addr;
  tvAddress.sin_port = htons(PORT);

  char sendBuffer[24] = {0};
  char recBuffer[24] = {0};

  int socket_fd;

  if((socket_fd = socket(AF_INET,SOCK_STREAM, 0)) < 0)
  {
    perror("socket failed");
    exit(EXIT_FAILURE);
  }
  else
  {
    if(connect(socket_fd, (struct sockaddr *)&tvAddress, sizeof(struct sockaddr)))
    {
      perror("connection failed failed");
      exit(EXIT_FAILURE);
    }

   struct timeval tv;
   fd_set sockRead;
   int selectStatus;

   memcpy(&sendBuffer, &POWER_ON, sizeof(sendBuffer));
   write(socket_fd, sendBuffer, strlen(sendBuffer));

   do
   {
     FD_ZERO(&sockRead);
     FD_SET(socket_fd, &sockRead);
     tv.tv_sec = 2;
     tv.tv_usec = 500000;
     selectStatus = select(socket_fd + 1, &sockRead, NULL, NULL, &tv);

     switch(selectStatus)
     {
       case -1:
         perror("select()");
         exit(EXIT_FAILURE);
         break;

       case 0:
         break;

       default:
        printf("Ready for Reading\n");
        read(socket_fd, recBuffer, sizeof(recBuffer));
        printf("%s\n", recBuffer);
     }
   }while (selectStatus > 0);

   close(socket_fd); 
  }
}
0 голосов
/ 29 февраля 2020

Вам нужно читать до тех пор, пока ваш буфер не будет заполнен следующим образом:

unsigned readLen = 0;
unsigned totalLen = sizeof(recBuffer);

while (readLen < totalLen) {
    int ret = read(socket_fd, recBuffer + readLen, totalLen - readLen);
    if (ret > 0) {
        readLen += ret;
    } else {
        // error handling here
        break;
    }
}

Это необходимо, потому что read() возвращает только имеющееся в настоящее время количество байтов, которое может быть меньше, чем вы запрашивали. Из соответствующей man-страницы :

ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ

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

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

Если вы решили сделать ваше приложение более изощренным, вы можете использовать один из механизмов IO Multiplexing , чтобы ваше ожидание ответа прерывалось таймером или входом терминала. Например:

while (true) {
    pollfd fds[] = {
        { socket_fd, POLLIN, 0 },
        { STDIN_FILENO, POLLIN, 0 }
    };

    int ret = poll(fds, sizeof(fds) / sizeof(*fds), -1);
    if (ret > 0) {
        if (fds[0].revents & POLLIN) {
            readResponse(); // read and process response
        }
        if (fds[1].revents & POLLIN) {
            break; // exit on terminal input
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...