WebServer -- 八股(终章)

WebServer -- 八股(终章)

👂 Honey Honey - 孙燕姿 - 单曲 - 网易云音乐

目录

🌼触类旁通 

🚩线程 && 进程

线程与进程的区别

多线程锁是什么

进程 / 线程 / 协程 的区别

线程切换时,需要切换的状态

🎂并发 && 并行

并发和并行是什么

并发编程需要加锁时不加,有什么问题

阻塞 和 非阻塞什么意思

🌼HTTP

常见 HTTP 状态码

HTTP 长连接和短连接的区别

HTTP 请求方法有哪些

TCP 建立连接的三次握手

TCP 断开连接的四次挥手

🌹服务器

服务器出现大量 close_wait 的连接,原因 && 解决

服务器中缓存的作用 && 实现

设计模式需要遵循的原则

❤号外


🌼触类旁通 

TinyWebServer 中出现过的,面试可能考察的专业名词,在 InterviewGuide大厂面试真题 

进行检索,汇总而成(适当补充)

上两篇博客

WebServer -- 架构图 && 面试题(上)-CSDN博客

WebServer -- 面试题(下)-CSDN博客

最后一篇 WebServer 终于写完了

未完成的工作👇

  • 复习完剩下几篇写过的博客(8小时)
  • 默写一遍  线程池源码 + 单例模式代码 + 架构图(3小时)
  • 重新实现一遍源码【不到 3000 行代码】,然后跑通(9小时)(学有余力,就将定时器的升序链表改成最小堆或者跳表)

整个项目耗时约 85 小时(还有20小时完结撒花,准备开始迎接下一个项目)

说真的,趁现在还早,抓紧时间提升

英语

(比如六级580+,雅思7.5+,90%英文计算机文档无障碍阅读 -- 外企 + 0距离接触开源)

数据结构和算法

(无聊就刷,OIer 和 ACMer 就这么来的)

这俩决定了你以后能走多远

多研究源码,废寝忘食,保证作息,健身,娱乐的前提下,尽可能投入学习

然后多面试积累经验

最后作个分享

怎样度过人生的低谷期?

安静地等待。

好好睡觉,像一只冬眠的熊。

锻炼身体,坚信无论是承受更深的低潮或是迎接高潮,好的体魄都用得着。

和知心的朋友谈天,基本上不发牢骚,主要是回忆快乐的时光。

多读书,看一些传记。 一来增长知识,顺带还可瞧瞧别人倒霉的时候是怎么挺过去的。

趁机做家务,把平时忙碌顾不上的活儿都抓紧此时干完。

🚩线程 && 进程

线程与进程的区别

Answer 1  

  • 线程启动速度快,轻量级
  • 线程系统开销小
  • 线程使用有一定难度,需要处理数据一致性问题
  • 不同线程间共享的有:堆,全局变量,静态变量,指针,引用,文件;
    而每个线程独占的是自己的栈空间

Answer 2

  1. 调度
    线程是操作系统调度的基本单位(PC,状态码,通用寄存器,线程栈以及栈指针)
    进程是拥有资源的基本单位(打开文件,堆,静态区,代码段)
  2. 并发性
    一个进程内的多个线程可以并发(最好和CPU核数相等)
    多个进程可以并发
  3. 拥有资源
    线程不拥有系统资源,但一个进程的多个线程,可以共享隶属进程的资源
    进程是拥有资源的独立单位
  4. 系统开销
    线程创建销毁,只需要处理 PC 值,状态码,通用寄存器值,线程栈 以及 栈指针
    进程创建销毁,需要重新分配 和 销毁 task_struct 结构

补充解释

PC值是指示当前执行位置的寄存器,用于控制程序的执行流程;

而task_struct 是描述进程或线程属性和状态的数据结构

  1. PC值

    • PC值(Program Counter,程序计数器)是一个寄存器,用于存储当前正在执行的指令的地址或下一条要执行的指令的地址。
    • 线程的PC值表示了线程当前执行的位置,当线程被创建时,PC值会初始化为线程的入口地址,即开始执行线程代码;当线程被暂停或切换时,PC值会保存当前执行位置,以便稍后恢复执行。
    • 在线程的上下文切换过程中,需要保存和恢复PC值,以确保线程能够正确地继续执行。
  2. task_struct

    • task_struct 是 Linux 内核中表示进程或线程的数据结构。在 Linux 中,进程和线程都被视为任务(task),task_struct 结构体用来描述一个任务的各种属性和状态。
    • task_struct 包含了许多信息,如进程/线程的状态、PID(Process ID,进程标识符)、进程/线程的内存管理信息、调度信息、文件描述符表等。
    • 在进程创建和销毁过程中,需要重新分配和销毁 task_struct 结构。当创建新的进程或线程时,需要为其分配一个新的 task_struct 结构;而当进程或线程结束时,需要释放对应的 task_struct 结构以释放资源。

