热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

关于网络编程中的一些小问题研究总结

关于网络编程中的一些小问题研究总结前言一、关于“惊群问题”二、关于socket网络编程中的reuseport三、关于select、poll、epoll的原理探究3.1关于selec


关于网络编程中的一些小问题研究总结

  • 前言
  • 一、关于“惊群问题”
  • 二、关于socket网络编程中的reuseport
  • 三、关于select、poll、epoll的原理探究
    • 3.1 关于select 的基本原理
    • 3.2 关于poll的基本原理
    • 3.3 关于epoll的基本原理
  • 四、关于socket api中,内核的工作与用户空间的工作讨论。
  • 参考


前言

原本打算继续往下总结第五个并发模型来着,但是觉得有些不妥。一是因为真正进入到Reactor并发模式,理解的还不是很深,总觉的缺点什么;二是自己原本实现的示例代码写的也不是很好,不是很成一定的style。 所以想着先一般总结一些前面遇到的比较碎的知识点,然后一边研究【1】中的网络库实现,模仿一个简单的网络库,倒是在继续往下总结Reactor部分。

这里先探究几个小问题:
(1)、关于“惊群问题”。
(2)、关于socket网络编程中的reuseport。
(3)、关于select、poll、epoll的原理探究。
(4)、关于socket api中,内核的工作与用户空间的工作讨论。
(5)、…

下面一个一个进行探究。




一、关于“惊群问题”

  惊群问题在以前的博客中也有提及,主要的原因就是多个进程或者线程在阻塞等待一个共同的资源,当资源可以获得之时,这些进程或者线程被一起唤醒所造成的的资源浪费、性能下降的现象(主要的浪费在于操作系统把所有的进程唤醒之后,大部分进程无法获得资源,只能重新回到阻塞队列当中,这其中会有上下文的白白的切换)。

对于惊群现象来说,它是一个广义的概念,在多线程、多进程编程中很多情况下都会遇到。 特别的在网络编程中,常见的有两种惊群现象,一个是accept惊群,另一个是IO复用的惊群现象(selec,poll,epoll等)。 下面这篇文章讲解的很好,我就不班门弄斧了。^ _ ^


Linux惊群效应详解(最详细的了吧)

这里说一个我的小疑问:在IO复用的惊群问题底层实现中,是如何做到唤醒“部分”阻塞的进程的?【1】中的说法是事件在一部分进程唤醒之前已经处理了,所以内核不会在唤醒剩下的进程。 enen…, 说得通,但是按照我目前对操作系统“唤醒”的理解,其是在条件达成之时,将阻塞在条件上的所有进程都移到就绪队列上(如果只阻塞在一个条件之上的时候)。
  非常希望有了解的小伙伴能给我讲讲.


二、关于socket网络编程中的reuseport

对于上面介绍的"accept惊群"现象,linux内核在3.9版本之后推出了另外一种解决方式-REUSEPORT。

“accept”惊群是多个进程或者线程同时阻塞在同一个监听套接字上(listenFd),当有连接到来之时,内核会唤醒阻塞的进程或线程们。linux 2.6版内核之后,内核会唤醒其中的某一个进程或线程。 本质上来说,这是现代内核帮我们解决的一个方案,避免了加锁带来的消耗。

这里说的reuseport, 是内核以另外一种形式帮我们解决“accept惊群问题”的方案(可能还有其他的作用,这里暂且不讨论了)。 socket 如果设置成了reuseport的选项,那么它就可以和其他的socket(它们也需要设置为reuseport选项) 共同绑定到同一个端口,具体来说是同一个addr+port 。 再具体点说,内核会把来自客户端的请求(请求的目的是addr+port)均衡的发到这些socketX 上。 如下示意图所示。

在这里插入图片描述
上图摘自【2】,详细的说明可以参照【2】。


enen… 基本内容介绍完了,但是稍微深挖一点的话,这儿还有几点不太理解的:
(1)、为什么内核已经解决了“accept”惊群的问题,还需要弄一个reuseport呢?
(2)、内核的这两种对“惊群”问题的解决方案有什么区别?

