Linux socket编程(7):I/O系统调用(读/写/连接)的超时处理

在网络编程中,对套接字的I/O的系统调用(如read,write,connect)进行超时处理是至关重要的,特别是在需要响应及时的实时数据或避免无限期阻塞的情境下。本文将深入介绍处理套接字I/O超时的两种方法:setsockoptselectsetsockopt允许直接设置套接字的发送和接收超时时间,而select提供了一种多路复用的机制,使得在等待多个套接字就绪时能够设置超时。

文章目录

  • 1 setsockopt
  • 2 select
    • 2.1 读/写操作的超时处理
    • 2.2 连接操作(connect)的超时处理

1 setsockopt

SO_SNDTIMEOSO_RCVTIMEO是与套接字选项相关的两个选项,它们可以用于设置发送和接收数据的超时时间。这两个选项主要用于在套接字上设置超时,以便在指定的时间内等待发送或接收操作完成。

SO_SNDTIMEO(发送超时)

SO_SNDTIMEO 用于设置发送数据的超时时间。通过这个选项,你可以指定在发送数据时等待的最长时间。如果在指定的时间内无法完成发送操作,系统将会返回一个错误。

struct timeval tv;
tv.tv_sec = 3;
tv.tv_usec = 0;
setsockopt(socket_descriptor, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));

SO_RCVTIMEO(接收超时)

SO_RCVTIMEO 用于设置接收数据的超时时间。通过这个选项,可以指定在接收数据时等待的最长时间。如果在指定的时间内未接收到数据,系统将会返回一个错误。

struct timeval tv;
tv.tv_sec = 3;
tv.tv_usec = 0;
setsockopt(socket_descriptor, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));

返回值

当超时时间到达后,read/recv/write/send等函数将返回-1,同时errno.h中的全局变量errno将置为EWOULDBLOCK

例子

下面是一个简单的服务端/客户端的例子,如果3秒内没有收到数据则recv会立即返回。

(1)服务端

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <errno.h>int main() {int server_socket, client_socket;struct sockaddr_in server_addr, client_addr;// 创建服务器套接字server_socket = socket(AF_INET, SOCK_STREAM, 0);if (server_socket == -1) {perror("socket");exit(EXIT_FAILURE);}// 设置服务器地址server_addr.sin_family = AF_INET;server_addr.sin_addr.s_addr = INADDR_ANY;server_addr.sin_port = htons(8080);// 绑定服务器套接字if (bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {perror("bind");exit(EXIT_FAILURE);}// 监听连接if (listen(server_socket, 1) == -1) {perror("listen");exit(EXIT_FAILURE);}printf("Server listening on port 8080...\n");socklen_t client_addr_len = sizeof(client_addr);client_socket = accept(server_socket, (struct sockaddr *)&client_addr, &client_addr_len);if (client_socket == -1) {perror("accept");exit(EXIT_FAILURE);}printf("Client connected...\n");// 设置发送和接收超时为3秒struct timeval tv;tv.tv_sec = 3;tv.tv_usec = 0;setsockopt(client_socket, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));setsockopt(client_socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));char buffer[1024];ssize_t bytesRead, bytesWritten;// 尝试接收数据bytesRead = recv(client_socket, buffer, sizeof(buffer), 0);if (bytesRead == -1) {//perror("recv");if (errno == EWOULDBLOCK) {// 超时,需要进行适当的处理printf("timeout\n");} else {perror("recv");// 其他错误处理}} else if (bytesRead == 0) {printf("Connection closed by client.\n");} else {buffer[bytesRead] = '\0';printf("Received: %s\n", buffer);// 尝试发送数据const char *response = "Hello, Client!";bytesWritten = write(client_socket, response, strlen(response));if (bytesWritten == -1) {perror("write");} else {printf("Sent: %s\n", response);}}// 关闭套接字close(client_socket);close(server_socket);return 0;
}

(2)客户端

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>int main() {int client_socket;struct sockaddr_in server_addr;// 创建客户端套接字client_socket = socket(AF_INET, SOCK_STREAM, 0);if (client_socket == -1) {perror("socket");exit(EXIT_FAILURE);}// 设置服务器地址server_addr.sin_family = AF_INET;server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  // 服务器地址server_addr.sin_port = htons(8080);// 连接到服务器if (connect(client_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {perror("connect");exit(EXIT_FAILURE);}printf("Connected to server...\n");while(1);//客户端:不发任何消息,等待服务端recv超时return 0;
}

2 select

在上一节IO复用模型之select原理及例子中我们介绍了select函数的使用,其中最后一个字段可以设置超时时间。select相比setsockopt更常用,所以这里重点介绍这个方法。先回忆一下select的原型:

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

