C/C++ Socket设置接收超时时间的多种方法

 更新时间:2024年01月24日 10:29:56   作者:Dontla  
网络编程中经常需要处理的一个问题就是如何正确地处理Socket超时,对于C/C++,有几种常用的技术可以用来设置Socket接收超时时间,在这篇文章中,我们将详细介绍如何在C/C++中设置Socket的非阻塞模式以及如何配置接收超时时间,需要的朋友可以参考下

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为EWOULDBLOCKEAGAIN。因此,可以通过检查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指向包含新选项值的缓冲区;optlenoptval缓冲区的大小。

使用setsockopt设置接收超时

在Socket编程中,SO_RCVTIMEOSO_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接收超时时间的资料请关注脚本之家其它相关文章!

相关文章

  • 基于稀疏图上的Johnson算法的详解

    基于稀疏图上的Johnson算法的详解

    本篇文章介绍了,稀疏图上的Johnson算法的详解。需要的朋友参考下
    2013-05-05
  • Qt数据库应用之实现数据打印到纸张

    Qt数据库应用之实现数据打印到纸张

    关于Qt打印内容到纸张,网上的办法非常多,比如有些直接用painter绘制,逐步控制分页打印。本文介绍的方法则是将内容作为html设置到文档对象,再调用文档对象的print方法传入QPrinter对象打印,感兴趣的同学可以了解一下
    2022-01-01
  • C++ XML库用法详解

    C++ XML库用法详解

    TinyXML-2是C++中一个轻量级、易于使用的XML解析库,支持XML的读取和写入,内存占用小,适合嵌入式系统,本文给大家介绍C++ XML库用法,感兴趣的朋友一起看看吧
    2025-03-03
  • c语言中main函数用法及知识点总结

    c语言中main函数用法及知识点总结

    在本篇文章里小编给大家分享的是一篇关于c语言中main函数用法及知识点总结内容,有需要的朋友们可以跟着学习参考下。
    2021-10-10
  • c++ vector对象相关总结

    c++ vector对象相关总结

    这篇文章主要介绍了c++ vector对象的相关资料,帮助大家更好的理解和学习使用c++,感兴趣的朋友可以了解下
    2021-02-02
  • 获取本地网卡适配器信息具体代码

    获取本地网卡适配器信息具体代码

    这篇文章主要介绍了获取本地网卡适配器信息具体代码,有需要的朋友可以参考一下
    2013-12-12
  • C++实现俄罗斯方块源码

    C++实现俄罗斯方块源码

    这篇文章主要为大家详细介绍了C++实现俄罗斯方块源码完整版,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-06-06
  • C++实现顺序表的方法

    C++实现顺序表的方法

    本文给大家带来了C++实现顺序表的方法,代码简单易懂,附有注释,感兴趣的朋友一起看下吧
    2016-08-08
  • Qt消除警告的实现示例

    Qt消除警告的实现示例

    Qt5 和 Qt6 之间存在一些差异,导致在编译时可能产生警告,为了消除这些警告,Qt 提供了一些宏定义来帮助你在代码中处理这些差异,本文主要介绍了Qt消除警告的实现示例,感兴趣的可以了解一下
    2023-09-09
  • 快速学习C语言中for循环语句的基本使用方法

    快速学习C语言中for循环语句的基本使用方法

    这篇文章主要简单介绍了C语言中for循环语句的基本使用方法,是C语言入门学习中的基础知识,需要的朋友可以参考下
    2015-11-11

最新评论