C/C++ Socket設(shè)置接收超時(shí)時(shí)間的多種方法
C/C++ Socket設(shè)置非阻塞模式接收超時(shí)時(shí)間的多種方法
網(wǎng)絡(luò)編程中經(jīng)常需要處理的一個(gè)問(wèn)題就是如何正確地處理Socket超時(shí)。對(duì)于C/C++,有幾種常用的技術(shù)可以用來(lái)設(shè)置Socket接收超時(shí)時(shí)間。在這篇文章中,我們將詳細(xì)介紹如何在C/C++中設(shè)置Socket的非阻塞模式以及如何配置接收超時(shí)時(shí)間。
非阻塞模式(fcntl)
默認(rèn)情況下,Socket操作都是阻塞的。這意味著當(dāng)調(diào)用某個(gè)Socket函數(shù)時(shí)(例如recv),如果數(shù)據(jù)還未就緒,函數(shù)會(huì)阻塞等待,直到有數(shù)據(jù)可用為止。然而,在許多情況下,讓函數(shù)阻塞并不是最佳解決方案(容易造成卡死)。這時(shí),就需要使用非阻塞模式。
設(shè)置非阻塞模式
要將Socket設(shè)置為非阻塞模式,可以使用fcntl
函數(shù)。以下是一段示例代碼:
int flags = fcntl(sock_fd, F_GETFL, 0); fcntl(sock_fd, F_SETFL, flags | O_NONBLOCK);
上述代碼首先獲取了Socket當(dāng)前的文件狀態(tài)標(biāo)志,然后將O_NONBLOCK
標(biāo)志位添加到文件狀態(tài)標(biāo)志中,最后使用F_SETFL
命令將新的文件狀態(tài)標(biāo)志設(shè)置回Socket。此時(shí),Socket已經(jīng)處于非阻塞模式。
非阻塞模式下的接收超時(shí)
在非阻塞模式下,如果沒(méi)有數(shù)據(jù)可用,recv
函數(shù)會(huì)立即返回一個(gè)錯(cuò)誤,并設(shè)置errno為EWOULDBLOCK
或EAGAIN
。因此,可以通過(guò)檢查errno來(lái)確定是否超時(shí)。以下是一段示例代碼:
#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); }
在上述代碼中,我們?cè)谝粋€(gè)循環(huán)中不斷地嘗試接收數(shù)據(jù)。如果recv返回了錯(cuò)誤,并且errno被設(shè)置為EWOULDBLOCK或EAGAIN,我們就讓進(jìn)程睡眠一段時(shí)間,然后重試。如果嘗試了指定的次數(shù)還未能成功接收到數(shù)據(jù),那么我們就認(rèn)為已經(jīng)超時(shí)。
這種方法的優(yōu)點(diǎn)是簡(jiǎn)單直觀(guān)。但缺點(diǎn)是可能會(huì)占用大量的CPU資源,因?yàn)樵诔瑫r(shí)期間,程序會(huì)不斷地在循環(huán)中運(yùn)行。
使用select函數(shù)
另一種處理Socket超時(shí)的方法是使用select
函數(shù)。select
函數(shù)可以監(jiān)聽(tīng)一組文件描述符,等待它們中的任何一個(gè)進(jìn)入就緒狀態(tài)(例如,數(shù)據(jù)可讀),或者直到超時(shí)。這種方法的優(yōu)點(diǎn)是可以同時(shí)監(jiān)聽(tīng)多個(gè)Socket,并且不會(huì)占用過(guò)多的CPU資源。
使用select設(shè)置接收超時(shí)
以下是一段使用select
設(shè)置接收超時(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; }
在上述代碼中,我們首先初始化了一個(gè)文件描述符集合和一個(gè)時(shí)間間隔結(jié)構(gòu)體。然后,我們將目標(biāo)Socket添加到文件描述符集合中,并設(shè)置了超時(shí)時(shí)間。最后,我們調(diào)用select函數(shù)并檢查其返回值。如果select返回0,表示已經(jīng)超時(shí)。如果select返回負(fù)數(shù),表示發(fā)生了錯(cuò)誤。如果select返回正數(shù),表示有文件描述符已經(jīng)就緒,此時(shí)我們就可以調(diào)用recv來(lái)接收數(shù)據(jù)了。
setsockopt方法設(shè)置Socket超時(shí)
除了上述介紹的非阻塞模式和select
函數(shù),還有一種常用的方法是使用setsockopt
函數(shù)來(lái)直接設(shè)置Socket的超時(shí)時(shí)間。
setsockopt函數(shù)概述
setsockopt
函數(shù)用于設(shè)置指定的Socket選項(xiàng)。它的原型如下:
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
這個(gè)函數(shù)接收五個(gè)參數(shù):sockfd
是要設(shè)置的Socket的文件描述符;level
指定選項(xiàng)所在的協(xié)議層;optname
是需要設(shè)置的選項(xiàng)的名稱(chēng);optval
指向包含新選項(xiàng)值的緩沖區(qū);optlen
是optval
緩沖區(qū)的大小。
使用setsockopt設(shè)置接收超時(shí)
在Socket編程中,SO_RCVTIMEO
和SO_SNDTIMEO
選項(xiàng)可以分別用來(lái)設(shè)置接收和發(fā)送超時(shí)。這兩個(gè)選項(xiàng)都位于套接字層,所以在調(diào)用setsockopt
函數(shù)時(shí),level
參數(shù)應(yīng)設(shè)為SOL_SOCKET
。
以下是一段示例代碼,展示如何使用setsockopt
設(shè)置接收超時(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)建了一個(gè)timeval結(jié)構(gòu)體,并設(shè)置了超時(shí)時(shí)間。然后,我們調(diào)用setsockopt函數(shù),將SO_RCVTIMEO選項(xiàng)的值設(shè)置為指向timeout結(jié)構(gòu)體的指針。如果setsockopt返回負(fù)數(shù),表示發(fā)生了錯(cuò)誤。
需要注意的是,SO_RCVTIMEO和SO_SNDTIMEO選項(xiàng)設(shè)置的超時(shí)時(shí)間是一個(gè)總時(shí)間,而不是在Socket函數(shù)阻塞時(shí)每次等待的時(shí)間。這意味著,如果你在一個(gè)循環(huán)中多次調(diào)用recv函數(shù),那么這些函數(shù)調(diào)用的總時(shí)間將不會(huì)超過(guò)你設(shè)置的超時(shí)時(shí)間。
完整示例代碼
下面是一個(gè)unix domain socket使用setsockopt函數(shù)設(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選項(xiàng)是一種直接且有效的方法來(lái)設(shè)置Socket接收超時(shí)。這種方法的優(yōu)點(diǎn)是簡(jiǎn)單直觀(guān),只需要一行代碼就可以完成設(shè)置。然而,它的缺點(diǎn)是靈活性較差,因?yàn)樗荒茉O(shè)置一個(gè)固定的超時(shí)時(shí)間,而不能動(dòng)態(tài)地根據(jù)網(wǎng)絡(luò)狀況調(diào)整超時(shí)時(shí)間。
總結(jié)
在C/C++中,有多種方法可以用來(lái)設(shè)置Socket接收超時(shí)時(shí)間。非阻塞模式和select函數(shù)亦或setsockopt函數(shù)都是處理這個(gè)問(wèn)題的有效工具。需要注意的是,選擇哪種方法取決于具體的應(yīng)用場(chǎng)景。例如,如果你需要同時(shí)處理多個(gè)Socket,那么select函數(shù)可能是更好的選擇。如果想要方便,setsockopt函數(shù)可以考慮
以上就是C/C++ Socket設(shè)置接收超時(shí)時(shí)間的多種方法的詳細(xì)內(nèi)容,更多關(guān)于C/C++ Socket接收超時(shí)時(shí)間的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Qt數(shù)據(jù)庫(kù)應(yīng)用之實(shí)現(xiàn)數(shù)據(jù)打印到紙張
關(guān)于Qt打印內(nèi)容到紙張,網(wǎng)上的辦法非常多,比如有些直接用painter繪制,逐步控制分頁(yè)打印。本文介紹的方法則是將內(nèi)容作為html設(shè)置到文檔對(duì)象,再調(diào)用文檔對(duì)象的print方法傳入QPrinter對(duì)象打印,感興趣的同學(xué)可以了解一下2022-01-01c語(yǔ)言中main函數(shù)用法及知識(shí)點(diǎn)總結(jié)
在本篇文章里小編給大家分享的是一篇關(guān)于c語(yǔ)言中main函數(shù)用法及知識(shí)點(diǎn)總結(jié)內(nèi)容,有需要的朋友們可以跟著學(xué)習(xí)參考下。2021-10-10c++ vector對(duì)象相關(guān)總結(jié)
這篇文章主要介紹了c++ vector對(duì)象的相關(guān)資料,幫助大家更好的理解和學(xué)習(xí)使用c++,感興趣的朋友可以了解下2021-02-02快速學(xué)習(xí)C語(yǔ)言中for循環(huán)語(yǔ)句的基本使用方法
這篇文章主要簡(jiǎn)單介紹了C語(yǔ)言中for循環(huán)語(yǔ)句的基本使用方法,是C語(yǔ)言入門(mén)學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-11-11