近四十场面试汇聚成的超全Web服务器面经总结

本文是作者秋招面试经历总结的Web服务器面经,涵盖项目细节如端口复用、多路IO复用(epoll+EPOLLONESHOT)、线程池与reactor模式的实现。文章讨论了常见的面试知识点,如优先队列、五种IO模型(阻塞、非阻塞、IO多路复用、信号驱动、异步IO)以及阻塞与非阻塞、同步与异步的区别。此外,还详细解释了epoll的LT和ET模式,并对比了epoll、poll、select的优劣。文中还介绍了Reactor和Proactor事件处理模式,以及线程池的设计与优化策略,最后探讨了高并发场景下的处理策略和常见面试延伸问题。
摘要由CSDN通过智能技术生成

上期写了简历项目链接简历项目烂大街怎么办?教你最有谱的摆烂,有位读者照做以后,拿下了主管面,在群里宣传以后,最近多了不少小伙伴来催我更新服务器项目相关知识点。

这份总结是我之前秋招的时候,根据每次面试的问题,不断查漏补缺总结而成,迭代了很多次。每次遇到新的问题,自己在网上边查边总结,当时主要是自己看嘛,也没有什么版权问题,但是现在要发在微信公众号这个公开平台,就需要追本溯源的查一查当初内容来源,尊重原作者的成果。我会尽量确认出处,如有侵权烦请告知!

说在前面的话

每个人服务器项目实现的功能不同,可以延伸的方向也会不同,第一节关于项目本身细节问题仅限于个人改进后的项目,大家只需参考与自己重合的内容即可。

面试官的问题千奇百怪,通常问你的时候不太可能就像下文标题一样,非常精准的问你某某知识点。通常可能会抛出一些笼统的问题,这个时候就需要你快速定位一下,他到底感兴趣的是什么。所以我们需要以不变应万变,引导面试官到你准备好的内容上面来。

同时需要强调一点, 学无止境,本篇内容只能算是抛砖引玉,给大家一些借鉴,并不能止步于此,大家还是要继续做一些更深更细的学习~

当然我也知道,这只是我个人的面试问题,不一定非常全面,后面我会开源本篇笔记,如果后面有同学被问到了关于这个项目新的问题,可以更新上来,一同维护!

一、项目细节

项目介绍

这个项目主要的目的是对浏览器的链接请求进行解析处理,处理完之后给浏览器客户端返回一个响应,如文字图片视频等。服务器后端的处理方式使用socket通信,利用多路IO复用,可以同时处理多个请求,请求的解析使用预先准备好的线程池,使用reactor模式,主线程负责监听IO,获取io请求后把请求对象放入请求队列,交给工作线程。睡眠在请求队列上的工作线程被唤醒进行数据读取以及逻辑处理。利用==状态机==思想解析 Http 报文,支持 GET/POST 请求,支持长/短连接;

使用基于小根堆的定时器关闭超时请求,解决超时,连接系统资源占用问题。

架构概述

alt
主线程:

  1. 在主线程中,epoll监听套接字, 处理就绪套接字上的外部IO事件,包括已连接客户的写请求(发送报文),或者新客户的连接请求。

  2. 将就绪IO套接字发过来的请求封装成一个requestData对象,对象里面包括了就绪文件描述符是哪一个,发过来的报文数据是啥,这些数据的处理函数是啥等等。

  3. 并且设置requestData中的timer为NULL,也就是处理完一个requestData就delete掉,默认是短连接。如果报文解析到长连接,则会在后面补上timer。然后将requestData放在线程池的任务队列里面等待工作线程的处理。

  4. 主线程还有一个while循环,利用定时器堆管理定时器结点,删除超时事件。

工作线程:

  1. 工作线程采用条件变量和锁的形式从任务队列里面取任务,用requestData自己的处理函数分析http报文,发送http响应。

  2. 如果分析到报文里面有keep-alive选项,则requestData不会销毁,而是用清空的方式保留。

  3. keep-alive长连接不是永久保留的,而是设置了一个定时器(也就是keep-alive中的timer成员)超时,超出时间以后,就把他关闭掉,这里超时时间设定为500ms。

  4. 然后就把它加入到定时器最小堆中

  5. 最后由于一开始fd是epolloneshot模式,还需要再epoll ctl设置一下fd的状态,使得他可以再次被监听。

其中线程池中工作线程取用任务队列里面的任务,在工作线程调用requestData中的handleRequest进行使用状态机解析了HTTP请求

