epoll与socket缓冲区的恩恩怨怨

news2024/12/27 11:57:19

文章目录

  • 前言
  • 一、什么是socket缓冲区
  • 二、阻塞与非阻塞内核缓冲区
    • 1、如果发送缓冲区满了会怎么样
      • 阻塞
      • 非阻塞
    • 2、如果接受缓冲区为空会怎么样
      • 阻塞
      • 非阻塞
  • 三、epoll与缓冲区的恩恩怨怨
    • 水平触发
    • 边缘触发
      • 非阻塞
      • 阻塞
    • 结论


前言

本文深挖网络编程中的缓冲区,从什么是缓冲区出发,然后分析epoll 两个模式使用阻塞与非阻塞缓冲区的区别,

一、什么是socket缓冲区

TCP三次握手成功,TCP连接成功建立后,操作系统内核会为每个连接创建配套的基础设施,包括 发送缓冲区,其大小可通过套接字选项改变。程序调用write函数时,实际所做的事是把数据从应用程序中拷贝到操作系统内核中。既然是写给操作系统,那操作系统就需要提供一个地方给用户写。同理,接收消息也是一样。这个地方就是 socket 缓冲区。

也就是说一个socket ,会带有两个缓冲区,一个用于发送,一个用于接收。因为这是个先进先出的结构,有时候也叫它们发送、接收队列。在这里插入图片描述

二、阻塞与非阻塞内核缓冲区

使用TCP建立连接之后,一般会使用 send 发送数据。执行 send 之后,数据只是拷贝到了socket 缓冲区。至 什么时候会发数据,发多少数据,全听操作系统安排。根据实际情况(比如拥塞窗口等)判断是否要发数据。如果不发送数据,那么此时直接返回

阻塞IO:当你去读一个阻塞的文件描述符时,如果在该文件描述符上没有数据可读,那么它会一直阻塞(通俗一点就是一直卡在调用函数那里),直到有数据可读。当你去写一个阻塞的文件描述符时,如果在该文件描述符上没有空间(通常是缓冲区)可写,那么它会一直阻塞,直到有空间可写。以上的读和写我们统一指在某个文件描述符进行的操作,不单单指真正的读数据,写数据,还包括接收连接accept(),发起连接connect()等操作…

非阻塞IO:当你去读写一个非阻塞的文件描述符时,不管可不可以读写,它都会立即返回,返回成功说明读写操作完成了,返回失败会设置相应errno状态码,根据这个errno可以进一步执行其他处理。它不会像阻塞IO那样,卡在那里不动!!

1、如果发送缓冲区满了会怎么样

发送缓冲区有足够的空间,可以用于拷贝待发送数据。如果发送缓冲区空间不足,或者满了,执行发送,会怎么样?这里分两种情况。

阻塞

如果此时 socket 是阻塞的,那么程序会在那干等、死等,直到释放出新的缓存空间,就继续把数据拷进去,然后返回。在这里插入图片描述

非阻塞

如果此时 socket 是非阻塞的,程序就会立刻返回一个 EAGAIN 错误信息,意思是 Try again , 现在缓冲区满了,你也别等了,待会再试一次。在这里插入图片描述

2、如果接受缓冲区为空会怎么样

阻塞

如果此时 socket 是阻塞的,那么程序会在那干等,直到接收缓冲区有数据,就会把数据从接收缓冲区拷贝到用户缓冲区,然后返回。在这里插入图片描述

非阻塞

如果此时 socket 是非阻塞的,程序就会立刻返回一个 EAGAIN 错误信息。在这里插入图片描述

三、epoll与缓冲区的恩恩怨怨

我们都知道epoll()模型即支持水平触发,也支持边缘触发,默认是水平触发。
Level_triggered(水平触发):当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据一次性全部读写完(如读写缓冲区太小),那么下次调用 epoll_wait()时,它还会通知你在上没读写完的文件描述符上继续读写,当然如果你一直不去读写,它会一直通知你!!!如果系统中有大量你不需要读写的就绪文件描述符,而它们每次都会返回,这样会大大降低处理程序检索自己关心的就绪文件描述符的效率!!!
Edge_triggered(边缘触发):当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用epoll_wait()时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知你!!!这种模式比水平触发效率高,系统不会充斥大量你不关心的就绪文件描述符!!