这些留待以后慢慢研究吧。


三、关于select、poll、epoll的原理探究

IO复用机制算是一个比较“牛”的机制,因为它给了并发编程提供了一种新的思路。

long long ago, 实现的服务器端模型大都是迭代式服务器,一个服务完成了,才可以进行下一个服务。

然后是多进程并发服务器,但是多进程服务器有两个基本的问题,一个是开销比较大(包括生成、销毁、切换等),另一个是通信问题,进程间的通信有点麻烦。

线程模型出来之后,有些并发服务器开始使用线程来代替进程,使用多线程模型来进行服务器端并发模型的开发,每个线程监控一个服务(客户端连接或者其他形式的IO)。多线程模型也有其固有的问题,一个是受限于并发连接数受限于系统的最大线程数量(一般内核都有最大的线程数量限制),另一个是线程之间通过共享进程空间的方式来通信(共享数据)会有互斥、同步等问题(包括编程难度和加锁、解锁时的消耗)(一旦陷入到死锁问题,很容易就让人崩溃,^ _ ^|||)。

后来,不知哪位大佬就创造性的开发出了IO复用机制,在一个进程或者线程中可以同时阻塞等待多个IO(多个客户端连接或者其他形式的IO请求),当其中的某一个或者某几个IO有事件到来之时(可读、可写或者异常)才会返回通知上层的用户。 以这种方式从某种程度上大大提高了服务器端的并发量。

所以,后来以IO复用为基础,现在比较流行的IO并发模型框架就是Reactor模型 。


上面说了IO复用的由来和基本概念,下面主要介总结介绍一下linux系统下select、poll、和epoll的基本实现原理。


3.1 关于select 的基本原理

按照我比较抽象、比较大的方面理解:select的实现主要就是以下几个步骤:

=》select 进入

(1)、用户提供所关注的fd_set(其中包括每个fd所感兴趣的事件:可读、可写、异常等)
(2)、内核将fd_set从用户空间拷贝到内核空间(为什么需要拷贝请参照【4】)
(3)、遍历fd_set,将当前进程(current)挂到不同fd的等待队列中去。
(4)、如果没有感兴趣的fd事件发生,则进程处于阻塞状态(这里不考虑schdule_timeout 超时和信号到来被唤醒)。当感兴趣的事件到来之时,会唤醒阻塞的进程。
(5)、被唤醒的进程重新遍历fd_set中的事件,将准备好的fd收集起来,最后拷贝到用户空间(返回的事整个文件描述符数组,里面并不是都是就绪的IO,需要用户代码自行判断)

=》select 返回

以上是我比较粗矿的理解,有很多措辞和说法可能不是很准确,请见谅。


比较详细的讨论可以参照【3】。

为了帮助理解,我从【5】那儿也盗了张图 ^ _ ^|||。

在这里插入图片描述



下面说说select这种机制的一些缺点:


  • 由于使用位图来存储fd 以及一些内核本身的设置,select 最多支持1024个fd.
  • 每次调用select进入内核或者从内核返回之时需要进行用户空间和内核空间的fd_set的传递,如果fd数量比较大的话,开销也会不小。
  • 在select 内部实现时,有一个大循环( for(; ; ) ),每次唤醒之后都需要遍历所有的fd_set集合(线性扫描,复杂度是O(N)),开销应该也是“杠杠的”。
  • 从本质上来说,select返回到用户空间的是整个文件描述符数组,需要用户代码遍历数组以找出哪些文件描述符上有IO事件,



3.2 关于poll的基本原理

对于poll来说,其基本原理和上面的select差不多,区别就是poll机制的fd描述采用了链表的形式(select是bitmap)这样支持的最大文件描述符数量就不止1024个了。



3.3 关于epoll的基本原理


对于epoll来说,它基本解决了上面select的几个缺点。 它的基本原理,简要来说可以用下面这张图来说明(⁄(⁄ ⁄•⁄ω⁄•⁄ ⁄)⁄ 也是从【5】中“偷” 过来的)。