多线程锁是什么

多线程锁是一种用来保护共享资源的机制。

多线程编程中,如果多个线程同时访问一个共享资源,可能会发生竞态条件(Race Condition),导致程序出现未定义行为。

为了避免这种未定义,用多线程锁来保护共享资源。

多线程锁的基本思想是,在访问共享资源之前,先获取锁,访问完成后再释放锁。

保证同一时刻只有一个线程可以访问共享资源,从而避免竞态条件的发生。

常见的多线程锁包括:互斥锁,读写锁,条件变量等。

其中,互斥锁用于保护对共享资源的访问;读写锁用于,在读多写少的情况下,提高并发性能;条件变量用于,线程之间的同步和通信

进程 / 线程 / 协程 的区别

  • 进程是资源分配的基本单位,运行一个可执行程序会创建一个或多个进程,进程运行起来就是可执行程序
  • 线程是资源调度的基本单位,也是程序执行的基本单元,是轻量级进程。每个进程中都有唯一的主线程,而且只能有一个;主线程和进程是相互依存的关系,主线程结束进程也会结束
  • 协程是用户态的轻量级线程,线程内部调度的基本单位
进程

线程

协程
基本单位 资源分配和拥有的 程序执行的 用户态的轻量级线程,线程内部调度的
切换情况 进程CPU环境(栈,寄存器,页表和文件句柄等)的保存以及新调度的进程CPU环境的设置 保存和设置程序计数器,少量寄存器和栈的内容 先将寄存器上下文和栈保存,等切换回来的时候再恢复
切换者 操作系统 操作系统 用户
切换过程 用户态->内核态->用户态 用户态->内核态->用户态 用户态(没有陷入内核)
调用栈 内核栈 内核栈 用户栈
拥有资源 CPU资源,内存资源,文件资源和句柄 程序计数器,寄存器,栈和状态字 自己的寄存器上下文和栈
并发性 不同进程之间切换实现并发,各自占有CPU实现并行 一个进程内部的多个线程并发执行 同一时间只能执行一个协程,其他协程处于休眠状态,适合对任务进行分时处理
系统开销 切换虚拟地址空间,切换内核栈和硬件上下文,CPU告诉缓存失效,页表切换,开销很大 切换时,只需保存和设置少量寄存器内容,开销很小 直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文切换非常快
通信方面 进程间通信需要借助操作系统 线程间可以直接读写进程数据段(如全局变量)来进行通信 共享内存,消息队列

寄存器

位于CPU内部的高速存储器,它的快速访问速度有助于提高CPU执行效率

线程切换时,需要切换的状态

线程切换是指在多线程程序中,当一个线程执行完毕后,操作系统需要将CPU分配给另一个线程执行的过程,涉及多个状态转换

  1. 就绪状态(Runnable):线程已经准备好运行,但是未被分配到CPU上执行。
  2. 运行状态(Running):线程已经被分配到CPU上执行,正在运行。
  3. 阻塞状态(Blocked):线程因为某些原因无法继续执行,例如等待 I/O 操作完成,等待锁释放。
  4. 等待状态(Waiting):线程在等待其他线程或系统资源的操作完成,例如等待信号量,条件变量。
  5. 终止状态(Terminated):线程已经执行完毕或被强制终止。

在进行线程切换时,操作系统需要根据当前的调度策略和线程的状态,来选择合适的线程进行切换。

一般来说,操作系统会优先选择就绪状态和运行状态的线程进行切换,以提高程序的性能和响应速度。

🎂并发 && 并行

并发和并行是什么

并发:同一段时间内能同时运行多个程序

并行:同一时刻能运行多个指令

并发:需要操作系统引入的进程和线程,来实现并发运行

并行:需要硬件支持,如:多流水线,多核处理器,分布式计算系统

并发编程需要加锁时不加,有什么问题

两个线程使用同一个全局变量会有不一致问题,比如 a 线程把全局变量 +1,b 线程读的时候,如果还是从缓存中读,那么会没有发现这个更新,就会产生不一致的问题

