C/C++ Socket设置接收超时时间的多种方法
作者:Dontla
C/C++ Socket设置非阻塞模式接收超时时间的多种方法
网络编程中经常需要处理的一个问题就是如何正确地处理Socket超时。对于C/C++,有几种常用的技术可以用来设置Socket接收超时时间。在这篇文章中,我们将详细介绍如何在C/C++中设置Socket的非阻塞模式以及如何配置接收超时时间。
非阻塞模式(fcntl)
默认情况下,Socket操作都是阻塞的。这意味着当调用某个Socket函数时(例如recv),如果数据还未就绪,函数会阻塞等待,直到有数据可用为止。然而,在许多情况下,让函数阻塞并不是最佳解决方案(容易造成卡死)。这时,就需要使用非阻塞模式。
设置非阻塞模式
要将Socket设置为非阻塞模式,可以使用fcntl
函数。以下是一段示例代码:
int flags = fcntl(sock_fd, F_GETFL, 0); fcntl(sock_fd, F_SETFL, flags | O_NONBLOCK);
上述代码首先获取了Socket当前的文件状态标志,然后将O_NONBLOCK
标志位添加到文件状态标志中,最后使用F_SETFL
命令将新的文件状态标志设置回Socket。此时,Socket已经处于非阻塞模式。
非阻塞模式下的接收超时
在非阻塞模式下,如果没有数据可用,recv
函数会立即返回一个错误,并设置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); }
在上述代码中,我们在一个循环中不断地尝试接收数据。如果recv返回了错误,并且errno被设置为EWOULDBLOCK或EAGAIN,我们就让进程睡眠一段时间,然后重试。如果尝试了指定的次数还未能成功接收到数据,那么我们就认为已经超时。
这种方法的优点是简单直观。但缺点是可能会占用大量的CPU资源,因为在超时期间,程序会不断地在循环中运行。
使用select函数
另一种处理Socket超时的方法是使用select
函数。select
函数可以监听一组文件描述符,等待它们中的任何一个进入就绪状态(例如,数据可读),或者直到超时。这种方法的优点是可以同时监听多个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函数并检查其返回值。如果select返回0,表示已经超时。如果select返回负数,表示发生了错误。如果select返回正数,表示有文件描述符已经就绪,此时我们就可以调用recv来接收数据了。
setsockopt方法设置Socket超时
除了上述介绍的非阻塞模式和select
函数,还有一种常用的方法是使用setsockopt
函数来直接设置Socket的超时时间。
setsockopt函数概述
setsockopt
函数用于设置指定的Socket选项。它的原型如下:
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
这个函数接收五个参数:sockfd
是要设置的Socket的文件描述符;level
指定选项所在的协议层;optname
是需要设置的选项的名称;optval
指向包含新选项值的缓冲区;optlen
是optval
缓冲区的大小。
使用setsockopt设置接收超时
在Socket编程中,SO_RCVTIMEO
和SO_SNDTIMEO
选项可以分别用来设置接收和发送超时。这两个选项都位于套接字层,所以在调用setsockopt
函数时,level
参数应设为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 }
在上述代码中,我们首先创建了一个timeval结构体,并设置了超时时间。然后,我们调用setsockopt函数,将SO_RCVTIMEO选项的值设置为指向timeout结构体的指针。如果setsockopt返回负数,表示发生了错误。
需要注意的是,SO_RCVTIMEO和SO_SNDTIMEO选项设置的超时时间是一个总时间,而不是在Socket函数阻塞时每次等待的时间。这意味着,如果你在一个循环中多次调用recv函数,那么这些函数调用的总时间将不会超过你设置的超时时间。
完整示例代码
下面是一个unix domain socket使用setsockopt函数设置接收超时的示例代码(用文件套接字通信),其中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函数设置SO_RCVTIMEO选项是一种直接且有效的方法来设置Socket接收超时。这种方法的优点是简单直观,只需要一行代码就可以完成设置。然而,它的缺点是灵活性较差,因为它只能设置一个固定的超时时间,而不能动态地根据网络状况调整超时时间。
总结
在C/C++中,有多种方法可以用来设置Socket接收超时时间。非阻塞模式和select函数亦或setsockopt函数都是处理这个问题的有效工具。需要注意的是,选择哪种方法取决于具体的应用场景。例如,如果你需要同时处理多个Socket,那么select函数可能是更好的选择。如果想要方便,setsockopt函数可以考虑
以上就是C/C++ Socket设置接收超时时间的多种方法的详细内容,更多关于C/C++ Socket接收超时时间的资料请关注脚本之家其它相关文章!