0


WebServer -- 八股(终章)

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

🌼触类旁通

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

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

上两篇博客

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

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

Github 地址

11days/TinyWebServer: TinyWebServer一百小时 (github.com)

最后一篇 WebServer 终于写完了

尚未完成的工作👇

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

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

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

英语

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

数据结构和算法

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

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

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

然后多面试积累经验

最后作个分享!

怎样度过人生的低谷期?

安静地等待。

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

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

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

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

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

🚩线程 && 进程

线程与进程的区别

41

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 结构以释放资源。

多线程锁是什么

42

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

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

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

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

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

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

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

进程 / 线程 / 协程 的区别

43

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

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

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

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

44

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

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

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

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

🎂并发 && 并行

并发和并行是什么

45

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

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

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

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

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

46

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

阻塞 和 非阻塞什么意思

47

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

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

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

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

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

🌼HTTP

常见 HTTP 状态码

48
状态码类别含义1XXInformational(信息行)请求正在处理2XXSuccess(成功)请求正常处理完毕3XXRedirection(重定向)需要进一步操作以完成请求4XXClient Error(客户端错误)客户端发送信息有误5XXServer 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 长连接和短连接的区别

49

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

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

HTTP 请求方法有哪些

50

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

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

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

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

TCP 建立连接的三次握手

51

三次握手(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 断开连接的四次挥手

52

流程

  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 的连接,原因 && 解决

53

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

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

处理方法:

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

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

54

原因

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

实现方法

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

设计模式需要遵循的原则

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

  • 单一职责 设计类的时候要尽量缩小粒度,使功能明确,单一,不要做多余的事情(高内聚,低耦合) 单一职责下的类会比较短小而精悍,需要使用结构性模式组合复用它们
  • 开闭原则 一个类应该对扩展开发,对修改关闭 关键是要做好封装,隐藏内部的实现细节,开发足够的接口,这样外部的代码就只能通过接口去扩展功能,不需要侵入到类的内部 final 关键字 不能说要实现某个功能就一定要修改类内部代码才能实现,最好可以通过扩展实现新的功能(低耦合)
  • 里氏替换 子类能够完全替换父类,不会改变父类定义的行为 比如一个基类鸟类中有一个方法,能够飞行,所有鸟类都必须继承它,但是企鹅,鸵鸟这些没法飞行(使用接口代替继承)
  • 接口隔离 不应该强迫客户依赖于他们不需要的接口 建立单一接口,不要建立庞大的接口,尽量细化,接口中的方法要尽量少 类 “犬科” 依赖接口 I 的方法:捕食(),行走(),奔跑();宠物狗类是对类 “犬科” 的实现。 对于具体的类宠物狗来说,虽然存在用不到的方法,但由于继承了接口,所以也必须实现这些用不到的方法
  • 依赖倒置 上层要避免依赖下层的实现细节,两者都依赖于抽象 比如 Java 的操作数据库,Java 定义了一组接口,由各个数据库去实现它,Java 不依赖于它们,数据库依赖于 Java
  • 组合优于继承 继承耦合度高,组合耦合度低 继承基类是好的,但是组合通过组合类的指针,可以传入不同的类,避免高耦合

补充理解

1~2小时认真阅读下👇

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

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

❤号外

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

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

标签: 面试 web 网络

本文转载自: https://blog.csdn.net/csdner250/article/details/136711667
版权归原作者 千帐灯无此声 所有, 如有侵权,请联系我们删除。

“WebServer -- 八股(终章)”的评论:

还没有评论