阻塞 和 非阻塞什么意思

操作系统中,阻塞和非阻塞是指,进程在执行某个操作时,是否会等待操作的完成

  • 阻塞:是指当一个进程执行某个操作时,如果该操作未完成,进程会被挂起,等待操作完成后再继续执行。在阻塞状态下,进程无法进行其他操作,直到阻塞的操作完成或被取消。
  • 非阻塞:是指当一个进程执行某个操作时,如果该操作未完成,进程不会被挂起,而是立即返回一个错误码和一个特殊值,继续执行其他的操作。在非阻塞状态下,进程可以进行其他的操作,无需等待阻塞操作的完成

阻塞和非阻塞的选择取决于应用程序的需求和设计。

阻塞操作可以保证操作的正确性和一致性,但可能会导致应用程序响应时间延长。

非阻塞操作可以提高应用程序响应速度,但需要额外的处理逻辑来处理未完成的操作。

🌼HTTP

常见 HTTP 状态码

状态码 类别 含义
1XX Informational(信息行) 请求正在处理
2XX Su***ess(成功) 请求正常处理完毕
3XX Redirection(重定向) 需要进一步操作以完成请求
4XX Client Error(客户端错误) 客户端发送信息有误
5XX Server Error(服务器错误) 服务器无法正常接收信息
  • 1XX 信息
    一)100 Continue:目前为止正常,客户端可以继续发送请求或忽略该响应
  • 2XX 成功
    一)200 OK
    二)204 No Content:请求已成功处理,但是返回的响应报文不包含实体的主体部分。一般在只需要从客户端往服务器发送信息,而不需要返回数据时使用。
    三)206 Partial Content:客户端进行了范围请求,响应报文包含由 Content-Range 指定范围的实体内容
  • 3XX 重定向
    一)301 Moved Permanently:永久重定向
    二)302 Found:临时重定向
    三)303 See Other:和 302 有相同功能,但是 303 明确要求客户端应采取 GET 方法获取资源
    四)304 Not Modified:如果请求报文包含一些条件,例如:If-Match, If-Modified-Since,
    If-None-Match, If-Range, If-Unmodified-Since,如果不满足条件,服务器会返回 304 状态码
    五)307 Temporary Redirect:临时重定向,类似 302,但是 307 要求浏览器不会把重定向的 POST 方法改成 GET
  • 4XX 客户端错误
    一)400 Bad Request:请求报文存在语法错误
    二)401 Unauthorized:发送的请求需要有认证信息(BASIC 认证,DiGEST 认证)
    如果之前已进行过一次请求,表示用户认证失败
    三)403 Forbidden:请求被拒绝
    四)404 Not Found
  • 5XX 服务器错误
    一)500 Internal Server Error:服务器执行请求时发生错误
    二)503 Service Unavailable:服务器暂时处于超负载 或 正在停机维护,现在无法处理请求

HTTP 长连接和短连接的区别

HTTP/1.0 默认使用短链接。也就是说,客户端和服务器每进行一次 HTTP 操作,就建立一次连接,任务结束就中断连接。

而 HTTP/1.1 起,默认使用长连接,以保持连接特性

HTTP 请求方法有哪些

客户端发送的 请求报文 第一行为请求行,包含了方法字段

HTTP/1.0 定义了 3 种请求方法:GET,POST,HEAD

HTTP/1.1 新增了 6 种请求方法:OPTIONS,PUT,PATCH,DELETE,TRACE,CONNECT

序号 方法 描述
1 GET 请求指定的页面信息,返回实体主体
2 HEAD 类似 GET,但是返回的响应种没有具体内容,用于获取报头
3 POST 向指定资源提交数据进行处理请求(例如提交表单或上传文件)。数据被包含在请求体中。POST 请求可能会导致新的资源建立 或 已有资源的修改
4 PUT 从客户端向服务器传送的数据取代指定的文档内容
5 DELETE 请求服务器删除指定页面
6 CONNECT HTTP/1.1 协议中,预留给能够将连接改为管道方式的代理服务器
7 OPTIONS 允许客户端查看服务器性能
8 TRACE 回显服务器收到的请求,用于测试 / 诊断
9 PATCH PUT 方法的补充,对已知资源进行局部更新

TCP 建立连接的三次握手

三次握手(Three-way HandShake)其实就是建立一个 TCP 连接,需要客户端和服务器总共发送 3 个包。