在这里插入图片描述

具体的详细介绍请参阅【3】、【5】【6】。 下面主要以epoll如何解决select上面的几个缺点为切入点进行总结。

(1)、对于select 支持的fd数量有限制。
  epoll中使用红黑树(如上图中红色圈起来的部分)来存储感兴趣的IO,理论上fd的数量没有限制(可能受限于硬件条件如内存等,这里不考虑)


(2)、对于每次调用select 进入内核和从内核返回时需要进行fd_set的传递拷贝。
   epoll机制中含有一个epoll_ctl 系统调用,只有在第一次插入fd(本质上插入的是感兴趣的事件,在内核中被封装成了epitem)时,会进行拷贝,以后的每一次epoll_wait( epoll机制的阻塞调用),都不会重新将fd从用户空间拷贝到内核去。
   同时,内核和用户空间还共享了一片内存,用来存放epoll_wait返回的时候所带回来的有IO到来的fd_set。 这样也就避免了从内核空间向用户空间复制数据的开销


(3)、关于select 每次有感兴趣的事件到来时(进程被唤醒)都需要进行重新进行线性扫描的“尴尬”。
  epoll机制中有两个比较重要的数据结构,一个是红黑树(如上图红色圈起来的部分),它用来存放用户注册的感兴趣的事件。 另一个是就绪队列(上图中蓝色圈起来的部分),这个是存放感兴趣的事件中已经就绪的IO, ( epoll_wait( )就是从这个队列中读取数据的。 )

  那为什么epoll不需要每次的线性扫描呢? 因为在有IO事件到来之时,其注册的回调函数(在epoll_ctl()中注册)会把事件添加到就绪队列中,这样epoll_wait()就不用向select 内部的do_select()函数那样,还需要重新遍历所有的文件描述符。 因此达到了O(1)的复杂度。

(4)、关于select最终返回的事真个文件描述符数组的问题。
   在epoll的实现中,因为有了就绪队列(不是进程就绪的那个队列啊)这个数据结构,所有epoll_wait( ) 本质上就是等待这个就绪队列里面有数据,然后从这个队列里扒拉数据给用户空间(其实是共享内存啦),最终给的数据都是“货真价实”的IO请求,绝不让用户代码再费力判断。


关于IO复用的原理总结部分,就先到这儿吧,原来还打算正儿八经的看看源码来着,但是到时正儿八经的被源码“抽”了个耳光子。 有点小难,虽然大体也看了一点,但很多细节的地方还是没怎么看懂。自然,也有一些源码的精华部分,没有体会到:比如说,“有人说,epoll机制中最重要的是回调机制,类似于事件处理那种 是? ” 胃不好,一口吃不成个胖子,这些还需要以后再慢慢体会啊。 ^ _ ^


四、关于socket api中,内核的工作与用户空间的工作讨论。

最后这一部分主要来扯扯内核工作和用户空间的工作。

一般来说,对于非内核开发者(操作系统)来说,我们大都算是app 开发者(只是细分的层不一样,框架类系统类可能更靠appd的底层,java web业务应用可能更靠app的上层)。我们最终都需要调用内核给我们提供的各种服务(可以简要理解为系统调用),所以本质上说内核和应用在一起构成了我们一整套“应用”(可以从用户空间和内核空间来理解)。

在我们调用系统api时,比如说网络编程中经常遇到的socket api,内核在底部帮我们做了很多事,比如说在connect/accept 时,内核中的网络协议栈帮我们自动完成了三次握手等连接过程。 write(或者read)时,上层用户只是把想要传输的数据写到了内核缓冲区(确切的说这一步也是内核帮我们完成的,我们只是简单的调用了write 这个系统调用),然后由内核帮我们按照TCP的协议把数据传输到远端。

