面经:服务器相关

阻塞IO

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

非阻塞IO

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

Level_triggered(水平触发)

当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据一次性全部读写完(如读写缓冲区太小),那么下次调用 epoll_wait()时,它还会通知你在上没读写完的文件描述符上继续读写,当然如果你一直不去读写,它会一直通知你!!!如果系统中有大量你不需要读写的就绪文件描述符,而它们每次都会返回,这样会大大降低处理程序检索自己关心的就绪文件描述符的效率!!!

LT的缺点

LT模式下,可写状态的fd会一直触发事件,该怎么处理这个问题

方法1:每次要写数据时,将fd绑定EPOLLOUT事件,写完后将fd同EPOLLOUT从epoll中移除。

方法2:方法一中每次写数据都要操作epoll。如果数据量很少,socket很容易将数据发送出去。可以考虑改成:数据量很少时直接send,数据量很多时在采用方法1
为什么ET模式下一定要设置非阻塞?

因为ET模式下是无限循环读,直到出现错误为EAGAIN或者EWOULDBLOCK,这两个错误表示socket为空,不用再读了,然后就停止循环了,如果是阻塞,循环读在socket为空的时候就会阻塞到那里,主线程的read()函数一旦阻塞住,当再有其他监听事件过来就没办法读了,给其他事情造成了影响,所以必须要设置为非阻塞。
最大TCP连接数是多少?

理论上是等于 客户端IP数✖客户端的端口数,即2的32次方✖2的16次方即2的48次方,但是实际上受到文件描述符数量限制和内存空间限制。

Edge_triggered(边缘触发)

当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用epoll_wait()时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知你!!!这种模式比水平触发效率高,系统不会充斥大量你不关心的就绪文件描述符!!!
所以ET所以循环处理,保证能将数据读取完毕,即同时要保证非阻塞IO,不然最后会被阻塞

Reactor

主线程往epoll内核上注册socket读事件,主线程调用epoll_wait等待socket上有数据可读,当socket上有数据可读的时候,主线程把socket可读事件放入请求队列。睡眠在请求队列上的某个工作线程被唤醒,处理客户请求,然后往epoll内核上注册socket写请求事件。主线程调用epoll_wait等待写请求事件,当有事件可写的时候,主线程把socket可写事件放入请求队列。睡眠在请求队列上的工作线程被唤醒,处理客户请求。

Proactor

主线程调用aio_read函数向内核注册socket上的读完成事件,并告诉内核用户读缓冲区的位置,以及读完成后如何通知应用程序,主线程继续处理其他逻辑,当socket上的数据被读入用户缓冲区后,通过信号告知应用程序数据已经可以使用。应用程序预先定义好的信号处理函数选择一个工作线程来处理客户请求。工作线程处理完客户请求之后调用aio_write函数向内核注册socket写完成事件,并告诉内核写缓冲区的位置,以及写完成时如何通知应用程序。主线程处理其他逻辑。当用户缓存区的数据被写入socket之后内核向应用程序发送一个信号,以通知应用程序数据已经发送完毕。应用程序预先定义的数据处理函数就会完成工作。

reactor模式和Proactor模式对比

reactor模式:同步阻塞I/O模式,注册对应读写事件处理器,等待事件发生进而调用事件处理器处理事件。 proactor模式:异步I/O模式。

Reactor和Proactor模式的主要区别就是真正的读取和写入操作是有谁来完成的,Reactor中需要应用程序自己读取或者写入数据,Proactor模式中,应用程序不需要进行实际读写过程。

Reactor:非阻塞同步网络模型,可以理解为:来了事件我通知你,你来处理

Proactor:异步网络模型,可以理解为:来了事件我来处理,处理完了我通知你。

理论上:Proactor比Reactor效率要高一些。

webserver相关

并发模型

面向对象的Reactor模型,和面向过程的程序不同,面向对象的程序耦合度更低,可以将每一个行为分离开来,形成一个个事物,如果某一个事物阻塞了或者出现了异常可以及时切换到其他事物中,不会阻塞住整个系统,muduo网络库,Redis和Nginx都使用了Reactor模式。通过多线程或者多进程技术,提高了系统的效率。本系统采用多线程提高并发度,因为多线程粒度更小切换的消耗更低,性能更好。使用线程池是为避免线程频繁创建和销毁带来的开销,在程序的开始创建固定数量的线程,线程数量和计算机内存的数量保持一致可以有较好的CPU利用率,通常IO多的程序开更多的线程,CPU操作多的程序开更少的线程。使用Epoll作为IO多路复用的实现方式,为了调试方便在mac使用了Kqueue作为实现方式。
一个主Reactor主要用来处理accept的连接并负责分配client的连接请求给副Reactor,它是一种面向对象的IO复用模式。在建立连接后用轮询的方式分配给工作线程,因为涉及到多线程的任务分配会有竞争问题,可以使用eventfd或者条件变量实现异步唤醒工作线程,线程会从Epoll_wait中醒来,获取从主线程中获取活跃连接进行处理,主线程会删除这个连接,这也类似于生产者消费者模型,主线程处理新的连接,等第二次来数据的连接时唤醒消费者线程处理,本系统这里的互斥锁由某个特定线程中loop创建,不会出现惊群的情况只会被该线程和主线程中使用。