alt

屏蔽掉SIGPIPE信号

在整个epoll监听循环开始之前,需要先屏蔽掉SIGPIPE信号。
默认读写一个关闭的socket会触发sigpipe信号。该信号的默认操作是关闭进程,这明显是我们不想要的。所以我们需要重新设置sigpipe的信号回调操作函数,比如忽略操作等,可以防止调用它的默认操作。

//处理sigpipe信号
void handle_for_sigpipe(){
     struct sigaction sa; //信号处理结构体
     memset(&sa, '\0', sizeof(sa));
     sa.sa_handler = SIG_IGN;//设置信号的处理回调函数 这个SIG_IGN宏代表的操作就是忽略该信号 
     sa.sa_flags = 0;
     if(sigaction(SIGPIPE, &sa, NULL))//将信号和信号的处理结构体绑定
       return;
}

端口复用

用setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)消除bind时"Address already in use"错误,即设置SO_REUSEADDR 端口复用

epoll监管和EPOLLONESHOT

epoll监管套接字的时候用边沿触发+EPOLLONESHOT+非阻塞IO

EPOLLONESHOT事件

即使可以使用边缘触发模式,一个socket上的某个时间还是可能被触发多次。比如一个线程在读取完某个socket上的数据后开始处理这些数据,而在数据的处理过程中,socket上又有了新数据可以读(EPOLLIN再次被触发),此时另外一个线程被唤醒来读取这些新的数据。就会出现两个线程同时操作一个socket的局面。一个socket连接在任意时刻都只被一个线程处理,可以使用epoll EPOLLONESHOT实现。

对于注册了EPOLLONESHOT事件的文件描述符有,操作系统最多出发其注册的一个刻度、可写或异常事件,且只触发一次除非我们使用epoll_ctl函数重置该文件描述符上注册的EPOLLONESHOT事件。

这样一个线程在处理某个socket时,其他线程是不可能有机会操作该socket,但反过来要注意,注册了EPOLLONESHOT事件的socket一旦被某个线程处理完毕,该线程就应该立即重置socket上的EPOLLONESHOT事件以确保这个socket下一次可读时,其EPOLLIN事件能被触发,进而可以让其他线程有几回处理这个socket。

多线程和线程池

使用多线程充分利用多核CPU,并使用线程池避免线程频繁创建、销毁加大系统开销。

  • 创建一个线程池来管理多线程,线程池中主要包含任务队列工作线程集合,将任务添加到队列中,然后在创建线程后,自动启动这些任务。使用了一个固定线程数的工作线程,限制线程最大并发数。
  • 多个线程共享任务队列,所以需要进行线程间同步,工作线程之间对任务队列的竞争采用条件变量互斥锁结合使用
  • 一个工作线程先加互斥锁,当任务队列中任务数量为0时候,阻塞在条件变量,当任务数量大于0时候,用条件变量通知阻塞在条件变量下的线程,这些线程来继续竞争获取任务
  • 对任务队列中任务的调度采用先来先服务算法

线程池的线程数量最直接的限制因素是CPU处理器的个数。

如果CPU是四核的,那么对于CPU密集的任务,线程池的线程数量最好也为4,或者+1防止其他因素导致阻塞。

如果是IO密集的任务,一般要多于CPU的核数,因为线程间竞争的不是CPU资源而是IO,IO的处理一般比较慢,多于核数的线程将为CPU争取更多的任务,不至于在县城处理IO的时候造成CPU空闲导致资源浪费。

解析HTTP请求

  1. 采用reactor事件处理模式,主线程只负责监听IO,获取io请求后把请求对象放入请求队列,交给工作线程,工作线程负责数据读取以及逻辑处理。

    proactor模式将所有IO读写操作 都交给主线程和内核来处理,工作线程仅仅负责业务逻辑。

  2. 在主线程循环监听到读写套接字有报文传过来以后,在工作线程调用requestData中的handleRequest进行使用状态机解析了HTTP请求

    • http报文解析和报文响应 解析过程状态机如上图所示。

      在一趟循环过程中,状态机先read一个数据包,然后根据当前状态变量判断如何处理该数据包。当数据包处理完之后,状态机通过给当前状态变量传递目标状态值来实现状态转移。那么当状态机进行下一趟循环时,将执行新的状态对应的逻辑。

    • </
  • 49
    点赞
  • 310
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值