设想这样一个场景,有两个服务器进程A和B, 它们以相同的并发模型方式向外提供服务。A进程连接了10个客户,B进程连接了100个客户,那么问题来了,一般普通情况下(不要较真,这里说的是一般普通的情况,A和B有相同的环境,CPU还算比较空闲,…),在给定的一段时间内,A和B谁占用的内核时间长,或者说内核作为一种服务(连接阻塞、读写阻塞等)为谁服务的时间长?

用脚趾头都能想到,肯定是B啦,在一定时间范围内,内核作为一个勤劳的“奶妈”,它给B喂的奶肯定较多些。但是这里要稍微注意一下,如何A和B如果分到的时间片相同的话,B进程内核占用的时间较多,那么其用户的时间片占据的就少些。同理,A进程内核占用的时间片较少,它就有较多的时间片执行用户态的任务。 (当然,总体来说,B进程应该还占得一些异步中断的便宜,因为异步中断时不考虑进程的时间片)

以上是我根据自己的理解总结出来的,如果有什么不妥的地方,还希望各位小伙伴可以指正。^ _ ^|||。




参考

【1】、Linux惊群效应详解(最详细的了吧)
【2】、不可不知的socket和TCP连接过程
【3】、select/poll/epoll原理探究及总结
【4】、epoll原理剖析#2: select & poll
【5】、select、poll、epoll的原理与区别
【6】、epoll原理剖析#3: epoll


推荐阅读
  • Android工程师面试准备及设计模式使用场景
    本文介绍了Android工程师面试准备的经验,包括面试流程和重点准备内容。同时,还介绍了建造者模式的使用场景,以及在Android开发中的具体应用。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • LeetCode笔记:剑指Offer 41. 数据流中的中位数(Java、堆、优先队列、知识点)
    本文介绍了LeetCode剑指Offer 41题的解题思路和代码实现,主要涉及了Java中的优先队列和堆排序的知识点。优先队列是Queue接口的实现,可以对其中的元素进行排序,采用小顶堆的方式进行排序。本文还介绍了Java中queue的offer、poll、add、remove、element、peek等方法的区别和用法。 ... [详细]
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • javascript  – 概述在Firefox上无法正常工作
    我试图提出一些自定义大纲,以达到一些Web可访问性建议.但我不能用Firefox制作.这就是它在Chrome上的外观:而那个图标实际上是一个锚点.在Firefox上,它只概述了整个 ... [详细]
  • 本文回顾了3.21开学以来的学习情况,包括javaWeb课程的迷糊感和未预习导致的不知所措,以及对VOJ题目的归类和解答。午饭前完成了阶乘相关的两道题目。下午的数据结构课听懂了队列的讲解,但有几个疑问未能及时复习。设计模式课程因预习效率低而感到困惑,同时也没搞清楚下节课的内容。晚上去图书馆学习。通过反思和总结,对自己的学习收获有了更深刻的认识。 ... [详细]
  • Google Play推出全新的应用内评价API,帮助开发者获取更多优质用户反馈。用户每天在Google Play上发表数百万条评论,这有助于开发者了解用户喜好和改进需求。开发者可以选择在适当的时间请求用户撰写评论,以获得全面而有用的反馈。全新应用内评价功能让用户无需返回应用详情页面即可发表评论,提升用户体验。 ... [详细]
  • Tomcat/Jetty为何选择扩展线程池而不是使用JDK原生线程池?
    本文探讨了Tomcat和Jetty选择扩展线程池而不是使用JDK原生线程池的原因。通过比较IO密集型任务和CPU密集型任务的特点,解释了为何Tomcat和Jetty需要扩展线程池来提高并发度和任务处理速度。同时,介绍了JDK原生线程池的工作流程。 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • This article discusses the efficiency of using char str[] and char *str and whether there is any reason to prefer one over the other. It explains the difference between the two and provides an example to illustrate their usage. ... [详细]
  • 本文介绍了操作系统的定义和功能,包括操作系统的本质、用户界面以及系统调用的分类。同时还介绍了进程和线程的区别,包括进程和线程的定义和作用。 ... [详细]
author-avatar
手机用户26536338_53
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有