socket的阻塞模式和非阻塞模式_socket的怎么设置非阻塞模式-程序员宅基地

技术标签: Linux网络编程基础  网络  linux  tcp/ip  

socket的阻塞模式和非阻塞模式

如何将socket设置为非阻塞模式

无论是Windows还是Linux,默认创建的socket都是阻塞模式的。

在linux上,我们可以使用fcntl函数或者ioctl函数给创建的socket增加O_NONBLOCK标志来将socket设置为非阻塞模式

int oldSocketFlag = fcntl(sockfd, F_GETFL, 0);
int newSocketFlag = oldSocketFlag | O_NONBLOCK;
fcntl(sockfd, F_SETFL, newSocketFlag);

Linux上的socket函数也可以直接在创建时将socket设置为非阻塞式,socket函数签名如下:

int socket(int domain, int type, int protocol);

type参数增加一个SOCK_NONBLOCK标志即可,例如:

int s = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP);

不仅如此,在Linux上利用accept函数返回的代表与客户端通信的socket也提供了一个扩展函数accept4,直接将accept函数返回的socket设置为非阻塞的;

int accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen);
int accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen, int flags);

只要将accept4函数的最后一个参数flags设置为SOCK_NONBLOCK即可。

以下代码等价。

socklen_t addrlen = sizeof(clientaddr);
int clientfd = accept4(listenfd, &clientaddr, &addrlen, SOCK_NONBLOCK);

socklen_t addrlen = sizeof(clientaddr);
int clientfd = accept(listenfd,&clientaddr,&addrlen);
if(clientfd != -1)
{
    
    int oldSocketFlag = fcntl(clientfd, F_GETFL, 0);
    int newSocketFlag = oldSocketFlag | O_NONBLOCK;
    fcntl(clientfd, F_SETFL, newSocketFlag);
}

send和recv函数在阻塞和非阻塞模式下的表现

send函数在本质上并不是向网络上发送数据,而是将应用层发送缓冲区的数据拷贝到内核缓冲区中,至于数据什么时候会从网卡缓冲区中真正地发到网络中,要根据TCP/IP协议栈的行为来确定。如果socket设置了TCP_NODELAY选项(即禁用nagel算法)。存放到内核缓冲区的数据就会被立即发送出去,反之,一次放入内核缓冲区的数据包如果太小,则系统会在多个小的数据包凑成一个足够大的数据包之后才会将数据发送出去。

recv函数在本质上并不是从网络上收取数据,而是将内核缓冲区中的数据拷贝到应用程序的缓冲区中。在拷贝完成后会将内核缓冲区中的该部分数据移除

在这里插入图片描述

通过上图可以知道,不同的程序在进行网络通信时,发送的一方会将内核缓冲区的数据通过网络传输给接收方的内核缓冲区。在应用程序A与应用程序B建立连接之后,假设应用程序A不断调用send函数,则数据会不断拷贝至对应的内核缓冲区中,如果应用程序B一直不调用recv函数,那么在应用程序B的内核缓冲区被填满之后,应用程序A的内核缓冲区也会被填满,此时应用程序A继续调用send函数会发生什么结果?具体的结果取决于socket是否是阻塞模式,这里先给出结论。

  1. socket是阻塞模式时,继续调用send/recv函数,程序会阻塞在send/recv调用处;
  2. socket是非阻塞模式时,继续调用send/recv函数,send/recv不会阻塞程序执行流,而是立即出错返回,我们会得到一个相关的错误码,在Linux上该错误码为EWOULDBLOCKEAGAIN

非阻塞模式下send和recv函数的返回值总结

返回值n 返回值的含义
大于0 成功发送send或接收recvn字节
0 对端关闭连接
小于0 出错,被信号中断,对端TCP窗口太小导致数据发送不出去或者当前网卡缓冲区已无数据可接收

这三种情况:

  1. 返回值大于0。当send/recv函数的返回值大于0时,表示发送或接收多少字节。需要注意的是,在这种情况下,我们一定要判断send函数的返回值是不是我们期望发送的字节数,而不是简单判断其返回值大于0。举个例子
int n = send(socket, buf, buf_length, 0);
if(n > 0)
{
    
    printf("send data successful");
}

虽然返回值n大于0,但在实际情况中,由于对端的TCP窗口可能因为缺少一部分字节就满了,所以n的值可能为 ( 0 , b u f _ l e n g t h ] (0, buf\_length] (0,buf_length]。当 0 < n < b u f _ l e n g t h 0 < n < buf\_length 0<n<buf_length​​时,虽然此时send函数调用成功,但在业务上并不算正确,因为有部分数据并没有被发送出去。我们可能在一次测试中测不出n小于buf_length的情况,但不代表实际上不存在​​。所以,建议要么在返回值n等于buf_length时才认为正确,要么在一个循环中调用send函数,如果数据一次性发送不完,则记录偏移量,下一次从偏移量处接着发送,直到全部发送完为止。

