[英]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.