簡體   English   中英

為什么 read() 在讀取緩沖區時無限期阻塞

[英]Why does read() block indefinitely when reading a buffer

我是套接字編程的新手,想嘗試一些簡單的事情。 這個程序可以操縱我電視上的設置。 所有消息都是 24 字節。 可能會返回一條或多條消息。 我想不出一個很好的解決方案來獲取所有消息,而 read() 不會阻止我。 下面是我希望是一個簡單的解決方案。 它似乎適用於我發現的許多示例代碼。 然而,在第一個循環之后發生的事情似乎只是無限地阻塞了 read() 操作。 如果我刪除循環並只進行多次讀取,則會發生同樣的事情。 只要我不嘗試閱讀發送的更多信息,我就可以。

我確實嘗試了其他一些事情,例如關閉阻塞和添加計時器。 都沒有工作。 在這一點上,我可以忍受幾秒鍾的阻塞。 我只希望程序在讀取后正常退出。

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

您需要閱讀,直到您的緩沖區已滿,如下所示:

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()僅返回當前可用的字節數,這可能少於您請求的字節數。 從相應的手冊頁

返回值

成功時,返回讀取的字節數(零表示文件結束),文件位置按此數字前進。 如果此數字小於請求的字節數,則不是錯誤; 例如,這可能是因為現在實際可用的字節較少(可能因為我們接近文件尾,或者因為我們正在從管道或終端讀取),或者因為 read() 被中斷信號。

如果您需要接收多個響應,您可以將所描述的算法放入一個函數中並重復使用。 在任何情況下,您都需要知道預期有多少響應,否則您的read()將阻塞,因為您的電視服務器似乎已被編程為保持連接打開,並且客戶端有責任選擇何時斷開連接。

如果您決定讓您的應用程序更復雜,您可以使用IO 多路復用機制之一,使您的等待響應可被計時器或終端輸入中斷。 例如:

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

事實證明, 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); 
  }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM