C/C++ Socket設(shè)置接收超時時間的多種方法
C/C++ Socket設(shè)置非阻塞模式接收超時時間的多種方法
網(wǎng)絡編程中經(jīng)常需要處理的一個問題就是如何正確地處理Socket超時。對于C/C++,有幾種常用的技術(shù)可以用來設(shè)置Socket接收超時時間。在這篇文章中,我們將詳細介紹如何在C/C++中設(shè)置Socket的非阻塞模式以及如何配置接收超時時間。
非阻塞模式(fcntl)
默認情況下,Socket操作都是阻塞的。這意味著當調(diào)用某個Socket函數(shù)時(例如recv),如果數(shù)據(jù)還未就緒,函數(shù)會阻塞等待,直到有數(shù)據(jù)可用為止。然而,在許多情況下,讓函數(shù)阻塞并不是最佳解決方案(容易造成卡死)。這時,就需要使用非阻塞模式。
設(shè)置非阻塞模式
要將Socket設(shè)置為非阻塞模式,可以使用fcntl函數(shù)。以下是一段示例代碼:
int flags = fcntl(sock_fd, F_GETFL, 0); fcntl(sock_fd, F_SETFL, flags | O_NONBLOCK);
上述代碼首先獲取了Socket當前的文件狀態(tài)標志,然后將O_NONBLOCK標志位添加到文件狀態(tài)標志中,最后使用F_SETFL命令將新的文件狀態(tài)標志設(shè)置回Socket。此時,Socket已經(jīng)處于非阻塞模式。
非阻塞模式下的接收超時
在非阻塞模式下,如果沒有數(shù)據(jù)可用,recv函數(shù)會立即返回一個錯誤,并設(shè)置errno為EWOULDBLOCK或EAGAIN。因此,可以通過檢查errno來確定是否超時。以下是一段示例代碼:
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#define MAX_RETRIES 5
#define SLEEP_DURATION 1000000 // One second
#define BUFFER_SIZE 1024
int retries = 0;
char buffer[BUFFER_SIZE];
while(retries < MAX_RETRIES) {
memset(buffer, 0, sizeof(buffer)); // Clear the buffer
ssize_t recv_status = recv(sock_fd, buffer, BUFFER_SIZE - 1, 0);
if(recv_status < 0) {
if(errno == EWOULDBLOCK || errno == EAGAIN) {
usleep(SLEEP_DURATION);
retries++;
} else {
perror("Error in recv"); // Print error message
break;
}
} else if(recv_status == 0) { // Socket is closed
printf("Socket is closed by the peer\n");
break;
} else {
// Handle received data
printf("Received data: %s\n", buffer);
break;
}
}
if(retries >= MAX_RETRIES) {
printf("Failed to receive data after %d retries\n", MAX_RETRIES);
}
在上述代碼中,我們在一個循環(huán)中不斷地嘗試接收數(shù)據(jù)。如果recv返回了錯誤,并且errno被設(shè)置為EWOULDBLOCK或EAGAIN,我們就讓進程睡眠一段時間,然后重試。如果嘗試了指定的次數(shù)還未能成功接收到數(shù)據(jù),那么我們就認為已經(jīng)超時。
這種方法的優(yōu)點是簡單直觀。但缺點是可能會占用大量的CPU資源,因為在超時期間,程序會不斷地在循環(huán)中運行。
使用select函數(shù)
另一種處理Socket超時的方法是使用select函數(shù)。select函數(shù)可以監(jiān)聽一組文件描述符,等待它們中的任何一個進入就緒狀態(tài)(例如,數(shù)據(jù)可讀),或者直到超時。這種方法的優(yōu)點是可以同時監(jiān)聽多個Socket,并且不會占用過多的CPU資源。
使用select設(shè)置接收超時
以下是一段使用select設(shè)置接收超時的示例代碼:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define TIMEOUT_SECONDS 5
#define BUFFER_SIZE 1024
int main() {
fd_set set;
struct timeval timeout;
char buffer[BUFFER_SIZE];
int sock_fd;
// TODO: Initialize the socket here. You need to write your own logic to do this.
FD_ZERO(&set);
FD_SET(sock_fd, &set);
timeout.tv_sec = TIMEOUT_SECONDS;
timeout.tv_usec = 0;
int rv = select(sock_fd + 1, &set, NULL, NULL, &timeout);
if(rv == 0) {
// Timeout
printf("Timeout occurred! No data after %d seconds.\n", TIMEOUT_SECONDS);
} else if(rv < 0) {
// Error occurred
perror("Error occurred in select");
} else {
// Socket ready, can receive data now
ssize_t bytes_received = recv(sock_fd, buffer, BUFFER_SIZE - 1, 0); // leave space for '\0'
if(bytes_received < 0) {
// Error occurred in recv
perror("Error occurred in recv");
} else {
// Null-terminate the received data
buffer[bytes_received] = '\0';
printf("Received data: %s\n", buffer);
}
}
// Clean up and close the socket
if(close(sock_fd) < 0) {
perror("Error occurred while closing the socket");
}
return 0;
}
在上述代碼中,我們首先初始化了一個文件描述符集合和一個時間間隔結(jié)構(gòu)體。然后,我們將目標Socket添加到文件描述符集合中,并設(shè)置了超時時間。最后,我們調(diào)用select函數(shù)并檢查其返回值。如果select返回0,表示已經(jīng)超時。如果select返回負數(shù),表示發(fā)生了錯誤。如果select返回正數(shù),表示有文件描述符已經(jīng)就緒,此時我們就可以調(diào)用recv來接收數(shù)據(jù)了。
setsockopt方法設(shè)置Socket超時
除了上述介紹的非阻塞模式和select函數(shù),還有一種常用的方法是使用setsockopt函數(shù)來直接設(shè)置Socket的超時時間。
setsockopt函數(shù)概述
setsockopt函數(shù)用于設(shè)置指定的Socket選項。它的原型如下:
int setsockopt(int sockfd, int level, int optname,
const void *optval, socklen_t optlen);
這個函數(shù)接收五個參數(shù):sockfd是要設(shè)置的Socket的文件描述符;level指定選項所在的協(xié)議層;optname是需要設(shè)置的選項的名稱;optval指向包含新選項值的緩沖區(qū);optlen是optval緩沖區(qū)的大小。
使用setsockopt設(shè)置接收超時
在Socket編程中,SO_RCVTIMEO和SO_SNDTIMEO選項可以分別用來設(shè)置接收和發(fā)送超時。這兩個選項都位于套接字層,所以在調(diào)用setsockopt函數(shù)時,level參數(shù)應設(shè)為SOL_SOCKET。
以下是一段示例代碼,展示如何使用setsockopt設(shè)置接收超時:
struct timeval timeout;
timeout.tv_sec = TIMEOUT_SECONDS;
timeout.tv_usec = 0;
if (setsockopt(sock_fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0) {
// Error occurred
}
在上述代碼中,我們首先創(chuàng)建了一個timeval結(jié)構(gòu)體,并設(shè)置了超時時間。然后,我們調(diào)用setsockopt函數(shù),將SO_RCVTIMEO選項的值設(shè)置為指向timeout結(jié)構(gòu)體的指針。如果setsockopt返回負數(shù),表示發(fā)生了錯誤。
需要注意的是,SO_RCVTIMEO和SO_SNDTIMEO選項設(shè)置的超時時間是一個總時間,而不是在Socket函數(shù)阻塞時每次等待的時間。這意味著,如果你在一個循環(huán)中多次調(diào)用recv函數(shù),那么這些函數(shù)調(diào)用的總時間將不會超過你設(shè)置的超時時間。
完整示例代碼
下面是一個unix domain socket使用setsockopt函數(shù)設(shè)置接收超時的示例代碼(用文件套接字通信),其中FILE_PATH是文件路徑。
bool nonBlockingRecv()
{
struct sockaddr_un addr;
int sock_fd;
char buffer[BUFFER_SIZE] = "REQ";
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, FILE_PATH.c_str());
sock_fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock_fd < 0)
{
std::cout << "Request socket failed\n";
return false;
}
if (connect(sock_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0)
{
std::cout << "Connect socket failed\n";
close(sock_fd);
return false;
}
//1.send command
SEND_INFO(COMMAND);
// Set recv timeout to 100ms
struct timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 100000; // 100 ms
if (setsockopt(sock_fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0)
{
std::cout << "Setting socket timeout failed\n";
close(sock_fd);
return false;
}
//2.receive response of register req
memset(buffer, 0, BUFFER_SIZE);
int recv_status = recv(sock_fd, buffer, BUFFER_SIZE, 0);
if (recv_status < 0)
{
if (errno == EWOULDBLOCK || errno == EAGAIN)
{
std::cout << "Receive timeout\n";
}
else
{
std::cout << "Receive error\n";
}
close(sock_fd);
return false;
}
std::cout << "Received [" << buffer << "] from manager" << std::endl;
//3.check result
if (NULL != strstr(buffer, SUCCESS.c_str()))//receive success.
{
std::cout << "Received success\n";
close(sock_fd);
return true;
}
else
{
std::cout << "Received fail\n";
close(sock_fd);
return false;
}
}
小結(jié)
使用setsockopt函數(shù)設(shè)置SO_RCVTIMEO選項是一種直接且有效的方法來設(shè)置Socket接收超時。這種方法的優(yōu)點是簡單直觀,只需要一行代碼就可以完成設(shè)置。然而,它的缺點是靈活性較差,因為它只能設(shè)置一個固定的超時時間,而不能動態(tài)地根據(jù)網(wǎng)絡狀況調(diào)整超時時間。
總結(jié)
在C/C++中,有多種方法可以用來設(shè)置Socket接收超時時間。非阻塞模式和select函數(shù)亦或setsockopt函數(shù)都是處理這個問題的有效工具。需要注意的是,選擇哪種方法取決于具體的應用場景。例如,如果你需要同時處理多個Socket,那么select函數(shù)可能是更好的選擇。如果想要方便,setsockopt函數(shù)可以考慮
以上就是C/C++ Socket設(shè)置接收超時時間的多種方法的詳細內(nèi)容,更多關(guān)于C/C++ Socket接收超時時間的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Qt數(shù)據(jù)庫應用之實現(xiàn)數(shù)據(jù)打印到紙張
關(guān)于Qt打印內(nèi)容到紙張,網(wǎng)上的辦法非常多,比如有些直接用painter繪制,逐步控制分頁打印。本文介紹的方法則是將內(nèi)容作為html設(shè)置到文檔對象,再調(diào)用文檔對象的print方法傳入QPrinter對象打印,感興趣的同學可以了解一下2022-01-01