首先明确read函数以及recv函数分别在阻塞和非阻塞状态的时候他们对缓冲区有没有数据的反应

 ret = recv(sockfd, buffer, MAX_BUFFER_SIZE, 0);

在非阻塞状态下:
1、如果没有数据可读,并且没有发生错误,recv() 返回值为 -1,同时 errno 被设置为 EAGAIN 或 EWOULDBLOCK。这表示当前没有可读数据,并且需要稍后再次尝试读取。
2、如果连接已被关闭(对方已关闭连接),recv() 返回值为 0,表示读取到的数据长度为 0,这意味着连接已经结束。
3、如果发生其他错误,recv() 返回值为 -1,并且 errno 被设置为相应的错误代码,例如 ECONNRESET 表示连接被重置,ETIMEDOUT 表示连接超时等。
在阻塞的状态下
如果是阻塞模式,read() 函数或 recv() 函数在没有数据可读时会一直阻塞,直到有数据可读或者出现错误。在此期间,程序会一直等待,并且不会执行其他操作。如果连接被关闭或者发生错误,read() 或 recv() 函数会立即返回相应的错误代码

水平触发

在水平触发的情况下,设置非阻塞与阻塞他们的效果都是一样的。不过为了防止特殊情况,还是建议设置非阻塞。
个人认为,因为在水平触发的情况下,缓冲区有数据就会通知处理程序去读写。如果这次没有把数据一次性全部读写完(如读写缓冲区太小),那么下次调用 epoll_wait()时,它还会通知你在上没读写完的文件描述符上继续读写。那么水平触发就保证每次缓冲区都是有数据的,就不会出现阻塞connfd遇到缓冲区为空的情况了。

  /* LT处理流程 */
void epoll_lt(int sockfd){
     char buffer[MAX_BUFFER_SIZE];
     int ret;
     
     memset(buffer, 0, MAX_BUFFER_SIZE);
     printf("开始recv()...\n");
     ret = recv(sockfd, buffer, MAX_BUFFER_SIZE, 0);
     printf("ret = %d\n", ret);
     if (ret > 0)
         printf("收到消息:%s, 共%d个字节\n", buffer, ret);
     else{
         if (ret == 0)
             printf("客户端主动关闭!!!\n");
         close(sockfd);
     }
  
     printf("LT处理结束!!!\n");
 }
  

实验
设置应用缓冲区大小为5,编译运行后,用一个客户端连接,并发送1-9这几个数:
在这里插入图片描述
再看服务器的反映,可以看到水平触发触发了2次。因为我们代码里面设置的缓冲区是5字节,处理代码一次接收不完,水平触发一直触发,直到数据全部读取完毕:
在这里插入图片描述

边缘触发

由于边缘触发只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知。所以要用循环来处理。


  /* 带循环的ET处理流程 */
void epoll_et_loop(int sockfd){
     char buffer[MAX_BUFFER_SIZE];
     int ret;
  
     printf("带循环的ET读取数据开始...\n");
     while (1){
         memset(buffer, 0, MAX_BUFFER_SIZE);
         ret = recv(sockfd, buffer, MAX_BUFFER_SIZE, 0);
         if (ret == -1){
            // 非阻塞的时候 没有东西读了 就会返回-1
             if (errno == EAGAIN || errno == EWOULDBLOCK){
                 printf("循环读完所有数据!!!\n");
                break;
             }
             close(sockfd);
             break;
          }
          else if (ret == 0){
             printf("客户端主动关闭请求!!!\n");
             close(sockfd);
             break;
         }
         else
             printf("收到消息:%s, 共%d个字节\n", buffer, ret);
     }
     printf("带循环的ET处理结束!!!\n");
}
 

非阻塞

在非阻塞的情况下,epoll_wait()一次提醒进入循环之后,会不断地读取一定长度的数据,知道套接字上没有新的数据到达时,recv() 函数会立即返回 -1,并且退出。
用一个客户端连接,并发送1-9。再观测服务器的反映,可以看到数据全部读取完毕,处理函数也退出了,因为非阻塞IO如果没有数据可读时,会立即返回,并设置error,这里我们根据EAGAIN和EWOULDBLOCK来判断数据全部读取完毕了,可以退出循环了:在这里插入图片描述