//不推荐的方式
int n = send(socket, buf, buf_length, 0);
if(n == buf_length)
{
    
    printf("send data successfully\n");
}

//推荐的方式
bool SendData(const char* buf, int buf_length)
{
    
    //已经发送的字节数
    int sent_bytes = 0;
    int ret = 0;
    while(true)
    {
    
        ret = send(m_hSocket, buf + sent_bytes, buf_length - sent_bytes, 0);
        if(ret == -1)
        {
    
            if(errno == EWOULDBLOCK)
            {
    
                //严谨的做法:如果发送不出去,则应该缓存尚未发送出去的数据
                break;
            }
            else if(errno == EINTR)
                continue;
            else
                return false;
        }
        else if(ret == 0)
            return false;
        sent_bytes += ret;
        if(sent_bytes == buf_length)
            break;
    }
    return true;
}
  1. 返回值等于0。在通常情况下,如果sendrecv函数返回0,我们就认为对端关闭了连接,我们这端也关闭连接即可。send函数主动发送0字节时也会返回0,这是一种特例
  2. 返回值小于0。对于sendrecv函数返回值小于0的情况(即返回-1),此时并不表示send或者recv函数一定调用出错。

下表表示的是非阻塞模式下,socketsendrecv返回值,对于阻塞模式下的socket,如果返回值为-1,则一定表示出错。

返回值和错误码 send函数 recv函数
返回-1,错误码是EWOULDBLOCK或EAGAIN TCP窗口太小,数据暂时发送不出去 在当前缓冲区中无可读数据
返回-1,错误码是EINTR 被信号中断,需要重试 被信号中断,需要重试
返回-1,错误码不是以上3种 出错 出错

阻塞与非阻塞socket的各自使用场景

阻塞的socket函数在调用sendrecvconnectaccept等函数时,如果特定的条件不满足,就会阻塞其调用线程直至超时,非阻塞的socket恰恰相反。

非阻塞模式一般用于需要支持高并发多QPS的场景(如服务器程序),但是正如前文所述,这种模式让程序的执行流和控制逻辑变得复杂;相反,阻塞模式逻辑简单,程序结构简单明了,常用于一些特殊场景中。

应用场景一:某程序需要临时发送一个文件,文件分段发送,每发送一段,对端都会给予一个响应,该程序可以单独开一个任务线程,在这个任务线程函数里面,使用先sendrecvsendrecv的模式,每次sendrecv都是阻塞模式的。

应用场景二:A端与B端之间的通信只有问答模式,即A端每发送给B端一个请求,B端比定会给A端一个响应,除此之外,B端不会向A端推送任何数据,此时A端就可以采用阻塞模式,在每次send完请求后,都可以直接使用阻塞式的recv函数接收应答包。

发送给B端一个请求,B端比定会给A端一个响应,除此之外,B端不会向A端推送任何数据,此时A端就可以采用阻塞模式,在每次send完请求后,都可以直接使用阻塞式的recv函数接收应答包。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_36553387/article/details/119144470

智能推荐

Oracle数据库中instr()与substr()函数详解_oracle instr substr-程序员宅基地

文章浏览阅读3.4k次,点赞2次,收藏12次。instr()函数(俗称:字符查找函数)格式1:instr( string1, string2 ) 格式1解释 :instr(源字符串, 目标字符串)格式2:instr( string1, string2, start_position, nth_appearance) 格式2解释:instr(源字符串, 目标字符串, 起始位置, 匹配序号)实例格式1实例select instr('helloworld','l') from table--返回结果:3 默认第..._oracle instr substr

第五章 相似矩阵及二次型_相似矩阵及二次型总结-程序员宅基地

文章浏览阅读798次。线性代数同济版 第五章 相似矩阵及二次型 易错题和总结_相似矩阵及二次型总结

c# OpenCvSharp 检测(斑点检测、边缘检测、轮廓检测)(五)-程序员宅基地

文章浏览阅读4k次,点赞28次,收藏53次。在C#中使用OpenCV进行图像处理时,可以使用不同的算法和函数来实现斑点检测、边缘检测和轮廓检测。_c# opencv

web自动化测试之登录_web自动化测试登录-程序员宅基地

文章浏览阅读2.4k次。1. 登录代码一共有4条用例,其中前3条是执行成功的,最后一条执行失败import unittestfrom selenium import webdriverimport timeclass TestLogin(unittest.TestCase): def setUp(self): self.driver = webdriver.Firefox() self.driver.maximize_window() self.driver._web自动化测试登录

如何使用光盘启动计算机,电脑如何设置光驱启动 电脑设置光驱启动方法【图文】...-程序员宅基地

文章浏览阅读2.5k次。在我们所使用的电子网络工具中,其中电脑是最受人们的喜爱的,而用过电脑的人都会了解一下电脑光驱的功能,并且会需要知道光驱启动的设置是如何做到的,小编今天的文章就跟电脑光驱相关联,既然很多用户都想知道电脑光驱如何设置启动,小编今天的文章就来教会大家,电脑如何设计光驱启动的方法,其实这些方法都非常的简单,如何简单呢?就看小编的文章来告诉大家吧。电脑要设置光驱启动,那么首先人们就要想到在blos中设置,小..._电脑如何设置光驱启动

OSPF多区域配置与心得理解_多区域ospf配置-程序员宅基地

文章浏览阅读1.8w次,点赞11次,收藏81次。ospf 多区域文章目录ospf 多区域实验背景实验需求1.配置ip地址2.配置OSPF3查看R1,R4的邻居表4.强制发布一跳默认路由查看R1有没有收到默认路由5.在R3上使用静默接口测试PC ping外网6.6.6.66.查看路由实验背景实验需求1.配置IP地址2.完整配置ospf多区域3.查看邻居关系4.强制发布默认路由5.在R3的G0/0/1口配置静默接口6.验证路由表..._多区域ospf配置

随便推点

Windows上启用NTP服务器功能_windows开启ntp服务-程序员宅基地

文章浏览阅读1.5w次,点赞15次,收藏50次。Windows 10本身是可以作NTP时间同步服务器的,无需安装其它软件,只需要修改一些配置。_windows开启ntp服务

python函数_py如何为gender制定默认值为男-程序员宅基地

文章浏览阅读1k次。函数(多行代码整合)一、函数定义和调用1.1.函数的定义函数定义:定义函数的格式如下:def 函数名(): 代码​ 格式说明如下:​ (1)def是英文define的缩写​ (2)函数名称应该能够表达函数封装代码的功能,方便后续的调用(函数 中的代码不会执行,在函数调用时,才会执行)​ (3)函数名称的命名应该符合标识符的命名规则​ 。可以由字母、下划线和数字组成​ 。不能以数字开头​ 。不能与关键字重名_py如何为gender制定默认值为男

python pip install 报出(Retrying (Retry(total=4, connect=None,...)的解决办法_pip安装opencv一直retrying-程序员宅基地

文章浏览阅读758次。今天通过python 的pip 下载opencv-python pip install opencv-python报错然后换成了另外一种 去下载了对应的whl文件pip install opencv_python-3.2.0.6-cp36-cp36m-win_amd64.whl结果搞了半天,老出现(Retrying (Retry(total=4, connect=None, read=None, redirect=None, status=None)))然后通过查看大家的处理发现 是国外的p_pip安装opencv一直retrying

Linux cron定时介绍_/etc/cron.hourly-程序员宅基地

文章浏览阅读2.5k次。定时任务的使用场景非常广泛,比如定时发送邮件,定时清理日志等等,在持续集成中,可以定时的触发测试任务,比如希望在每天晚上下班时间执行自动化用例。本文主要介绍Linux cron定时任务的构建方法。_/etc/cron.hourly

windows远程提示credssp加密数据库修正问题解决_credssp加密数据库修正 远程桌面-程序员宅基地

文章浏览阅读8.4k次,点赞5次,收藏7次。背景:今天登录公司远程服务器的时候报如下错误(如下图所示),百度查了一下各种方法最终解决了,记录一下过程,防止以后遇到同样的问题需要再找,也可以为其它遇到同样问题的人提供帮助。步骤一. 修改注册表我先按照我们运维给我的一个链接进行修改,里面介绍说微软官方 2018 年 5 月更新了凭据安全支持提供程序协议(CredSSP)相关补丁和身份验证请求方式。当出现以下任一配置策略时会出现该连接错误:配置策略一:客户端的策略为未修补,服务器端策略为强制更新的客户端。配置策略二:客户端策略为强制更新的客户端_credssp加密数据库修正 远程桌面

SpringBoot + MyBatis + Sharding-JDBC4.X 实现数据库读写分离_springboot mybatis shardingjdbc读写分离-程序员宅基地

文章浏览阅读560次。1、说明,本文主要实现1主2从的读写分离主库: 添加、更新以及删除数据操作所使用的数据库。从库: 查询数据操作所使用的数据库。2、引用依赖包 <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.42<_springboot mybatis shardingjdbc读写分离