返回值

  1. 大于0: 表示有一个或多个文件描述符就绪(可读、可写或出错)。

    • 返回值表示就绪的文件描述符的数量。
  2. 等于0: 表示在指定的超时时间内没有文件描述符就绪。

    • 如果 select 函数的超时参数为 NULL,它可能会一直等待,直到有文件描述符就绪或出错。
    • 如果超时参数设置为一个时间值,表示等待指定时间内是否有文件描述符就绪。
  3. 等于-1: 表示出现错误。

    • 可以通过查看errno变量获取具体的错误信息。

在Linux中errno可能返回EINTR

EINTR是在系统调用(select是一个系统调用)被信号中断时返回的错误码,当一个信号(例如SIGINTSIGTERM)被发送给进程,并且进程正在执行一个系统调用时,该系统调用可能会被中断,返回 EINTR 错误。

2.1 读/写操作的超时处理

下面使用select来封装一下读、写和连接操作的超时处理流程。

1、读操作

下面是一个监听读描述符fd的例子,如果三秒没有数据到来,则select将返回0。同时我们还要判断EINTR的返回。

fd_set readfds;
FD_ZERO(&readfds);
FD_SET(fd, &readfds);struct timeval timeout;
timeout.tv_sec = 3;
timeout.tv_usec = 0;do
{ret = select(fd + 1, &readfds, NULL, NULL, &timeout);
} while (ret < 0 && errno == EINTR); //如果是中断信号则继续selectif (ready == -1) {// 出错
} else if (ready == 0) {// 超时
} else {// 文件描述符可读if (FD_ISSET(fd, &readfds)){...}
}

2、写操作

写操作和读操作类似,实现如下:

fd_set writefds;
FD_ZERO(&writefds);
FD_SET(fd, &writefds);struct timeval timeout;
timeout.tv_sec = 3;
timeout.tv_usec = 0;do
{ret = select(fd + 1, NULL, &writefds, NULL, &timeout);
} while (ret < 0 && errno == EINTR); //如果是中断信号则继续selectif (ready == -1) {// 出错
} else if (ready == 0) {// 超时
} else {// 文件描述符可读if (FD_ISSET(fd, &writefds)){...}
}

但在实际使用过程中,我一般将写描述符监听的超时时间设置为0,select的返回值可以判断当前内核是否有资源可以处理写操作,比如当前内存不足了,select将返回0。

2.2 连接操作(connect)的超时处理

对于系统调用connect,在网线没插或者对端没有在listen等情况下,connect函数可能会阻塞几十秒才返回。所以我们很有必要设置connect函数为非阻塞。

1、设置文件描述符为非阻塞

实现这个功能需要用到fcntl函数,它可以用来改变已打开文件描述符的属性:

int fcntl(int fd, int cmd, ... /* arg */);

其中参数说明如下:

  • fd:文件描述符,表示要操作的文件或套接字。
  • cmd:操作命令,指定对文件描述符进行何种操作。常用的命令包括:
    • F_DUPFD: 复制文件描述符
    • F_GETFD: 获取文件描述符标志
    • F_SETFD: 设置文件描述符标志
    • F_GETFL: 获取文件状态标志
    • F_SETFL: 设置文件状态标志
    • F_GETOWN: 获取异步I/O进程ID或套接字拥有者
    • F_SETOWN: 设置异步I/O进程ID或套接字拥有者
  • arg:可选参数,取决于操作命令,根据不同的命令,参数可能是一个整数、一个结构体指针等

我们可以使用操作命令F_SETFL设置文件描述符的非阻塞(O_NONBLOCK)属性来让connect函数变为非阻塞,现在我们就可以封装两个函数:设置非阻塞模式的函数setNonBlocking和设置阻塞模式的函数setBlocking

// 将文件描述符设置为非阻塞模式
int setNonBlocking(int sockfd) {int flags = fcntl(sockfd, F_GETFL, 0);if (flags == -1) {perror("fcntl");return -1;}if (fcntl(sockfd, F_SETFL, flags | O_NONBLOCK) == -1) {perror("fcntl");return -1;}return 0;
}// 将文件描述符设置为阻塞模式
int setBlocking(int sockfd) {int flags = fcntl(sockfd, F_GETFL, 0);if (flags == -1) {perror("fcntl");return -1;}if (fcntl(sockfd, F_SETFL, flags & ~O_NONBLOCK) == -1) {perror("fcntl");return -1;}return 0;
}

2、connect函数的封装

假设有一个待连接的套接字sockfd,然后我们将connect函数设置为非阻塞,整体的代码流程如下:

(1)将套接字设置为非阻塞模式

if (setNonBlocking(sockfd) == -1) {close(sockfd);return 1;
}

(2)调用connect函数

在连接成功的情况下,connect函数将返回0。但前面设置了套接字为非阻塞,所以这里connect函数将立即返回,而大概率是不可能在这么一瞬间建立连接的,所以connect这里会返回-1(这里就不判断返回0的情况了),然后置errno全局变量为EINPROGRESS,表示正在连接中。