阻塞

用一个客户端连接,发送1-9。看看服务器,可以看到数据全部读取完毕:
在这里插入图片描述
程序没有输出"带循环的ET处理结束",是因为程序一直卡在了recv()函数上,因为是阻塞IO,如果没数据可读,它会一直等在那里,直到有数据可读。如果这个时候,用另一个客户端去连接,服务器不能受理这个新的客户端!!

结论

所以ET模式下必须设为非阻塞模式。循环读取的时候,如果缓冲区没有数据或者低于水位线,recv/read就会阻塞等待读事件就绪,这会影响到epoll模型中其他文件描述符的操作。

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

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

相关文章

排序:基数排序算法分析

1.算法思想 假设长度为n的线性表中每个结点aj的关键字由d元组 ( k j d − 1 , k j d − 2 , k j d − 3 , . . . , k j 1 , k j 0 ) (k_{j}^{d-1},k_{j}^{d-2},k_{j}^{d-3},... ,k_{j}^{1} ,k_{j}^{0}) (kjd−1​,kjd−2​,kjd−3​,...,kj1​,kj0​)组成, 其中&am…

微信小程序开发基础(一)认识小程序

微信小程序,小程序的一种,英文名Wechat Mini Program,是一种不需要下载安装即可使用的应用,它实现了应用“触手可及”的梦想,用户扫一扫或搜一下即可打开应用。微信小程序是一种不用下载就能使用的应用,也是…

排序:外部排序算法分析

1.外存与内存之间的数据交换 1.外存(磁盘) 操作系统以“块”为单位对磁盘存储空间进行管理,如:每块大小1KB 各个磁盘块内存放着各种各样的数据。 2.内存 磁盘的读/写以“块”为单位数据读入内存后才能被修改修改完了还要写回磁盘。 2.外…

Purple-Pi-OH OHOS SDK编译手册

一、源码获取 1.1 源码获取 链接:百度网盘 请输入提取码 提取码:1234 $ mkdir purple-pi #将下载的ido_purple_pi_oh_ohos3.2_sdk.tgz拷贝到purple-pi $ cd purple-pi $ md5sum ido_purple_pi_oh_ohos3.2_sdk.tgz e6ca2d96aa7c628992ae0bbf4d14c2ca …

面试买书复习就能进大厂?

大家好,我是苍何。 现在进大仓是越来越难了,想通过简单的刷题面试背书,比几年前难的不少, 但也并非毫无希望,那究竟该如何准备才能有希望进大厂呢? 我总结了 4 点: 1、不差的学历背景 2、丰富…

EcmaScript标准-导入与导出-js

ECMAScript是一种由Ecma国际(前身为欧洲计算机制造商协会,European Computer Manufacturers Association)通过ECMA-262标准化的脚本程序设计语言。这种语言在万维网上应用广泛,它往往被称为JavaScript或JScript,所以它…

lwIP 开发指南(下)

目录 NETCONN 编程接口简介netbuf 数据缓冲区netconn 连接结构netconn 编程API 函数 NETCONN 编程接口UDP 实验NETCONN 实现UDPNETCONN 接口的UDP 实验硬件设计软件设计下载验证 NETCONN 接口编程TCP 客户端实验NETCONN 实现TCP 客户端连接步骤NETCONN 接口的TCPClient 实验硬件…

九、Delay函数

