简体   繁体   中英

Why does read() block indefinitely when reading a buffer

I'm new to socket programming and wanted to try something simple. This program can manipulate settings on my tv. All messages are 24 bytes. There may be one or more messages returned. I cannot figure out a good solution to get all of the messages without read() blocking on me. What is below would be what I hoped to be a simple solution. It seems to work in a lot of example code I have found. However, what happens is after the first loop it seems to just block on the read() operation infinitely. If I remove the loop and just put multiple reads, the same thing happens. As long as I don't try to read more information that is sent, I'm ok.

I did try a couple of other things like turning off blocking, and adding a timer. neither worked. At this point I can live with a couple seconds of blocking. I just want the program to exit normally after the read.

adding output for a power_on command. It correctly outputs the two lines it should then blocks indefinitely.

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

24: *SNPOWR0000000000000001

code below:

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

You need to read until your buffer is full like this:

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

This is needed because read() returns only the currently available amount of bytes which might be less than you have requested. From the corresponding man-page :

RETURN VALUE

On success, the number of bytes read is returned (zero indicates end of file), and the file position is advanced by this number. It is not an error if this number is smaller than the number of bytes requested; this may happen for example because fewer bytes are actually available right now (maybe because we were close to end-of-file, or because we are reading from a pipe, or from a terminal), or because read() was interrupted by a signal.

If you need to receive several responses you can put the described algorithm into a function and use it repeatedly. In any case you need to know how many responses to expect otherwise your read() will block because it seems that your TV's server is programmed to keep the connection open and it is client's responsibility to choose when to disconnect.

If you decide to make your application more sophisticated you can use one of the IO Multiplexing mechanisms to make your wait for response interruptable by timer or terminal input. For example:

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
        }
    }
}

As it turns out, select is designed exactly for that purpose. It checks the specified file descriptors for a specified time interval, and if successful repeats the process. Tweaking the time interval minimizes the blocking while allowing enough time for additional messages to come in.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM