简体   繁体   English

为什么 read() 在读取缓冲区时无限期阻塞

[英]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.所有消息都是 24 字节。 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.我想不出一个很好的解决方案来获取所有消息,而 read() 不会阻止我。 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.然而,在第一个循环之后发生的事情似乎只是无限地阻塞了 read() 操作。 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.为 power_on 命令添加输出。 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.这是必需的,因为read()仅返回当前可用的字节数,这可能少于您请求的字节数。 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.例如,这可能是因为现在实际可用的字节较少(可能因为我们接近文件尾,或者因为我们正在从管道或终端读取),或者因为 read() 被中断信号。

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.在任何情况下,您都需要知道预期有多少响应,否则您的read()将阻塞,因为您的电视服务器似乎已被编程为保持连接打开,并且客户端有责任选择何时断开连接。

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.如果您决定让您的应用程序更复杂,您可以使用IO 多路复用机制之一,使您的等待响应可被计时器或终端输入中断。 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.事实证明, select 正是为此目的而设计的。 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); 
  }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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