定时任务功能实现

服务器程序通常管理着众多定时事件,因此有效地组织这些定时事件,使之能在预期的时间点被触发且不影响服务器的主要逻辑,对于服务器的性能有着至关重要的影响。为此,我们要将每个定时事件分别封装成定时器,并使用某种容器类数据结构,比如链表、排序链表和时间轮,将所有定时器串联起来,以实现对定时事件的统一管理。本项目是为了方便释放那些超时的非活动连接,关闭被占用的文件描述符,才使用定时器。
每个副Reactor持有一个定时器,用于处理超时请求和长时间不活跃的连接。定时器可以使用时间轮,红黑树,双向链表和小根堆实现,我使用了使用了C++标准库中的priority_queue优先队列,它底层是堆,通过惰性删除的方法提高效率,因为在时间片到期的时候并不会马上删除超时节点,而是每次连接通信结束的时候循环的检查,会将超时的节点全部删除,由于小根堆的特点越早超时的节点越在堆的上方,检查时间队列的间隔和频率减少了,效率就会提高。

Epoll边缘模式

Epoll的触发模式在这里我选择了ET模式,muduo使用的是LT,这两者IO处理上有很大的不同。ET模式要比LE复杂许多,它对用户提出了更高的要求,即每次读,必须读到不能再读直到出现EAGAIN,每次写,写到不能再写知道出现EAGAIN。而LT则简单的多,可以选择也这样做,也可以为编程方便,比如每次只read一次,muduo就是这样做的,这样可以减少系统调用次数。

线程池模块

线程池是使用了已经创建好的线程进行循环处理任务,避免了大量线程的频繁创建与销毁的成本。
实现思想:利用生产者消费者队列,创建多个线程并全部初始化去运行,通过条件变量判断队列中是否有任务,没有任务就等待,当有任务时就可以通过条件变量来唤醒阻塞中的线程去处理这个任务。
代码实现:类主要有两个类,一个类是任务类,一个类是线程池类,其中任务类有两个成员变量,一个是数据和处理数据方式的一个函数指针,成员函数有两个,一个是用于接收任务和数据的处理方式的函数,一个是run函数,其执行这个处理数据的函数。线程池类中的成员变量主要有线程池的最大数量、一个缓冲队列、一个互斥锁,用来保护对队列的操作、一个条件变量,用于实现线程池中线程的同步。threadpool_create是线程的的入口函数,每个线程都在一个死循环里等待任务,当有任务到来,就可以获取队列中的任务对象,然后去执行任务对象中的回调函数来处理数据。

核心模块

Channel类:Channel和一个 EventLoop绑定是一种事物,在Channel类管理一个fd文件描述符,存储这个事件的数据类型event以及对应的函数,当事件活跃的时候会调用到之前保存在类中的函数。因此,程序中所有带有读写时间的对象都会和一个Channel关联,包括loop中的eventfd,listenfd,HttpData等。
EventLoop:One loop per thread意味着每个线程只能有一个EventLoop对象,EventLoop即是时间循环,每次从poller里拿活跃事件,并给到Channel里分发处理。EventLoop中的loop函数会在最底层Thread中被真正调用,开始无限的循环,直到某一轮的检查到退出状态后从底层一层一层的退出。

建立连接