进行三次握手的主要作用是:确认双方的接收能力和发送能力是否正常,指定自己的初始化序列号为后面的可靠性传输做准备。

实质上就是连接服务器指定端口,建立 TCP 连接,并同步连接双方的序列号和确认号,交换 TCP窗口大小 信息

  • 初始状态:客户端处于 closed(关闭) 状态,服务器处于 listen(监听) 状态
  • 第一次握手:客户端发送请求报文,将 SYN = 1 同步序列号和初始化序列号 seq = x 发送给服务端,发送完后,客户端处于 SYN_Send 状态(服务端收到后,站在服务器的角度,验证了客户端的发送能力和服务端的接收能力)
  • 第二次握手:服务端收到 SYN 请求报文后,如果同意连接,会以自己的
    同步序列号 SYN(s) = 1 ,初始化序列号 seq = y,确认序列号(期望下次接收到数据包) ack = x + 1 以及 确认号 ACK = 1 报文作为应答,此时服务器为 SYN_Receive 状态
    (问题来了,两次握手后,站在客户端角度思考:我发送和接收都 ok,服务端发送和接收也 ok)
    (但是站在服务端思考:你的发送和我的接受都 ok,但是你还没给我回复,我不知道我的发送和你的接收 ok 不 ok?所以,你要给我第三次握手,传个话告诉我。如果你不告诉我,我认为你跑了,出于安全性考虑,我会继续给你发一次)
  • 第三次握手:客户端收到服务器的 SYN + ACK 后,直到可以接着发送下一序列的数据包了,然后发送同步序列号 ack = y + 1 和数据包的序列号 seq = x + 1,确认号 ACK = 1 确认包作为应答,客户端转为 Established 状态(站在双方的角度思考,都 ok)

TCP 断开连接的四次挥手

流程

  1. 客户端发送 FIN 报文,表示客户端不再发送数据,请求断开连接
  2. 服务器接收 FIN 报文后,向客户端发送 ACK 报文,宝石服务器已经收到客户端请求,并准备好断开连接
  3. 服务器发送 FIN 报文,表示服务端不再发送数据,请求断开连接
  4. 客户端收到服务器的 FIN 报文后,向服务端发送 ACK 报文,表示收到服务端请求,然后断开连接

注意,四次挥手完成后,TCP 连接就彻底关闭了,双方不能再进行数据传输

流程2 -- 拟人

  1. 客户端:我要和你离婚,这是离婚协议书,我已经签了,你也签个字吧,发送此时的序列号 Seq = u,进入【FIN_WAIT1】
  2. 服务器:啊?你再说一遍?你要和我离婚,我还有好多愿望没和你一起实现呢,发送 ACK = 1,ack = u + 1,Seq = v,服务器进入【CLOSE_WAIT】,客户端听了后进入【FIN_WAIT2】
  3. 服务端:看到自己又说了那么多,客户端也没有说一句话,知道没有希望了,说道,“好吧,好聚好散”,于是狠下心来签了离婚协议书 FIN = 1,ACK = 1,Seq = w,ack = u + 1,服务端进入【LAST_ACK】状态
  4. 客户端:他冷静地看着签好的离婚协议书,说了句,“也许是最后一次见面了...”,ACK = 1,Seq = u + 1,ack = w + 1,客户端说完后,进入了【TIME_WAIT】状态,服务器收到信息后,进入【CLOSED】状态。客户端停顿了下,想看看服务器还有什么说的,毕竟以后再也没机会见面了,可等来的只有沉默,心想,“算了,结束吧”。客户端进入【CLOSED】

图解

四次挥手

三次为什么不行

Answer 1

个人觉得Answer 1解释的不够清楚

  • 1) 三次握手中,服务器收到客户端的 SYN 连接请求报文后,可以直接发送 SYN + ACK 报文。
    2) 其中 ACK 报文用来应答,SYN 报文用来同步。
  • 1) 但是四次挥手关闭连接时,当服务端收到 FIN 报文,很可能不会立即关闭 SOCKET,所以只能先回复一个 ACK 报文,告诉客户端,“你发的 FIN 报文我收到了”。
    2) 只有等到我服务器所有报文都发送完了,才能发送 FIN 报文,因此 FIN + ACK 一起发送,所以需要四次挥手

