【牛客网linux服务器开发项目】面试八股问答
[1]一次网页的访问从URL开始,说一下整个访问的过程
客户端获取URL - > DNS解析 - > TCP连接 - >发送HTTP请求 - >服务器处理请求 - >返回报文 - >浏览器解析渲染页面 - > TCP断开连接
[2]DNS
用户访问网页,DNS服务器(域名解析系统)会根据用户提供的域名查找对应的IP地址域名解析服务器是基于UDP实协议实现的一个应用程序,通常通过监听53端口来获取客户端的域名解析请求DNS查找过过程如下:浏览器缓存、系统缓存、路由器缓存、DNs服务器解析
[3]HTTP请求报文
是由三部分组成: 请求行, 请求头和请求体。
请求行: GET index.html HTTP/1.1 请求⽅法、URL、协议版本常用的方法有: GET, POST, PUT, DELETE, OPTIONS, HEAD。
常见的请求报头有: Accept, Accept-Charset, Accept-Encoding, Accept-Language, Content-Type, Authorization, Cookie, User-Agent等
[4]响应报文
HTTP响应报文也是由三部分组成: 响应行, 响应头和响应体。响应行:协议版本、状态码 描述 HTTP/1.1 200 ok常见的响应报头字段有: Server, Connection响应报文:服务器返回给浏览器的文本信息,通常HTML, CSS, JS, 图片等文件就放在这一部分。Web服务器有Tomcat, Jetty和Netty等等。
[5]状态码
是由3位数组成,第一个数字定义了响应的类别,且有五种可能取值:
1xx 【消息】服务器收到请求,需要请求者继续执行操作 2xx 【成功】请求已成功被服务器接收、理解、并接受。 3xx 【重定向】客户端需要采取进一步的操作以完成请求 4xx 【客户端请求错误】客户端错误,请求包含语法错误或无法完成请求 5xx 【服务器错误】服务器在处理请求的过程中发生了错误
平时遇到比较常见的状态码有:200, 204, 301, 302, 304, 400, 401, 403, 404, 422, 500等:
200 OK //客户端请求成功 304 Not Modified // 未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源 400 Bad Request //客户端请求有语法错误,不能被服务器所理解 401 Unauthorized // 当前请求要求用户的身份认证 403 Forbidden // 服务器理解请求客户端的请求,但是拒绝执行此请求 404 Not Found //请求资源不存在,输入了错误的URL 500 Internal Server Error //服务器发生不可预期的错误 503 Server Unavailable // 由于超载或系统维护,服务器暂时的无法处理客户端的请求。一段时间后可能恢复正常
301和·302区别:301为永久重定向,302为临时重定向
301永久重定向指的是旧地址A的资源已经被永久地移除了(这个资源不可访问了),搜索引擎在抓取新内容的同时也将旧的网址交换为重定向之后的网址。
302临时重定向指的是旧地址A的资源还在(仍然可以访问),这个重定向只是临时地从旧地址A跳转到地址B,搜索引擎会抓取新的内容而保存旧的网址。
[6]视频⾯试⽤到哪些协议(DNS,HTTP,HTTPS,TCP,UDP),UDP⽤在哪⾥?
将TCP和UDP的特性互相结合起来,让这个协议既能够保证可靠性,又能够保证明实时性,这也就是咱们所说的RUDP((Reliable UDP),常见的RUDP协议有QUIC,WebRTC,Aeron等等
[7]UDP如何实现可靠传输,上层应用怎么实现
UDP不属于连接协议,具有资源消耗少,处理速度快的优点,传输层无法保证数据的可靠传输,只能通过应用层来实现了。实现的方式可以参照tcp可靠性传输的方式,只是实现不在传输层,实现转移到了应用层
最简单的方式是在应用层模仿传输层TCP的可靠性传输:
• 1、添加seq/ack机制,确保数据发送到对端
• 2、添加发送和接收缓冲区
• 3、添加超时重传机制。详细说明:发送端发送数据时,生成一个随机seq=x,然后每一片按照数据大小分配seq。数据到达接收端后接收端放入缓存,并发送一个ack=x的包,表示对方已经收到了数据。发送端收到了ack包后,删除缓冲区对应的数据。时间到后,定时任务检查是否需要重传数据
三种使用UDP进行可靠数据传输的协议:RUDP、RTP、UDT
[8]常见安全漏洞
代码注入、会话固定、路径访问、弱密码
[9]栈(Stack)溢出漏洞:
一方面因为程序员的疏忽,使用了 strcpy、sprintf 等不安全的函数,增加了栈溢出漏洞的可能。另一方面,因为栈上保存了函数的返回地址等信息
[10]xss攻击、cors攻击原理
跨站脚本攻击(XSS),是最普遍的Web应用安全漏洞。这类漏洞能够使得攻击者嵌入恶意脚本代码到正常用户会访问到的页面中,当正常用户访问该页面时,则可导致嵌入的恶意脚本代码的执行,从而达到恶意攻击用户的目的。
CORS全名跨域资源共享(Cross-Origin-Resourece-sharing),该机制主要是解决浏览器同源策略所带来的不便,使不同域的应用能够无视同源策略,进行信息传递。
[11]Get和post区别
1、get请求一般用来请求获取数据。不对服务器产生影响
post请求一般向服务器提交数据,影响服务器
2、get请求也可以传参到后台,但是传递的参数则显示在地址栏,安全性低,且参数的长度也有限制(2048字符)
post请求则是将传递的参数放在request body中,不会在地址栏显示,安全性比get请求高,参数没有长度限制
3、get请求刷新浏览器或者回退没有影响
post请求则会重新请求一遍
4、get请求可以被缓存,也会保留在浏览器的历史记录中
post请求不会被缓存,也不好保留在浏览器的历史记录中
5、get请求通常是通过url地址请求
post常见的则是form表单请求
6、get安全幂等,post不安全不幂等 7、GET⽅法只产⽣⼀个TCP数据包,浏览器会把请求头和请求数据⼀并发送出去,服务器响 应200 ok(返回数据) POST会产⽣两个TCP数据包,浏览器会先将请求头发送给服务器,待服务器响应100 continue,浏览器再发送请求数据,服务 器响应200 ok(返回数据)
[12]安全+幂等:
安全:HTTP协议中,安全是指请求⽅法不会破坏服务器上的资源幂等:多次执⾏相同的操作,结果都相同
[13]RAII机制
/RAII机制//RAII机制,通过在栈上创建临时变量,这样临时变量就接管了堆上内存的控制权,当该临时变量声明周期结束时,则对应的堆上内存自然就被释放了。
// 在资源管理方面,智能指针(std::shared_ptr和std::unique_ptr)是RAII最具代表性的实现,使用了智能指针,可以实现自动的内存管理,再也不用担心忘记delete造成内存泄漏了。
// 在状态管理方面,线程同步中使用std::unique_lock或std::lock_guard对互斥量std::mutex进行状态管理也是RAII的典型实现,通过这种方式,我们再也不用担心互斥量之间的代码出现异常而造成线程死锁。
// 总结起来说,RAII的核心思想是将资源或状态与类对象的生命周期绑定,通过C++语言机制,实现资源与状态的安全管理。资源在对象构造初始化 资源在对象析构时释放*/
//管理资源、避免内存泄露的方法
[14]负载均衡
什么是负载均衡?当一台服务器无法支持大量的用户访问时,将用户分摊到两个或多个服务器上的方法叫负载均衡。负载均衡的方法很多,Nginx负载均衡、LVS-NAT、LVS-DR等
什么是Nginx?Nginx是一款面向性能设计的HTTP服务器,相较于Apache、lighttpd具有占有内存少,稳定性高等优势。Nginx有4种类型的模块:core、handlers、filters、load-balancers。我们这里讨论其中的2种,分别是负责负载均衡的模块load-balancers和负责执行一系列过滤操作的filters模块如果我们的平台配备了负载均衡的话,前一步DNS解析获得的IP地址应该是我们Nginx负载均衡服务器的IP地址。所以,我们的浏览器将我们的网页请求发送到了Nginx负载均衡服务器上。Nginx根据我们设定的分配算法和规则,选择一台后端的真实Web服务器,与之建立TCP连接、并转发我们浏览器发出去的网页请求。Web服务器收到请求,产生响应,并将网页发送给Nginx负载均衡服务器。Nginx负载均衡服务器将网页传递给filters链处理,之后发回给我们的浏览器
[15]浏览器渲染
- 浏览器根据页面内容,生成DOM Tree。根据CSS内容,生成CSS Rule Tree(规则树)。调用JS执行引擎执行JS代码。
- 根据DOM Tree和CSS Rule Tree生成Render Tree(呈现树)
- 根据Render Tree渲染网页 浏览器是一个边解析边渲染的过程。首先浏览器解析HTML文件构建DOM树,然后解析CSS文件构建渲染树,等到渲染树构建完成后,浏览器开始布局渲染树并将其绘制到屏幕上
[16]CDN
CDN叫内容分发网络,是依靠部署在各地的边缘服务器,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度。
[17]线程池相关
线程池:自己写的定义一个结构体(互斥锁、条件变量、任务队列queue)传入参数线程池数量,遍历创建线程,设置线程分离属性:如果任务队列不空,就取出任务加锁执行,为空,就阻塞等待;如果关闭就结束此线程添加任务:加锁,任务队列插入任务,唤醒一个等待线程结束线程池:回收线程池资源,把所有休眠线程唤醒,标志位设置为释放
线程的同步机制有哪些?临界区 互斥量 信号量 事件
线程池中的工作线程是一直等待吗?阻塞等待的模式下为了能够处理高并发的问题,将线程池中的工作线程都设置为阻塞等待在请求队列是否不为空的条件上
你的线程池工作线程处理完一个任务后的状态是什么?这里要分两种情况考虑(1) 当处理完任务后如果请求队列为空时,则这个线程重新回到阻塞等待的状态(2) 当处理完任务后如果请求队列不为空时,那么这个线程将处于与其他线程竞争资源的状态,谁获得锁谁就获得了处理事件的资格
如果同时1000个客户端进行访问请求,线程数不多,怎么能及时响应处理每一个呢?问服务器如何处理高并发的问题对子线程循环调用来解决高并发的问题的。
通过子线程的run调用函数进行while循环,让每一个线程池中的线程永远都不会终止,他处理完当前任务就去处理下一个,没有任务就一直阻塞在那里等待。这样就能达到服务器高并发的要求
如果一个客户请求需要占用线程很久的时间,会不会影响接下来的客户请求呢,有什么好的策略呢?会影响接下来的客户请求,因为线程池内线程的数量时有限的,如果客户请求占用线程时间过久的话会影响到处理请求的效率,当请求处理过慢时会造成后续接受的请求只能在请求队列中等待被处理,从而影响接下来的客户请求
应对策略:我们可以为线程处理请求对象设置处理超时时间, 超过时间先发送信号告知线程处理超时,然后设定一个时间间隔再次检测,若此时这个请求还占用线程则直接将其断开连接。
ET模式下一次性循环读
Read独到的字节数小于等于0,就break
[18]缓冲区自动扩容
因为缓冲区的大小是固定的大小,而我们通常是一次性将数据全部读取到缓冲区,那么就有可能装不下数据,所以需要临时创建一个缓冲区来缓解,将存不下的放到临时缓冲区,这样就可以一次性将所有的数据读入,这里利用临时缓冲区的技术是一个分散读的技术,即将数据分散读取到内存中不同的位置
如上图所示,当固定的缓冲区想要继续写数据的时候,发现剩余的位置不够写的时候那么就可以先把数据写入到临时缓冲中,再将临时数组的数据读取到固定缓冲区来处理,比如上图所示读指针的位置表示前面的数据已经被读取了,写指针的位置表示如果有数据那么要从这里开始写。
那么如何实现动态扩容的呢?
因为数据的处理只能放到固定大小的缓冲中进行处理,即上述的1024字节的缓冲中,那么如果很多数据都读取到这块区域的话,那么肯定是放不下的,我们就可以利用一个临时的缓冲区,把放不下的数据先放到临时的缓冲的位置,等到1024字节大小的内存有剩余的空间的时候我再将临时缓冲区的数据放入到1024的位置进行处理,那么何时1024缓冲中有空闲的位置呢?原理及实现如下图所示:
可以从上图看出,如果我们想要写入数据,那么此时可以利用的空间就是最前面的部分和最后面的部分的位置,但是写入数据一定要连续,所以唯一的办法就是将中间的数据移动到最前面,这样就可以将空闲的区域连接在一起,方便后面的写数据。具体的实现就是将读指针到写指针之间的数据复制到最前面,再更改读指针和写指针的位置,这就是利用一个缓冲实现自动增长的原理,
分散读、集中写
因为读设置的是边沿触发,需要一次性读完所有数据。所以定义一个大小1024的容器,但是有可能放不下,所以在定义一个65535的备用容器,采用分散读的形式,读到这两个容器里。然后整合这两个容器里数据(因为后序要吧数据取出来进行解析请求,所以需要合到一起):如果第一个能放下,写指针向后移动;如果放不下,看看能否凑出来,能凑则凑,凑不出来,第一个容器自动扩容resize。这样所有数据都在第一个容器里了。没必要一开始就用大容器:占内存,影响性能。
解析http请求,生成http响应响应首行,响应头在buffer里,响应体在内存映射里
写数据:边沿触发,一次性把数据从缓冲区buffer、内存映射写到socket中,所以需要分散写。
[19]并发模型相关
• 简单说一下服务器使用的并发模型?
• reactor、proactor、主从reactor模型的区别?
**Reactor是:同步阻塞I/O模式,**注册对应读写事件处理器,等待事件发生进而调用事件处理器处理事件主线程往epoll内核上注册socket读事件,主线程调用epoll_wait等待socket上有数据可读,当socket上有数据可读的时候,主线程把socket可读事件放入请求队列。睡眠在请求队列上的某个工作线程被唤醒,处理客户请求,然后往epoll内核上注册socket写请求事件。主线程调用epoll_wait等待写请求事件,当有事件可写的时候,主线程把socket可写事件放入请求队列。睡眠在请求队列上的工作线程被唤醒,处理客户请求。
Proactor: 异步I/O模式主线程调用aio_read函数向内核注册socket上的读完成事件,并告诉内核用户读缓冲区的位置,以及读完成后如何通知应用程序,主线程继续处理其他逻辑,当socket上的数据被读入用户缓冲区后,通过信号告知应用程序数据已经可以使用。应用程序预先定义好的信号处理函数选择一个工作线程来处理客户请求。工作线程处理完客户请求之后调用aio_write函数向内核注册socket写完成事件,并告诉内核写缓冲区的位置,以及写完成时如何通知应用程序。主线程处理其他逻辑。当用户缓存区的数据被写入socket之后内核向应用程序发送一个信号,以通知应用程序数据已经发送完毕。应用程序预先定义的数据处理函数就会完成工作。
[20]什么是ET(边缘触发)、LT(水平触发)?ET、LT优缺点?
LT:只要缓冲区有数据,epoll_wait就会被触发ET:数据来一次只触发一次
ET模式缺点:应用层业务逻辑复杂,容易遗漏事件,很难用好。优点:相对LT模式效率比较高。一触发立即处理事件。LT模式:优点:编程更符合用户直觉,业务层逻辑更简单。缺点:效率比ET低。
什么时候用ET,什么时候用LT?LT适用于并发量小的情况,ET适用于并发量大的情况。
为什么?ET在通知用户之后,就会将fd从就绪链表中删除,而LT不会,它会一直保留,这就会导致随着fd增多,就绪链表越大,每次都要从头开始遍历找到对应的fd,所以并发量越大效率越低。ET因为会删除所以效率比较高。
怎么解决LT的缺点?LT模式下,可写状态的fd会一直触发事件,该怎么处理这个问题方法1:每次要写数据时,将fd绑定EPOLLOUT事件,写完后将fd同EPOLLOUT从epoll中移除。方法2:方法一中每次写数据都要操作epoll。如果数据量很少,socket很容易将数据发送出去。可以考虑改成:数据量很少时直接send,数据量很多时在采用方法1.
触发LT模式后,读一次还是循环读?读一次。
为什么ET模式下一定要设置非阻塞?因为ET模式下是无限循环读,直到出现错误为EAGAIN或者EWOULDBLOCK,这两个错误表示socket为空,不用再读了,然后就停止循环了,如果是阻塞,循环读在socket为空的时候就会阻塞到那里,主线程的read()函数一旦阻塞住,当再有其他监听事件过来就没办法读了,给其他事情造成了影响,所以必须要设置为非阻塞。
#晒一晒我的offer##如何判断面试是否凉了##我的实习求职记录##实习,投递多份简历没人回复怎么办##23届找工作求助阵地#涵盖数据结构与算法、编程语言、数据库、操作系统、计算机网络、设计模式、项目经验、综合智力题