建立连接的时候服务器端首先使用socket()创建套接字,其次使用bind()将如IPv4绑定到套接字,最后调用listen()监听连接,使用Epoll IO复用的ET边缘模式监听listenfd的读请求,数据的通信已经由操作系统帮我们完成了,这里的通信是指3次握手的过程,这个过程不需要应用程序参与,当应用程序感知到连接时,此时该连接已经完成了3次握手的过程,accept()会在收到最后ACK文件后放回。另一个原因是一般情况下,连接是客户端主动发起,服务器端被动连接,也不会出现同时建立的情况。检查这个连接的fd描述符,如果是第一次建立连接则会讲这个描述符加入Epoll的红黑数中。第二次同一个连接活跃的时候就可以讲这个fd加入Epoll的就绪队列中,使用ET模式会比LT电平模式麻烦,与LT不同的是,LT模式当有活跃事物的时候会不停的触发提醒直到处理完这个事物,而ET模式的活跃事物只会提醒一次,因此开发的时候要无限循环读直到就绪连接为空。
假设server只监听一个端口,一个连接就是一个四元组原ip,原port,对端ip,对端port,那么理论上可以建立2^48个连接,可是,fd可没有这么多这是由于操作系统限制和用户进程限制实际上主要首先于内存,可以通过Linux中的终端命令ulimit临时修改fd数量,通过vim /etc/security/limits.conf文件中修改永久的fd数量。为了避免连接满了无法处理新的连接,新的连接会阻塞在connect()上,防止空等,在第一次客户端发起半连接包后就不在处理的DOS攻击,避免就绪队列满了后会导致新连接无法建立。本系统采取的方案是参考muduo网络库,准备一个空的文件描述符,限制最大连接数,不要在连接满了才进行处理,达到一定数量的连接后,accept()阻塞返回后直接close(),这样对端不会收到RST,客户端可以知道服务器还存活着而不是宕机。

优雅关闭

通常server和client都可以主动发Fin来关闭连接,这是TCP的特点全双工连接。对于client(非Keep-Alive),发送完请求后就可以半关闭写端,这个时候就是告诉客户端服务器端不在写数据但是可以继续读数据,这也是符合TCP协议的逻辑,然后收到server发来的应答后读空read(),最后关闭连接。也可以不使用shutdown()半关闭写端,等读完直接close()。对于Keep-Alive长连接的情况,需要观察客户端的行为,服务器端应该保证不主动断开主动权掌握在客户端手里。

共享内存

使用mmap加速内核与用户空间的消息传递。无论是select,poll还是epoll都需要内核把FD消息通知给用户空间,如何避免不必要的内存拷贝就很重要,在这点上,epoll是通过内核于用户空间mmap同一块内存实现的。(mmap 内存共享 少一份拷贝吧)

epoll开发相关

1、单个epoll并不能解决所有问题,特别是你的每个操作都比较费时的时候,因为epoll是串行处理的。 所以你有还是必要建立线程池来发挥更大的效能。
2、如果fd被注册到两个epoll中时,如果有时间发生则两个epoll都会触发事件。
3、如果注册到epoll中的fd被关闭,则其会自动被清除出epoll监听列表。
4、如果多个事件同时触发epoll,则多个事件会被联合在一起返回。
5、epoll_wait会一直监听epollhup事件发生,所以其不需要添加到events中。
6、为了避免大数据量io时,et模式下只处理一个fd,其他fd被饿死的情况发生。linux建议可以在fd联系到的结构中增加ready位,然后epoll_wait触发事件之后仅将其置位为ready模式,然后在下边轮询ready fd列表
ET模式仅当状态发生变化的时候才获得通知,这里所谓的状态的变化并不包括缓冲区中还有未处理的数据,也就是说,如果要采用ET模式,需要一直read/write直到出错为止,很多人反映为什么采用ET模式只接收了一部分数据就再也得不到通知了,大多因为这样;而LT模式是只要有数据没有处理就会一直通知下去的.

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

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

相关文章

如何用区块链保障数据安全和承载数据确权

区块链可以确保数据安全,体现在那些方面呢? 主要是两个维度,一是数据的不可篡改性;另外一个就是数据的隐私安全性。区块链技术本身并不解决任何的安全问题,因此需要搭配安全技术一起使用,比如非对称加密、…

面经:单例模式

侯捷单例 和剑指不同  饿汉式 饿汉式的特点是一开始就加载了,如果说懒汉式是“时间换空间”,那么饿汉式就是“空间换时间”,因为一开始就创建了实例,所以每次用到的之后直接返回就好了。饿汉式有两种常见的写法&…

属性加密技术及基于属性的ABE算法的访问控制技术介绍

属性加密技术 基于身份的加密体制简介 基于身份的加密体制可以看作一种特殊的公钥加密,它有如下特点:系统中用户的公钥可以由任意的字符串组成。这些字符串可以是用户在现实中的身份信息,如:身份证号码、用户姓名、电话号码、Email地址等,因…

面经:http协议

总结HTTPS传输过程 客户端先从服务器获取到证书,证书中包含公钥 客户端将证书进行校验 客户端生成一个对称密钥,用证书中的公钥进行加密,发送给服务器 服务器得到这个请求后用私钥进行解密,得到该密钥 客户端以后发出后续的请求&…

基于属性加密的ABE算法的应用场景思考展望

ABE算法先前使用在云计算场景中,和区块链存在交叉应用场景,具体问题体现在 数据的异地存储、云服务器提供商的不可信、管理员能否对自身数据拥有足够的控制能力以及如何保证数据的安全有效共享都是亟需解决的问题。 研究背景: 云计算越来越…

面经:设计模式

什么是接口隔离原则(Interface Segregation Principle) 定义:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。概括的说就是:建立单一接口,不要建立臃肿庞大的接口。&…

区块链、密码和银行之间的衍生关系

银行场景中密码服务 设置密码 用户在注册的时候,如果使用弱密码,系统会检测出来。我的猜测是将弱密码的hash运算和用户输入的密码hash比对,如果一致,禁止用户注册。 1、不要设置简单密码,您设置的密码必须符合中信银…

面经:多线程 线程池

使用线程池 当进程被初始化后,主线程就被创建了。对于绝大多数的应用程序来说,通常仅要求有一个主线程,但也可以在进程内创建多个顺序执行流,这些顺序执行流就是线程,每一个线程都是独立的。 线程是进程的组成部分&am…

AIgorand区块链中VRF随机函数的应用

VRF(Verifiable Random Function) 可验证随机函数可以看作是一个随机预言机,即可以通过任意的一个输入,获得一个随机数输出:输出的结果(Output)是一个随机数,其数值会均匀分布在值域…

AIgorand的相关学习参考链接

相关具体的开发者与SDK链接如下: GoSDKJavaScript SDK 网页链接 测试网申请链接Github存储库链接开发者网址AIgorand官网Telegram电报群综合白皮书MediumNaver Blog领英Linkedin区块链浏览器INC公示钱包地址基金会公示钱包地址Telegram电报群官方 Github地址 相关…

操作系统 内核栈

视频哈工大李治军老师:https://www.bilibili.com/video/BV1d4411v7u7?p12 参考文档:https://blog.csdn.net/SakuraA6/article/details/108810916 学长在我大一推荐我看,p12和p13的内容真的有那么难吗,现在已经是我看的第三遍了还…

区块链技术指南 序章理解感悟

序二 误区一: 区块链是一种颠覆性的新技术。区块链不是一个新的技术,而是一个新的技术的组合。其关键的技术,包括P2P动态组网、基于密码学的共享账本、共识机制(拜占庭将军问题,分布式场景下的一致性问题&#xff09…

面经:红黑树 B树 B+树 哈希表

1.对于插入,删除,查找 以及 输出有序序列 这几个操作,红黑树也可以完成,时间复杂度 与 用跳表实现是相同的。 但是,对于按照区间查找数据这个操作(比如 [20,300]),红黑树的效率没有跳表高&#…

回溯法和dfs的区别

值得注意,回溯法以深度优先搜索的方式搜索解空间,并且在搜索过程中用剪枝函数避免无效搜索。那为何 回溯算法 深度优先搜索 剪枝函数这一说法没有错? 因为树是特殊的图。简单来说,树是广义的图。再简单来说,树是图。…

C++学习笔记 简单部分

C 数据类型 使用变量来存储各种信息,变量保留的是它所存储的值的内存位置。这意味着,当创建一个变量时,就会在内存中保留一些空间。这段内存空间可以用于存储各种数据类型(比如字符型、宽字符型、整型、浮点型、双浮点型、布尔型…

Redis kqeue相关源码

mask 或delmask :添加或者删除的事件类型,AE_NONE表示没有任何事件;AE_READABLE表示可读事件;AE_WRITABLE表示可写事件; 如aeCreateFileEvent(loop,e->fd,AE_READABLE,redisAeReadEvent,e); static int aeApiAddEv…

C++学习笔记章节中 面向对象详解

C 类&对象 C类定义 本质上是一个数据类型的蓝图,定义了类的对象包含的信息,以及可以在这个类对象上执行哪些操作。类的定义是以class开头,后面接类的名称。类的主体是包含在一个花括号中,类的定义之后,必须跟着一…

Mac 破解软件打不开没有权限

Mac 破解软件打不开没有权限 sudo codesign -fs - /Applications/CleanMyMac\ X.app文件损坏 xxx sudo xattr -r -d /Applications/MarginNote\ 3.app sudo xattr -r -d com.apple.quarantine xxxx sudo codesign --force --deep --sign - /Applications/MarginNote\ 3\…

条件变量之虚假唤醒

当线程从等待已发出信号的条件变量中醒来,却发现它等待的条件不满足时,就会发生虚假唤醒。之所以称为虚假,是因为该线程似乎无缘无故地被唤醒了。但是虚假唤醒不会无缘无故发生:它们通常是因为在发出条件变量信号和等待线程最终运…

拷贝构造函数和拷贝赋节省代码最好用一个私有的函数

令 copy assignment操作符调用copy构造函数是不合理的,因为这就像试图构造一个已经存在的对象。这件事如此荒涔,乃至于根本没有相关语法。是有一些看似如你所愿的语法,但其实不是;也的确有些语法背后真正做了它,但它们…