Answer 2

  •  任何一方都可以在数据传送结束后,发出连接释放的通知,待对方确认后进入半关闭状态
  • 当另一方也没有数据再发送时,才能发出连接释放通知,对方确认后就完全关闭了 TCP 连接
  • 举个例子,A 和 B 打电话,通话即将结束时,A 说 “我没什么要说的了”,B 回答 “我知道了”,但是 B 可能还想收个尾。A 不能要求 B 按自己的节奏结束通话。然后 B 又巴拉一通,最后 B 才说 “我说完了”,A 回答 “知道了”,通话才算真正结束

🌹服务器

服务器出现大量 close_wait 的连接,原因 && 解决

close_wait 状态时在 TCP 四次挥手的时候收到 FIN,但是没有发送自己的 FIN 时出现的,服务器出现大量 close_wait 状态的原因有 2 种:

  • 服务器内部业务处理占用了过多时间,都没能处理完业务;或者还有数据需要发送;或者服务器的业务逻辑有问题,没有执行 close() 方法
  • 服务器的父进程派生出子进程,子进程继承了 socket,收到 FIN 时,子进程处理但父进程没有处理该信号,导致 socket 的引用不为 0 无法回收

处理方法:

  • 停止应用程序
  • 修改程序里的 bug

服务器中缓存的作用 && 实现

原因

  • 缓解服务器压力
  • 降低客户端获取资源的延迟:缓存通常位于内存中,读取缓存的速度更快。并且缓存服务器在地理位置上,可能比源服务器更近,比如浏览器缓存

实现方法

  • 让代理服务器进行缓存
  • 让客户端浏览器进行缓存

设计模式需要遵循的原则

标记 名称 定义
OCP 开闭原则 对扩展开房,对修改关闭
SRP 单一职责 一个类只负责一个职责
LSP 里氏替换 所有引用基类的地方,必须可以透明地使用其子类对象
DIP 依赖倒转 依赖于抽象,不能依赖于具体实现
ISP 接口隔离 类之间的依赖关系要建立在最小的接口上
CARP 组合优于继承 尽量使用组合/聚合,而不是通过继承达到复用的目的
  • 单一职责

    设计类的时候要尽量缩小粒度,使功能明确,单一,不要做多余的事情(高内聚,低耦合)

    单一职责下的类会比较短小而精悍,需要使用结构性模式组合复用它们
  • 开闭原则

    一个类应该对扩展开发,对修改关闭

    关键是要做好封装,隐藏内部的实现细节,开发足够的接口,这样外部的代码就只能通过接口去扩展功能,不需要侵入到类的内部

    final 关键字

    不能说要实现某个功能就一定要修改类内部代码才能实现,最好可以通过扩展实现新的功能(低耦合)
  • 里氏替换

    子类能够完全替换父类,不会改变父类定义的行为

    比如一个基类鸟类中有一个方法,能够飞行,所有鸟类都必须继承它,但是企鹅,鸵鸟这些没法飞行(使用接口代替继承)
  • 接口隔离

    不应该强迫客户依赖于他们不需要的接口

    建立单一接口,不要建立庞大的接口,尽量细化,接口中的方法要尽量少

    类 “犬科” 依赖接口 I 的方法:捕食(),行走(),奔跑();宠物狗类是对类 “犬科” 的实现。
    对于具体的类宠物狗来说,虽然存在用不到的方法,但由于继承了接口,所以也必须实现这些用不到的方法
  • 依赖倒置

    上层要避免依赖下层的实现细节,两者都依赖于抽象

    比如 Java 的操作数据库,Java 定义了一组接口,由各个数据库去实现它,Java 不依赖于它们,数据库依赖于 Java
  • 组合优于继承

    继承耦合度高,组合耦合度低

    继承基类是好的,但是组合通过组合类的指针,可以传入不同的类,避免高耦合

补充理解

1~2小时认真阅读下👇

设计模式之七大基本原则 - 知乎 (zhihu.***)

设计模式概念和七大原则-腾讯云开发者社区-腾讯云 (tencent.***)

❤号外

关于 TCP 三次握手,四次挥手,本篇文章讲了一点,如果想要更详细,更具体一点的,还需要看看 小林coding 的八股

35 张图解:被问千百遍的 TCP 三次握手和四次挥手面试题 - 小林coding - 博客园 (***blogs.***)

转载请说明出处内容投诉
CSS教程_站长资源网 » WebServer -- 八股(终章)

发表评论

欢迎 访客 发表评论

一个令你着迷的主题!

查看演示 官网购买