1、两个延时函数 vTaskDelay:至少等待指定个数的Tick Interrupt才能变为就绪态。vTaskDelayUntil:等待到指定的绝对时刻,才能变为就绪态。 2、函数原型 /* xTicksToDelay: 等待多少个Tick */ void vTaskDelay( const TickType_t xTicksToD…

1.(vue3.x+vite)封装组件

前端技术社区总目录(订阅之前请先查看该博客) 关联博客 2.(vue3.x+vite)组件注册并调用 1:创建组件目录package,并创建相关工程结构 2:编写组件内容(index.vue) 3:添加注册组件方法(index.js) 4:添加路由

QT按钮介绍

目录 按钮基类 QAbstractButton QPushButton QToolButton QRadioButton QCheckBox 按钮基类 QAbstractButton 这是按钮的基类,它是继承QWidget类 它可对当前的图标,标题等进行设置。 它有自己的一些信号与槽函数: /* 当按钮被激活时(即…

【C++的OpenCV】第十三课-OpenCV基础强化(一):绝对有用!Mat相关的一系列知识(基础->进阶)

🎉🎉🎉 欢迎各位来到小白 p i a o 的学习空间! \color{red}{欢迎各位来到小白piao的学习空间!} 欢迎各位来到小白piao的学习空间!🎉🎉🎉 💖💖&…

rhel8 网络操作学习

一、查询dns服务器地址汇总 1.查询dns服务器地址: (1)方法一:执行命令 cat /etc/resolv.conf 执行结果如下: nameserver后面就是dns服务器的ip地址。 (2)方法2:查看/etc/syscon…

Linux性能优化--性能工具-系统CPU

2.0.概述 本章概述了系统级的Linux性能工具。这些工具是你追踪性能问题时的第一道防线。 它们能展示整个系统的性能情况和哪些部分表现不好。 1.理解系统级性能的基本指标,包括CPU的使用情况。 2.明白哪些工具可以检索这些系统级性能指标。2.1CPU性能统计信息 为了…

基于单片机的煤气泄漏检测报警装置设计

一、项目介绍 煤气泄漏是一种常见的危险情况,可能导致火灾、爆炸和人员伤亡。为了及时发现煤气泄漏并采取相应的安全措施,设计了一种基于单片机的煤气泄漏检测报警装置。 主控芯片采用STM32F103C8T6作为主控芯片,具有强大的计算和控制能力。…

《PPT 自我介绍》:一本让你的职场表现更加出色的秘籍?

这里提供一个2000字左右的PPT自我介绍模板制作指南: 自我介绍是面试或工作中常见的情况,利用PPT可以给人留下更深刻的印象。但如何快速且专业地制作一个自我介绍PPT呢?这里给大家介绍几点技巧: 1. 选择一个简洁大方的PPT模板 首先要选择一…

最新AI创作系统源码ChatGPT源码+附详细搭建部署教程+AI绘画系统+支持国内AI提问模型

一、AI系统介绍 SparkAi创作系统是基于国外很火的ChatGPT进行开发的Ai智能问答系统。本期针对源码系统整体测试下来非常完美,可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如何搭建部署AI创作ChatGPT?小编这里写一个详细图文教程吧&am…

中秋海报制作不求人,详细教程来袭

中秋节是我国传统的节日,也是家庭团聚的重要时刻。在节日即将到来之际,如何设计一张温馨、美丽的中秋节海报,让家庭成员感受到节日的氛围和温暖呢?下面就为大家分享一些中秋节海报设计的秘诀。 一、选择合适的模版 登录乔拓云后&a…

NSSCTF做题(4)

[NISACTF 2022]checkin 简单的一道代码审计了 但是发现传参传不上去 后来发现 在选中nisactf的时候,注释里面的内容也被标记了 不知道是为什么,把它复制到010里边去看看 发现了不对的地方 nisactf应该传参 根据这个进行url编码 我们选择实际的参名和…

让文件传输变得更简单高效——推荐强大的Mac FTP客户端Transmit 5

无论是个人用户还是专业人士,文件传输都是我们日常工作中不可或缺的一部分。而针对Mac用户,Transmit 5正是一款强大且易用的FTP客户端,为您提供了最佳的文件传输体验。 Transmit 5在功能上非常丰富,可以满足各种文件传输需求。首…

uboot启动流程涉及reset函数

一. uboot启动流程中函数 之前了解了uboot链接脚本文件 u-boot.lds。 从 u-boot.lds 中我们已经知道了入口点是 arch/arm/lib/vectors.S 文件中的 _start。 本文了解 一下,uboot启动过程中涉及的 reset 函数。本文继上一篇文章学习,地址如下&#xff…