C/C++ Socket設置接收超時時間的多種方法
C/C++ Socket設置非阻塞模式接收超時時間的多種方法
網(wǎng)絡編程中經(jīng)常需要處理的一個問題就是如何正確地處理Socket超時。對于C/C++,有幾種常用的技術可以用來設置Socket接收超時時間。在這篇文章中,我們將詳細介紹如何在C/C++中設置Socket的非阻塞模式以及如何配置接收超時時間。
非阻塞模式(fcntl)
默認情況下,Socket操作都是阻塞的。這意味著當調用某個Socket函數(shù)時(例如recv),如果數(shù)據(jù)還未就緒,函數(shù)會阻塞等待,直到有數(shù)據(jù)可用為止。然而,在許多情況下,讓函數(shù)阻塞并不是最佳解決方案(容易造成卡死)。這時,就需要使用非阻塞模式。
設置非阻塞模式
要將Socket設置為非阻塞模式,可以使用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)標志設置回Socket。此時,Socket已經(jīng)處于非阻塞模式。
非阻塞模式下的接收超時
在非阻塞模式下,如果沒有數(shù)據(jù)可用,recv
函數(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被設置為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設置接收超時
以下是一段使用select
設置接收超時的示例代碼:
#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; }
在上述代碼中,我們首先初始化了一個文件描述符集合和一個時間間隔結構體。然后,我們將目標Socket添加到文件描述符集合中,并設置了超時時間。最后,我們調用select函數(shù)并檢查其返回值。如果select返回0,表示已經(jīng)超時。如果select返回負數(shù),表示發(fā)生了錯誤。如果select返回正數(shù),表示有文件描述符已經(jīng)就緒,此時我們就可以調用recv來接收數(shù)據(jù)了。
setsockopt方法設置Socket超時
除了上述介紹的非阻塞模式和select
函數(shù),還有一種常用的方法是使用setsockopt
函數(shù)來直接設置Socket的超時時間。
setsockopt函數(shù)概述
setsockopt
函數(shù)用于設置指定的Socket選項。它的原型如下:
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
這個函數(shù)接收五個參數(shù):sockfd
是要設置的Socket的文件描述符;level
指定選項所在的協(xié)議層;optname
是需要設置的選項的名稱;optval
指向包含新選項值的緩沖區(qū);optlen
是optval
緩沖區(qū)的大小。
使用setsockopt設置接收超時
在Socket編程中,SO_RCVTIMEO
和SO_SNDTIMEO
選項可以分別用來設置接收和發(fā)送超時。這兩個選項都位于套接字層,所以在調用setsockopt
函數(shù)時,level
參數(shù)應設為SOL_SOCKET
。
以下是一段示例代碼,展示如何使用setsockopt
設置接收超時:
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結構體,并設置了超時時間。然后,我們調用setsockopt函數(shù),將SO_RCVTIMEO選項的值設置為指向timeout結構體的指針。如果setsockopt返回負數(shù),表示發(fā)生了錯誤。
需要注意的是,SO_RCVTIMEO和SO_SNDTIMEO選項設置的超時時間是一個總時間,而不是在Socket函數(shù)阻塞時每次等待的時間。這意味著,如果你在一個循環(huán)中多次調用recv函數(shù),那么這些函數(shù)調用的總時間將不會超過你設置的超時時間。
完整示例代碼
下面是一個unix domain socket使用setsockopt函數(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; } }
小結
使用setsockopt函數(shù)設置SO_RCVTIMEO選項是一種直接且有效的方法來設置Socket接收超時。這種方法的優(yōu)點是簡單直觀,只需要一行代碼就可以完成設置。然而,它的缺點是靈活性較差,因為它只能設置一個固定的超時時間,而不能動態(tài)地根據(jù)網(wǎng)絡狀況調整超時時間。
總結
在C/C++中,有多種方法可以用來設置Socket接收超時時間。非阻塞模式和select函數(shù)亦或setsockopt函數(shù)都是處理這個問題的有效工具。需要注意的是,選擇哪種方法取決于具體的應用場景。例如,如果你需要同時處理多個Socket,那么select函數(shù)可能是更好的選擇。如果想要方便,setsockopt函數(shù)可以考慮
以上就是C/C++ Socket設置接收超時時間的多種方法的詳細內容,更多關于C/C++ Socket接收超時時間的資料請關注腳本之家其它相關文章!
相關文章
Qt數(shù)據(jù)庫應用之實現(xiàn)數(shù)據(jù)打印到紙張
關于Qt打印內容到紙張,網(wǎng)上的辦法非常多,比如有些直接用painter繪制,逐步控制分頁打印。本文介紹的方法則是將內容作為html設置到文檔對象,再調用文檔對象的print方法傳入QPrinter對象打印,感興趣的同學可以了解一下2022-01-01