struct sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(8080);
serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");if (connect(sockfd, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == -1)
{if (errno == EINPROGRESS) {// 连接正在进行中,可以通过select/poll/epoll来检查连接状态,下面用select来判断printf("Connect in progress...\n");} else {perror("connect");close(sockfd);return 1;}
}

(3)使用select判断连接是否建立成功

当连接成功建立后,套接字将可写,所以我们可以使用select来监听写描述符,并使用timeout超时字段来设置超时时间:

  • 这里需要注意,select返回1的时候并不一定表示连接已经建立成功,如果检查套接字的错误状态(使用 getsockopt函数和SO_ERROR选项)发现没有错误,才表示连接成功建立
int ret;
fd_set wset;
struct timeval tv = {.tv_sec = 5,.tv_usec = 0};FD_ZERO(&wset);
FD_SET(sockfd, &wset);
do
{ret = select(sockfd + 1, NULL, &wset, NULL, &tv);
} while (ret < 0 && errno == EINTR);if(ret == 0)
{//连接超时:在超时时间内没有连接上return 1;
}else if(ret < 0)
{//可以查看errno看发生了什么错误,一般是内核的问题
}else if(ret == 1)
{//检测到可写int error, n;socklen_t len = sizeof(error);n = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len);if(n == 0){//理论上此时已经建立连接成功,但实际发现无论网络是否正常都会返回0,所以继续使用getsockname判断struct sockaddr_in clientAddr;socklen_t clientAddrLen;n = getsockname(sockfd, (struct sockaddr*)&clientAddr, &clientAddrLen);if(n == 0){//此时建立连接成功return 0;}}else{//建立连接失败return 1;}
}

(4)恢复阻塞模式

connect完毕后,需要恢复原来的设置:

// 恢复套接字为阻塞模式
if (setBlocking(sockfd) == -1) {close(sockfd);return 1;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/175375.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

小程序域名SSL证书的重要性

1. 数据安全 小程序中可能涉及用户的个人信息、支付信息等敏感数据&#xff0c;而未加密的通信容易受到中间人攻击。通过使用SSL证书&#xff0c;所有数据在传输过程中都会被加密&#xff0c;确保用户信息不被窃取或篡改。 2. 用户信任 浏览器和操作系统对使用SSL证书的网站…

索引出错问题。为什么建立了索引,也避免了索引失效问题,还是会出现查询不走索引的情况?

什么建立了索引&#xff0c;也避免了索引失效问题&#xff0c;还是会出现查询不走索引的情况&#xff1f; 其实这个问题在于CBO&#xff08;Cost-based Optimizer&#xff09;&#xff0c;优化器是很强大的&#xff01;他根据开销来决定是否要用索引以及用哪个索引&#xff01;…

Spring-SpringFramework特性以及IOC相关知识

SpringFramework五大模块 特性 IOC思想和DI IOC是容器&#xff0c;用于管理资源 IOC&#xff1a;Inversion of Control 反转控制 DI&#xff1a;Dependecy Injection 依赖注入 组件以预先定义好的方式接受来自容器的资源注入 IOC在Spring中的实现 spring提供两种方式&…

图书管理系统源码,图书管理系统开发,图书借阅系统源码四TuShuManager应用程序MVC视图View

Asp.net web应用程序MVC之View视图 .ASP.NET MVC页面也就是要说的视图基本被放在Views文件夹下&#xff1b; 2.利用APS.NET MVC模板生成框架&#xff0c;Views文件夹下的默认页面为.cshtml页面&#xff1b; 3.ASP.NET MVC默认页面为Razor格式的页面&#xff0c;因此默认页面为.…

NX二次开发UF_CURVE_ask_wrap_curves 函数介绍

文章作者&#xff1a;里海 来源网站&#xff1a;https://blog.csdn.net/WangPaiFeiXingYuan UF_CURVE_ask_wrap_curves Defined in: uf_curve.h int UF_CURVE_ask_wrap_curves(tag_t wrap_curve_object, int * num_output_curves, tag_t * * output_curves ) overview 概述 …

了解静态测试?

静态测试是一种软件测试方法&#xff0c;它主要通过分析软件或代码的静态属性来检查潜在的问题和缺陷&#xff0c;而无需实际执行程序。这种测试方法侧重于检查源代码和其他软件文档&#xff0c;以发现错误并提高软件质量。 为什么要做静态测试&#xff1f; 提前发现和修复错…

[操作系统]京东一面~进程相关汇总

1 进程、线程、协程的概念 进程&#xff1a; 静态程序的运行状态就叫进程。是资源分配的基本单位。 线程&#xff1a; 是进程的一个执行单元&#xff0c;是进程内的调度实体。是CPU调度的独立单位。线程也被称为轻量级进程。 协程&#xff1a; 是一种比线程更加轻量级的存在。…

vue3安装eslint和prettier,最简单的步骤

第1步&#xff1a; 安装eslint yarn add eslint -D 第2步&#xff1a; 在根文件夹中&#xff0c;创建.eslintrc.js文件 第3步&#xff1a; 在package.json文件中新增命令 "lint": "eslint --fix --ext .ts,.tsx,.vue src --quiet","prettier"…

红米手机如何远程控制荣耀手机?

很多人都知道&#xff0c;华为体系有【畅联】&#xff0c;与华为手机或平板“畅连”通话时&#xff0c;可共享屏幕给对方&#xff0c;一边聊天一边演示&#xff0c;还可在屏幕上涂鸦帮助理解。同样&#xff0c;小米体系有【小米通话】&#xff0c;它的远程协助功能可以帮助朋友…

蓝桥杯第100 题 九宫幻方 DFS 全排列 C++ 解题思维

题目 九宫幻方https://www.lanqiao.cn/problems/100/learning/?page1&first_category_id1&name%E4%B9%9D 思路和解题方法 一 &#xff08;DFS) 首先&#xff0c;定义了一些全局变量和数组。vis数组用于标记已经出现过的数字&#xff0c;a数组用于存储数独的初始状态…

mac上Homebrew的安装与使用

打开终端&#xff1a;command空格 &#xff0c;搜索‘’终端 ’&#xff0c;打开终端 在终端中输入以下命令并按下回车键&#xff1a; /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"这个命令会自动下载并安装…

Ubuntu18.04磁盘取证-中难度篇

涉及的镜像文件&#xff1a; sdb.vhd uac.tar ubuntu.20211208.mem 需要利用的工具&#xff1a; volatility3 volatility2.6.1 FTK/Autopsy Strings 题干 容器是一个Ubuntu Linux 蜜罐&#xff0c;用来观察利用 CVE-2021-41773 的漏洞攻击者想要做什么。 您将看到一个 cr…

【c++】多线程大幅缩减时间

多线程在进行复杂运算时能够大量节约时间 提醒自己能够在多线程运算的时候一定要充分利用 能够省下2/3的时间 测试代码 #include<vector> #include <iostream> #include <thread> using namespace cv; using namespace std; int result1 0 ; bool thread…

【TiDB】TiDB离线方式部署

目录 1 下载TiDB离线组件包 2 安装TiUP 3 合并离线包 4 TIDB 软件和硬件环境建议配置 5 TiDB环境与系统配置检查 6 生成集群初始化配置文件模板 7 执行部署命令 1 检查就能存在的潜在风险 2 手动修复风险 3 部署 TiDB 集群 8 查看TIUP管理的集群情况 9 检查部署的…

【jupyter notebook中插件 nbextensions 安装失败分析与解决方法】

文章目录 问题描述分析与解决总结 问题描述 一开始在安装 notebook 中的插件 nbextensions 时根本没有注意到版本的适配问题&#xff0c;都是进行默认的安装&#xff0c;结果安装是最新版本的 notebook7.x&#xff0c;恰好 notebook7.x 版本不再适应插件 nbextensions&#xf…

Java八股文面试全套真题【含答案】- SpringMVC篇

以下是一些关于Spring MVC语言的经典面试题以及它们的答案&#xff1a; 什么是Spring MVC框架&#xff1f;它的特点是什么&#xff1f; Spring MVC是基于Java的一种Web应用框架&#xff0c;用于开发基于MVC&#xff08;模型-视图-控制器&#xff09;模式的Web应用程序。它的特…

Oracle 内置sql函数大全

F.1字符函数——返回字符值 这些函数全都接收的是字符族类型的参数(CHR除外)并且返回字符值.除了特别说明的之外,这些函数大部分返回VARCHAR2类型的数值.字符函数的返回类型所受的限制和基本数据库类型所受的限制是相同的,比如: VARCHAR2数值被限制为2000字符(ORACLE 8中为400…

Kafka介绍(一)

什么是kafka Apache Kafka 是一个开源分布式事件流平台&#xff08;通俗点说其实就是一个类似中介的东西&#xff0c;房东将信息放入中介&#xff0c;客户咨询中介或者中介推送信息给咨询过的客户&#xff0c;获取消息&#xff09;&#xff0c;它的核心能力有&#xff0c;高吞吐…

使用Arthas排查性能问题

Arthas 是一款线上监控诊断产品&#xff0c;通过全局视角实时查看应用 load、内存、gc、线程的状态信息&#xff0c;并能在不修改应用代码的情况下&#xff0c;对业务问题进行诊断&#xff0c;包括查看方法调用的出入参、异常&#xff0c;监测方法执行耗时&#xff0c;类加载信…

AttributeError: ‘list‘ object has no attribute ‘size‘解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…