👂 Honey Honey - 孙燕姿 - 单曲 - 网易云音乐
目录
🌼触类旁通
🚩线程 && 进程
线程与进程的区别
多线程锁是什么
进程 / 线程 / 协程 的区别
线程切换时,需要切换的状态
🎂并发 && 并行
并发和并行是什么
并发编程需要加锁时不加,有什么问题
阻塞 和 非阻塞什么意思
🌼HTTP
常见 HTTP 状态码
HTTP 长连接和短连接的区别
HTTP 请求方法有哪些
TCP 建立连接的三次握手
TCP 断开连接的四次挥手
🌹服务器
服务器出现大量 close_wait 的连接,原因 && 解决
服务器中缓存的作用 && 实现
设计模式需要遵循的原则
❤号外
🌼触类旁通
TinyWebServer 中出现过的,面试可能考察的专业名词,在 InterviewGuide大厂面试真题
进行检索,汇总而成(适当补充)
上两篇博客
WebServer -- 架构图 && 面试题(上)-CSDN博客
WebServer -- 面试题(下)-CSDN博客
最后一篇 WebServer 终于写完了
尚未完成的工作👇
- 复习完剩下几篇写过的博客(10小时)
- 默写一遍 线程池源码 + 单例模式代码 + 架构图(3小时)
- 重新实现一遍源码【不到 3000 行代码】,然后跑通(12小时)(学有余力,就将定时器的升序链表改成最小堆或者跳表)
整个项目耗时 80 ~ 100小时(还有25小时完结撒花,准备开始迎接下一个项目)
说真的,趁现在还早,抓紧时间提升
英语
(比如六级580+,雅思7.5+,90%英文计算机文档无障碍阅读)
数据结构和算法
(无聊就刷,OIer 和 ACMer 就这么来的)
这俩决定了你以后能走多远
多研究源码,废寝忘食,保证作息,健身,娱乐的前提下,尽可能投入学习
然后多面试积累经验
最后作个分享!
怎样度过人生的低谷期?
安静地等待。
好好睡觉,像一只冬眠的熊。
锻炼身体,坚信无论是承受更深的低潮或是迎接高潮,好的体魄都用得着。
和知心的朋友谈天,基本上不发牢骚,主要是回忆快乐的时光。
多读书,看一些传记。 一来增长知识,顺带还可瞧瞧别人倒霉的时候是怎么挺过去的。
趁机做家务,把平时忙碌顾不上的活儿都抓紧此时干完。
🚩线程 && 进程
线程与进程的区别
Answer 1
- 线程启动速度快,轻量级
- 线程系统开销小
- 线程使用有一定难度,需要处理数据一致性问题
- 不同线程间共享的有:堆,全局变量,静态变量,指针,引用,文件;
而每个线程独占的是自己的栈空间
Answer 2
- 调度
线程是操作系统调度的基本单位(PC,状态码,通用寄存器,线程栈以及栈指针)
进程是拥有资源的基本单位(打开文件,堆,静态区,代码段)- 并发性
一个进程内的多个线程可以并发(最好和CPU核数相等)
多个进程可以并发- 拥有资源
线程不拥有系统资源,但一个进程的多个线程,可以共享隶属进程的资源
进程是拥有资源的独立单位- 系统开销
线程创建销毁,只需要处理 PC 值,状态码,通用寄存器值,线程栈 以及 栈指针
进程创建销毁,需要重新分配 和 销毁 task_struct 结构
补充解释
PC值是指示当前执行位置的寄存器,用于控制程序的执行流程;
而task_struct 是描述进程或线程属性和状态的数据结构
PC值:
- PC值(Program Counter,程序计数器)是一个寄存器,用于存储当前正在执行的指令的地址或下一条要执行的指令的地址。
- 线程的PC值表示了线程当前执行的位置,当线程被创建时,PC值会初始化为线程的入口地址,即开始执行线程代码;当线程被暂停或切换时,PC值会保存当前执行位置,以便稍后恢复执行。
- 在线程的上下文切换过程中,需要保存和恢复PC值,以确保线程能够正确地继续执行。
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分配给另一个线程执行的过程,涉及多个状态转换
- 就绪状态(Runnable):线程已经准备好运行,但是未被分配到CPU上执行。
- 运行状态(Running):线程已经被分配到CPU上执行,正在运行。
- 阻塞状态(Blocked):线程因为某些原因无法继续执行,例如等待 I/O 操作完成,等待锁释放。
- 等待状态(Waiting):线程在等待其他线程或系统资源的操作完成,例如等待信号量,条件变量。
- 终止状态(Terminated):线程已经执行完毕或被强制终止。
在进行线程切换时,操作系统需要根据当前的调度策略和线程的状态,来选择合适的线程进行切换。
一般来说,操作系统会优先选择就绪状态和运行状态的线程进行切换,以提高程序的性能和响应速度。
🎂并发 && 并行
并发和并行是什么
并发:同一段时间内能同时运行多个程序
并行:同一时刻能运行多个指令
并发:需要操作系统引入的进程和线程,来实现并发运行
并行:需要硬件支持,如:多流水线,多核处理器,分布式计算系统
并发编程需要加锁时不加,有什么问题
两个线程使用同一个全局变量会有不一致问题,比如 a 线程把全局变量 +1,b 线程读的时候,如果还是从缓存中读,那么会没有发现这个更新,就会产生不一致的问题
阻塞 和 非阻塞什么意思
操作系统中,阻塞和非阻塞是指,进程在执行某个操作时,是否会等待操作的完成
- 阻塞:是指当一个进程执行某个操作时,如果该操作未完成,进程会被挂起,等待操作完成后再继续执行。在阻塞状态下,进程无法进行其他操作,直到阻塞的操作完成或被取消。
- 非阻塞:是指当一个进程执行某个操作时,如果该操作未完成,进程不会被挂起,而是立即返回一个错误码和一个特殊值,继续执行其他的操作。在非阻塞状态下,进程可以进行其他的操作,无需等待阻塞操作的完成
阻塞和非阻塞的选择取决于应用程序的需求和设计。
阻塞操作可以保证操作的正确性和一致性,但可能会导致应用程序响应时间延长。
非阻塞操作可以提高应用程序响应速度,但需要额外的处理逻辑来处理未完成的操作。
🌼HTTP
常见 HTTP 状态码
状态码 | 类别 | 含义 |
---|---|---|
1XX | Informational(信息行) | 请求正在处理 |
2XX | Success(成功) | 请求正常处理完毕 |
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 断开连接的四次挥手
流程
- 客户端发送 FIN 报文,表示客户端不再发送数据,请求断开连接
- 服务器接收 FIN 报文后,向客户端发送 ACK 报文,宝石服务器已经收到客户端请求,并准备好断开连接
- 服务器发送 FIN 报文,表示服务端不再发送数据,请求断开连接
- 客户端收到服务器的 FIN 报文后,向服务端发送 ACK 报文,表示收到服务端请求,然后断开连接
注意,四次挥手完成后,TCP 连接就彻底关闭了,双方不能再进行数据传输
流程2 -- 拟人
- 客户端:我要和你离婚,这是离婚协议书,我已经签了,你也签个字吧,发送此时的序列号 Seq = u,进入【FIN_WAIT1】
- 服务器:啊?你再说一遍?你要和我离婚,我还有好多愿望没和你一起实现呢,发送 ACK = 1,ack = u + 1,Seq = v,服务器进入【CLOSE_WAIT】,客户端听了后进入【FIN_WAIT2】
- 服务端:看到自己又说了那么多,客户端也没有说一句话,知道没有希望了,说道,“好吧,好聚好散”,于是狠下心来签了离婚协议书 FIN = 1,ACK = 1,Seq = w,ack = u + 1,服务端进入【LAST_ACK】状态
- 客户端:他冷静地看着签好的离婚协议书,说了句,“也许是最后一次见面了...”,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.com)
设计模式概念和七大原则-腾讯云开发者社区-腾讯云 (tencent.com)
❤号外
关于 TCP 三次握手,四次挥手,本篇文章讲了一点,如果想要更详细,更具体一点的,还需要看看 小林coding 的八股
35 张图解:被问千百遍的 TCP 三次握手和四次挥手面试题 - 小林coding - 博客园 (cnblogs.com)