[IO复用] recv()和send()的阻塞和非阻塞、返回值、超时

2024-01-24 12:52

本文主要是介绍[IO复用] recv()和send()的阻塞和非阻塞、返回值、超时,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 前言
    • 阻塞
      • 超时设置
    • 非阻塞
      • 创建socket时,直接用SOCK_NOBLOCK指定为非阻塞
        • server部分
        • client 部分
      • 使用fcntl()把socket设置为非阻塞
      • socket阻塞,单独把recv或者send设置为非阻塞
    • recv和send的返回值
    • 参考文章

前言

记录一下recv和send函数的相关信息。

阻塞

头文件

#include <sys/socket.h> //socket()
#include <unistd.h>  //close()
#include<netinet/in.h> //sockaddr_in#include <stdio.h> //perror()
#include <errno.h>#include <string.h> //memset
#include <iostream>#include <netinet/in.h> //inet_ntoa
#include <arpa/inet.h>

阻塞模式server代码,进行recv

#if 1 //阻塞模式
int main()
{int listen_fd = socket(AF_INET, SOCK_STREAM, 0);if(listen_fd == -1) {perror("socket");return -1;}timeval timev;timev.tv_sec = 3;timev.tv_usec = 0;int ret = setsockopt(listen_fd, SOL_SOCKET, SO_RCVTIMEO, (void*)&timev, sizeof(timev));if(ret != 0){perror("setsockopt SO_RCVTIMEO");close(listen_fd);return -1;}sockaddr_in local_addr;local_addr.sin_family = AF_INET;local_addr.sin_addr.s_addr = htonl(INADDR_ANY);local_addr.sin_port = htons(9100);ret = bind(listen_fd, (sockaddr*)&local_addr, sizeof(local_addr));if(ret == -1){perror("bind");close(listen_fd);return -1;}ret = listen(listen_fd,5);if (ret == -1){perror("listen");close(listen_fd);return -1;}sockaddr_in cnn_addr;memset(&cnn_addr, 0x00, sizeof(cnn_addr));socklen_t len = sizeof(cnn_addr);int conn_fd = accept(listen_fd, (sockaddr*)&cnn_addr, &len);if(conn_fd == -1){perror("accept");close(listen_fd);return -1;}else{std::cout << "accept addr:" << inet_ntoa(cnn_addr.sin_addr)<< " port:" << ntohs(cnn_addr.sin_port) << std::endl;int loop_count = 0;char buff[512] = {0x00};while (1){std::cout << "loop:" << ++loop_count << " times" << std::endl;int count = recv(conn_fd, buff, 512, 0);if(count == 0) {//无论阻塞模式还是非阻塞模式,//recv 返回0,都表示链接断开std::cout << "close end" << std::endl;break;}if(count == -1){//无论阻塞模式还是非阻塞模式,//recv 返回-1,都表示错误,返回一下三个错误,被视为正常if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK){std::cout << "EWOULDBLOCK.continue" << std::endl;continue;}else{std::cout << "ERROR.break" << errno <<std::endl;break;}}else std::cout << "recv (" << count << " bytes):" << buff << std::endl;}}std::cout << "close connection" << std::endl;close(listen_fd);return 0;
}
#endif //阻塞模式

阻塞模式client代码,进行send

#if 1 //阻塞模式
int main()
{int local_fd = socket(AF_INET, SOCK_STREAM, 0);if(local_fd == -1) {perror("socket");return -1;}timeval timev;timev.tv_sec = 3;timev.tv_usec = 0;int ret = setsockopt(local_fd, SOL_SOCKET, SO_SNDTIMEO, (void*)&timev, sizeof(timev));sockaddr_in local_addr;local_addr.sin_family = AF_INET;local_addr.sin_addr.s_addr = htonl(INADDR_ANY);local_addr.sin_port = htons(9100);ret = connect(local_fd, (sockaddr*)&local_addr, sizeof(local_addr));if(ret == -1){perror("accept");close(local_fd);return -1;}else{std::cout << "connect addr:" << inet_ntoa(local_addr.sin_addr)<< " port:" << ntohs(local_addr.sin_port) << std::endl;int loop_count = 0;char buff[512] = {0x00};sprintf(buff, "%s", "hello");while (1){std::cout << "loop:" << ++loop_count << " times" << std::endl;getchar();int count = send(local_fd, buff, strlen(buff), 0);if(count == 0) {//无论阻塞模式还是非阻塞模式,//send 返回0,都表示链接断开std::cout << "close end" << std::endl;break;}if(count == -1){//无论阻塞模式还是非阻塞模式,//send 返回-1,都表示错误,返回一下三个错误,被视为正常if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK){std::cout << "EWOULDBLOCK.continue" << std::endl;continue;}else{std::cout << "ERROR.break" << errno <<std::endl;break;}}elsestd::cout << "send (" << count << " bytes):" << buff << std::endl;}}std::cout << "close connection" << std::endl;close(local_fd);return 0;
}
#endif //阻塞模式

超时设置

上面代码中均用setsockopt进行了recv和send的超时设置。

int ret = setsockopt(listen_fd, SOL_SOCKET, SO_RCVTIMEO, (void*)&timev, sizeof(timev));
 int ret = setsockopt(local_fd, SOL_SOCKET, SO_SNDTIMEO, (void*)&timev, sizeof(timev));

这个超时设置不但包括recv和send,还包括accept。
accept
当accept超时的时候,会返回-1,errno==11(“Resource temporarily unavailable”)。

recv
当recv超时的时候,会返回-1, errno==EAGAIN(11)。
EWOULDBLOCK 就是EAGAIN

#define	EWOULDBLOCK	EAGAIN	/* Operation would block */

非阻塞

创建socket时,直接用SOCK_NOBLOCK指定为非阻塞

server部分

设置如下,因为socket()默认是阻塞的,所以设置了SOCK_NONBLOCK就会是非阻塞socket了。

- int listen_fd = socket(AF_INET, SOCK_STREAM, 0); //block socket
+ int listen_fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0); //non-block socket

和上面的超时设置一样,如果在sock()函数中,设置了SOCK_NONBLOCK参数,accept会变成非阻塞,
如果执行时没有连接可以返回,accept就会直接返回-1,errno:11。
需要用一个while循环, 当accept返回 -1 并且errno 为EAGAIN 时,重复accept。

但是此时recv和send还是阻塞的
这是因为,上面的diff代码,创建的是用于listen的socket,它被设置为非阻塞后,会影响跟他有关的accept。
但是recv和send,关联的socket是accept返回的连接的socket,跟上面listen的fd的设置无关了。

 int conn_fd = accept(listen_fd, (sockaddr*)&cnn_addr, &len);...int count = recv(conn_fd, buff, 512, 0);

此时可以用accept4()把accept返回的socket,设置为非阻塞的:

- int conn_fd = accept(listen_fd, (sockaddr *)&cnn_addr, &len); //return block socket
+ int conn_fd = accept4(listen_fd, (sockaddr *)&cnn_addr, &len, SOCK_NONBLOCK); //return non-block socket

这样后续的recv 就是非阻塞了。

client 部分

向上面一样,在socket()中添加SOCK_NONBLOCK参数,可以把socket设置为非阻塞。
和socket关联的connect也是非阻塞的了。当无法立即建立连接,connect会返回EINPROGRESS。
可以直接不启动server,直接启动client进行connect来测试:
阻塞connect的执行结果:

~/share/myproj/2.1.1-multiIO/20240121$ ./client
connect: Connection refused ← perror输出

非阻塞connect的执行结果:

~/share/myproj/2.1.1-multiIO/20240121$ ./client
connect: Operation now in progress ← perror输出
connect EINPROGRESS continue ← 循环处的log,证明continue了
connect: Connection refused ← perror输出

跟上面同理,connect后面的send,也变成非阻塞的了。
此时让server不调用recv(), client的send会一直发送,直到server的接收缓冲区满了,send将会立刻返回一个EWOULDBLOCK错误。
而如果是阻塞的socket,send将会阻塞住。

使用fcntl()把socket设置为非阻塞

这里只是设置方式的差别。
实际效果是和上面一样的。
上面是在socket()函数中,设置SOCK_NONBLOCK来进行非阻塞设置。
还可以用以下方法:
代码来自于:使用fcntl()函数设置socket为阻塞态或非阻塞态
设置为非阻塞

#include <fcntl.h> //fcntl
int func()
{int fd = socket(AF_INET, SOCK_STREAM, 0);int flag = fcntl(fd,F_GETFL,0);//获取文件fd当前的状态//int flag = fcntl(fd,F_GETFL);//不用第3个参数也可以if(flag<0){perror("fcntl F_GETFL fail"); close(fd); return -1; }if (fcntl(fd, F_SETFL, flag | O_NONBLOCK) < 0)//设置为非阻塞态{  perror("fcntl F_SETFL fail"); close(fd);return -1; }
}

恢复阻塞

int func()
{int fd = socket(AF_INET, SOCK_STREAM, 0);int flag = fcntl(fd,F_GETFL,0);//获取文件fd当前的状态if(flag<0){perror("fcntl F_GETFL fail"); close(fd); return -1; }flag = flag&~O_NONBLOCK;//flag &= ~O_NONBLOCK;//等同于上行if (fcntl(fd, F_SETFL, flag) < 0)//设置为阻塞态{  perror("fcntl F_SETFL fail"); close(fd);return -1; }
}

socket阻塞,单独把recv或者send设置为非阻塞

也可以单独把recv和send函数设置为非阻塞。
设置方法如下:

- int count = recv(conn_fd, buff, 512, 0);
+  count = recv(conn_fd, buff, 512, MSG_DONTWAIT);

这样就是socket是阻塞的,但是设置了MSG_DONTWAIT的recv是非阻塞的(除了这个recv函数,别的accept等函数都还是阻塞的)。

recv和send的返回值

不论阻塞还是非阻塞,
recv的返回值,都是
大于0,表示接受的字节数
等于0,表示断开连接
等于-1,如果是errno == (EAGAIN || EWOULDBLOCK || EINTR) ,则表示正常,继续recv;
其余errno 表示发生错误。

send也是如此。

参考文章

socket阻塞与非阻塞情况下的recv、send、read、write返回值

使用fcntl()函数设置socket为阻塞态或非阻塞态

send与recv函数详解

这篇关于[IO复用] recv()和send()的阻塞和非阻塞、返回值、超时的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/639760

相关文章

Java的IO模型、Netty原理解析

《Java的IO模型、Netty原理解析》Java的I/O是以流的方式进行数据输入输出的,Java的类库涉及很多领域的IO内容:标准的输入输出,文件的操作、网络上的数据传输流、字符串流、对象流等,这篇... 目录1.什么是IO2.同步与异步、阻塞与非阻塞3.三种IO模型BIO(blocking I/O)NI

Javascript访问Promise对象返回值的操作方法

《Javascript访问Promise对象返回值的操作方法》这篇文章介绍了如何在JavaScript中使用Promise对象来处理异步操作,通过使用fetch()方法和Promise对象,我们可以从... 目录在Javascript中,什么是Promise1- then() 链式操作2- 在之后的代码中使

Go使用pprof进行CPU,内存和阻塞情况分析

《Go使用pprof进行CPU,内存和阻塞情况分析》Go语言提供了强大的pprof工具,用于分析CPU、内存、Goroutine阻塞等性能问题,帮助开发者优化程序,提高运行效率,下面我们就来深入了解下... 目录1. pprof 介绍2. 快速上手:启用 pprof3. CPU Profiling:分析 C

在 Spring Boot 中使用异步线程时的 HttpServletRequest 复用问题记录

《在SpringBoot中使用异步线程时的HttpServletRequest复用问题记录》文章讨论了在SpringBoot中使用异步线程时,由于HttpServletRequest复用导致... 目录一、问题描述:异步线程操作导致请求复用时 Cookie 解析失败1. 场景背景2. 问题根源二、问题详细分

Java中实现订单超时自动取消功能(最新推荐)

《Java中实现订单超时自动取消功能(最新推荐)》本文介绍了Java中实现订单超时自动取消功能的几种方法,包括定时任务、JDK延迟队列、Redis过期监听、Redisson分布式延迟队列、Rocket... 目录1、定时任务2、JDK延迟队列 DelayQueue(1)定义实现Delayed接口的实体类 (

Nginx设置连接超时并进行测试的方法步骤

《Nginx设置连接超时并进行测试的方法步骤》在高并发场景下,如果客户端与服务器的连接长时间未响应,会占用大量的系统资源,影响其他正常请求的处理效率,为了解决这个问题,可以通过设置Nginx的连接... 目录设置连接超时目的操作步骤测试连接超时测试方法:总结:设置连接超时目的设置客户端与服务器之间的连接

解读Redis秒杀优化方案(阻塞队列+基于Stream流的消息队列)

《解读Redis秒杀优化方案(阻塞队列+基于Stream流的消息队列)》该文章介绍了使用Redis的阻塞队列和Stream流的消息队列来优化秒杀系统的方案,通过将秒杀流程拆分为两条流水线,使用Redi... 目录Redis秒杀优化方案(阻塞队列+Stream流的消息队列)什么是消息队列?消费者组的工作方式每

Java CompletableFuture如何实现超时功能

《JavaCompletableFuture如何实现超时功能》:本文主要介绍实现超时功能的基本思路以及CompletableFuture(之后简称CF)是如何通过代码实现超时功能的,需要的... 目录基本思路CompletableFuture 的实现1. 基本实现流程2. 静态条件分析3. 内存泄露 bug

C#使用HttpClient进行Post请求出现超时问题的解决及优化

《C#使用HttpClient进行Post请求出现超时问题的解决及优化》最近我的控制台程序发现有时候总是出现请求超时等问题,通常好几分钟最多只有3-4个请求,在使用apipost发现并发10个5分钟也... 目录优化结论单例HttpClient连接池耗尽和并发并发异步最终优化后优化结论我直接上优化结论吧,

Springboot使用RabbitMQ实现关闭超时订单(示例详解)

《Springboot使用RabbitMQ实现关闭超时订单(示例详解)》介绍了如何在SpringBoot项目中使用RabbitMQ实现订单的延时处理和超时关闭,通过配置RabbitMQ的交换机、队列和... 目录1.maven中引入rabbitmq的依赖:2.application.yml中进行rabbit