[英]Linux socket send() error EAGAIN
我正在設計使用代理和HTTP CONNECT
請求的HTTP隧道。 大約90%的時間,當我嘗試發送連接請求CONNECT 192.168.1.68:3001 HTTP/1.1\\r\\n\\r\\n
發送失敗時,返回errno = 11, "Try Again"
。
我正在使用非阻塞套接字,並且套接字在連接嘗試之間先關閉然后重新打開。 我不認為發送緩沖區已滿,因為我沒有發送太多數據。 我很困惑為什么它有時會連接而有時卻不連接(並且似乎沒有某種模式,有時它會連續兩次連接,有時連接之間會出現10次失敗)。
我可能會收到此錯誤的原因有哪些?
編輯:
(碼)
if (connect(sock, pSockAddr, iSockAddrSize)) {
...
char message[80];
sprintf(message, "CONNECT 192.168.1.68:3001 HTTP/1.1\r\n\r\n");
printf("*J* Message loaded.\n"); //*J*
printf("*J* %s\n", message); //*J*
if (send(sock, message, strlen(message) + 1, 0) < 0) printf("*J* Send Failed: %d\n", errno);
else printf("*J* Data Sent.\n");
}
...
printf("\n*J* Close Socket (RESET HOST COMMAND)");
closesocket(sock);
(輸出)
[SUCCESS]
*J* Starting connect...
*J* Message loaded.
*J* CONNECT 192.168.1.68:3001 HTTP/1.1
*J* Data Sent.
*J* rv = 1
*J* Ending connect...
[FAIL]
*J* Starting connect...
*J* Message loaded.
*J* CONNECT 192.168.1.68:3001 HTTP/1.1
*J* Send Failed: 11
*J* Ending connect...
您的代碼中有幾個錯誤:
connect()
成功返回0,錯誤返回-1。 if
任何非零值, if
語句的評估結果為true。 這意味着當connect()
因錯誤而失敗時,您的代碼正在發送CONNECT
消息。 但是,並非所有錯誤都是致命的。 特別是,由於您使用的是非阻塞套接字,因此EINPROGRESS
/ WSAEWOULDBLOCK
錯誤指示連接仍在掛起,您需要使用select()
或(e)poll()
來等待套接字實際完成連接(或失敗)。 因此,只有在connect()
異步連接並且恰好在您有機會調用send()
之前完全連接到代理時,您的代碼才能工作。 但這是您不應該依賴的行為。 您需要正確處理錯誤。
send()
的返回值指定實際接受的字節數,如果出錯則返回-1。 可以接受的字節數少於請求的字節數(尤其是在非阻塞套接字上)。 而且您需要處理非致命錯誤,尤其是(WSA)EWOULDBLOCK
(和非Windows平台上的EAGAIN
,甚至還有EINTR
),這意味着套接字在調用send()
時無法接受新數據。 ,因此請再次調用send()
。 您應該在循環中調用send()
,直到發送完所有字節,否則將發生致命錯誤。
即使您正確地send()
讀取數據,在strlen()
之后使用+ 1
也是錯誤的。 您將空終止符包含在要發送的數據中,但這不是CONNECT
協議的一部分。 代理會將空終止符視為應用程序數據,並將按原樣通過隧道轉發到下一個服務器(如果隧道已成功打開),從而中斷與該服務器的通信。
話雖如此,請嘗試以下類似的方法。 您使用closeocket()
表示您正在為Windows編程,因此這是Windows特定的:
int ret, err;
...
ret = connect(sock, pSockAddr, iSockAddrSize);
if (ret == -1)
{
err = WSAGetLastError();
if (err == WSAEWOULDBLOCK)
{
fd_set wfd;
FD_ZERO(&wfd);
FD_SET(sock, &wfd);
fd_set efd;
FD_ZERO(&efd);
FD_SET(sock, &efd);
timeval timeout;
timeout.tv_sec = ...;
timeout.tv_usec = ...;
ret = select(0, NULL, &wfd, &wfd, &timeout);
if (ret == 0)
{
printf("*J* Connect Timeout\n");
// handle timeout as needed ...
return;
}
if ((ret > 0) && FD_ISSET(sock, &efd))
{
err = 0;
getsockopt(sock, SOL_SOCKET, SO_ERROR, (char*)&err, sizeof(err));
ret = -1;
}
}
if (ret == -1)
{
printf("*J* Connect Failed: %d\n", err);
// handle fatal error as needed ...
return;
}
}
...
char message[80];
sprintf(message, "CONNECT 192.168.1.68:3001 HTTP/1.1\r\n\r\n");
printf("*J* Message loaded.\n"); //*J*
printf("*J* %s\n", message); //*J*
char *pmsg = message;
int len = strlen(message);
do
{
ret = send(sock, pmsg, len, 0);
if (ret == -1)
{
err = getLastSocketError();
if (err == WSAEWOULDBLOCK)
{
fd_set wfd;
FD_ZERO(&wfd);
FD_SET(sock, &wfd);
timeval timeout;
timeout.tv_sec = ...;
timeout.tv_usec = ...;
ret = select(0, NULL, &wfd, NULL, &timeout);
if (ret > 0)
continue;
if (ret == 0)
{
printf("*J* Send Timeout\n");
// handle timeout as needed ...
return;
}
err = WSAGetLastError();
}
printf("*J* Send Failed: %d\n", err);
// handle fatal error as needed ...
return;
}
else
{
pmsg += ret;
len += ret;
}
}
while (len > 0);
printf("*J* Data Sent\n");
...
printf("*J* Close Socket\n");
closesocket(sock);
如果您想要更多的跨平台內容,請嘗試以下方法(或使用跨平台套接字庫):
int getLastSocketError()
{
#ifdef WINDOWS
return WSAGetLastError();
#else
return errno;
#endif
};
int ret, err;
...
ret = connect(sock, pSockAddr, iSockAddrSize);
if (ret == -1)
{
err = getLastSocketError();
if (
#ifdef WINDOWS
err == WSAEWOULDBLOCK
#else
err == EINPROGRESS
#endif
)
{
#ifndef WINDOWS
do {
#endif
fd_set wfd;
FD_ZERO(&wfd);
FD_SET(sock, &wfd);
#ifdef WINDOWS
fd_set efd;
FD_ZERO(&efd);
FD_SET(sock, &efd);
#endif
timeval timeout;
timeout.tv_sec = ...;
timeout.tv_usec = ...;
#ifdef WINDOWS
ret = select(0, NULL, &wfd, &wfd, &timeout);
#else
ret = select(sock+1, NULL, &wfd, NULL, &timeout);
#endif
if (ret == 0)
{
printf("*J* Connect Timeout\n");
// handle timeout as needed ...
return;
}
#ifdef WINDOWS
if ((ret > 0) && FD_ISSET(sock, &efd))
{
err = 0;
getsockopt(sock, SOL_SOCKET, SO_ERROR, (char*)&err, sizeof(err));
ret = -1;
}
#endif
#ifndef WINDOWS
} while ((ret == -1) && (errno == EINTR));
#endif
}
if (ret == -1)
{
printf("*J* Connect Failed: %d\n", err);
// handle fatal error as needed ...
return;
}
}
...
char message[80];
sprintf(message, "CONNECT 192.168.1.68:3001 HTTP/1.1\r\n\r\n");
printf("*J* Message loaded.\n"); //*J*
printf("*J* %s\n", message); //*J*
char *pmsg = message;
int len = strlen(message);
do
{
ret = send(sock, pmsg, len, 0);
if (ret == -1)
{
err = getLastSocketError();
if (
#ifdef WINDOWS
err == WSAEWOULDBLOCK
#else
(err == EWOULDBLOCK) || (err == EAGAIN)
#endif
)
{
fd_set wfd;
FD_ZERO(&wfd);
FD_SET(sock, &wfd);
timeval timeout;
timeout.tv_sec = ...;
timeout.tv_usec = ...;
ret = select(
#ifdef WINDOWS
0
#else
sock+1
#endif
, NULL, &wfd, NULL, &timeout);
if (ret > 0)
continue;
if (ret == 0)
{
printf("*J* Send Timeout\n");
// handle timeout as needed ...
return;
}
err = getLastSocketError();
}
#ifndef WINDOWS
if (err != EINTR)
#endif
{
printf("*J* Send Failed: %d\n", err);
// handle fatal error as needed ...
return;
}
}
else
{
pmsg += ret;
len += ret;
}
}
while (len > 0);
printf("*J* Data Sent\n");
...
printf("*J* Close Socket\n");
#ifdef WINDOWS
closesocket(sock);
#else
close(sock);
#endif
if (connect(sock, pSockAddr, iSockAddrSize)) {
...
if (send(sock, message, strlen(message) + 1, 0) < 0) printf("*J* Send Failed: %d\n", errno);
if (connect(..
在connect
返回非零值時輸入if (connect(..
)中的塊。由於connect在成功時返回0,而在錯誤時(永久或暫時)返回-1,這意味着在connect
未返回時(尚未)輸入該塊。在大多數情況下,由於連接尚未完成,因此在這種情況下調用send
會失敗,但在某些幸運的情況下,如果同時成功,則可能會成功。
如果連接成功,則可能打算進入該塊。 在這種情況下,正確的處理方法是
if (0 == connect(sock, pSockAddr, iSockAddrSize)) {
...
當然,由於使用了非阻塞式套接字,因此連接不可能立即成功進行,因此需要對非阻塞式連接進行適當的處理。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.