跳转至

设计模式

简述设计模式七大原则

开放封闭原则:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。

单一职责原则:一个类、接口或方法只负责一个职责,降低代码复杂度以及变更引起的风险。

依赖倒置原则:针对接口编程,依赖于抽象类或接口而不依赖于具体实现类。

接口隔离原则:将不同功能定义在不同接口中实现接口隔离。

里氏替换原则:任何基类可以出现的地方,子类一定可以出现。

迪米特原则:每个模块对其他模块都要尽可能少地了解和依赖,降低代码耦合度。

合成复用原则:尽量使用组合(has-a)/聚合(contains-a)而不是继承(is-a)达到软件复用的目的。

简述设计模式的分类

创建型模式:在创建对象的同时隐藏创建逻辑,不使用 new 直接实例化对象。有工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。

结构型模式:通过类和接口间的继承和引用实现创建复杂结构的对象。有适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。

行为型模式:通过类之间不同通信方式实现不同行为。有策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

简述简单工厂模式

简单工厂模式指由一个工厂对象来创建实例,适用于工厂类负责创建对象较少的情况。例子:Spring 中的 BeanFactory 使用简单工厂模式,产生 Bean 对象。

简述工厂模式

工厂方法模式指定义一个创建对象的接口,让接口的实现类决定创建哪种对象,让类的实例化推迟到子类中进行。例子:Spring 的 FactoryBean 接口的 getObject 方法也是工厂方法。

简述抽象工厂模式

抽象工厂模式指提供一个创建一系列相关或相互依赖对象的接口,无需指定它们的具体类。例子:java.sql.Connection 接口。

简述单例模式

一个单例类在任何情况下都只存在一个实例。 1>饿汉式实现

class singleton{   //实现单例模式的类  
    private:  
        singleton() {}  //私有的构造函数  
    public:  
        static singleton* GetInstance(){  
            static singleton Instance;  
            return &Instance;  
        }  
}; 
2>懒汉式实现

class singleton{   //实现单例模式的类    
    private:  
        singleton(){}  //私有的构造函数  
        static singleton* Instance;  
    public:  
        static singleton* GetInstance(){  
            if (Instance == NULL){ //判断是否第一调用  
                Lock(); //表示上锁的函数  
                if (Instance == NULL){  
                    Instance = new singleton();  
                }  
                UnLock() //解锁函数  
            }             
            return Instance;  
        }  
};  

简述代理模式

代理模式为其他对象提供一种代理以控制对这个对象的访问。优点是可以增强目标对象的功能,降低代码耦合度,扩展性好。缺点是在客户端和目标对象之间增加代理对象会导致请求处理速度变慢,增加系统复杂度。

静态代理:在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。

动态代理:程序运行期间动态的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定。

简述适配器模式

适配器模式将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作。

简述模板模式

模板模式定义了一个操作中的算法的骨架,并将一些步骤延迟到子类,适用于抽取子类重复代码到公共父类。 可以封装固定不变的部分,扩展可变的部分。但每一个不同实现都需要一个子类维护,会增加类的数量。

简述装饰器模式

装饰者模式可以动态地给对象添加一些额外的属性或行为,即需要修改原有的功能,但又不愿直接去修改原有的代码时,设计一个Decorator套在原有代码外面。

简述观察者模式

观察者模式表示的是一种对象与对象之间具有依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

数据结构

简述数据结构栈

栈是一种线性表,其限制只能在表尾进行插入或删除操作。由于该特性又称为后进先出的线性表。

简述数据结构队列

队列是一种先进先出的线性表。其限制只能在线性表的一端进行插入,而在另一端删除元素。

简述二叉树

二叉树是n个有限元素的集合,该集合或者为空、或者由一个称为根(root)的元素及两个不相交的、被分别称为左子树和右子树的二叉树组成。

简述满二叉树

一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。

简述完全二叉树

一棵深度为k的有n个结点的二叉树,对树中的结点按从上至下、从左到右的顺序进行编号,如果编号为i(1≤i≤n)的结点与满二叉树中编号为i的结点在二叉树中的位置相同,则这棵二叉树称为完全二叉树。

简述二叉树的前中后序遍历算法

前序遍历:若二叉树为空树,则执行空逻辑,否则:

  1. 访问根节点
  2. 递归前序遍历左子树
  3. 递归前序遍历右子树

中序遍历:若二叉树为空树,则执行空逻辑,否则:

  1. 递归中序遍历左子树
  2. 访问根节点
  3. 递归中序遍历右子树

后序遍历:若二叉树为空树,则执行空逻辑,否则:

  1. 递归后序遍历左子树
  2. 递归后序遍历右子树
  3. 访问根节点

简述解决Hash冲突的方法

开放定址法:当发生哈希冲突时,如果哈希表未被装满,那么可以把这个值存放到冲突位置中的下一个空位置中去

链地址法:对相同的哈希地址,设置一个单链表,单链表内放的都是哈希冲突元素。

简述AVL树

AVL树是一种改进版的搜索二叉树,其引入平衡因子(左子支高度与右子支高度之差的绝对值),通过旋转使其尽量保持平衡。 任何一个节点的左子支高度与右子支高度之差的绝对值不超过1。

简述红黑树

红黑树本身是有2-3树发展而来,红黑树是保持黑平衡的二叉树,其查找会比AVL树慢一点,添加和删除元素会比AVL树快一点。增删改查统计性能上讲,红黑树更优。 红黑树主要特征是在每个节点上增加一个属性表示节点颜色,可以红色或黑色。红黑树和 AVL 树类似,都是在进行插入和删除时通过旋转保持自身平衡,从而获得较高的查找性能。红黑树保证从根节点到叶尾的最长路径不超过最短路径的 2 倍,所以最差时间复杂度是 O(logn)。红黑树通过重新着色和左右旋转,更加高效地完成了插入和删除之后的自平衡调整。

简述稳定排序和非稳定排序的区别

稳定排序:排序前后两个相等的数相对位置不变,则算法稳定 非稳定排序:排序前后两个相等的数相对位置发生了变化,则算法不稳定

常见的稳定排序算法有哪些

插入排序、冒泡排序、归并排序

常见的不稳定排序算法有哪些

希尔排序、直接选择排序、堆排序、快速排序

简述插入排序

插入排序:每一趟将一个待排序记录按其关键字的大小插入到已排好序的一组记录的适当位置上,直到所有待排序记录全部插入为止。

排序算法稳定。时间复杂度 O(n²),空间复杂度 O(1)。

简述希尔排序

希尔排序:把记录按下标的一定增量分组,对每组进行直接插入排序,每次排序后减小增量,当增量减至 1 时排序完毕。

排序算法不稳定。时间复杂度 O(nlogn),空间复杂度 O(1)。

简述直接选择排序

直接选择排序:每次在未排序序列中找到最小元素,和未排序序列的第一个元素交换位置,再在剩余未排序序列中重复该操作直到所有元素排序完毕。

排序算法不稳定。时间复杂度 O(n²),空间复杂度 O(1)。

简述堆排序

堆排序:将待排序数组看作一个树状数组,建立一个二叉树堆。通过对这种数据结构进行每个元素的插入,完成排序工作。

排序算法不稳定,时间复杂度 O(nlogn),空间复杂度 O(1)。

简述冒泡排序

冒泡排序:比较相邻的元素,如果第一个比第二个大就进行交换,对每一对相邻元素做同样的工作。

排序算法稳定,时间复杂度 O(n²),空间复杂度 O(1)。

简述快速排序

快速排序:随机选择一个基准元素,通过一趟排序将要排序的数据分割成独立的两部分,一部分全部小于等于基准元素,一部分全部大于等于基准元素,再按此方法递归对这两部分数据进行快速排序。

排序算法不稳定,时间复杂度 O(nlogn),空间复杂度 O(logn)。

简述归并排序

归并排序:将待排序序列分成两部分,然后对两部分分别递归排序,最后进行合并。 排序算法稳定,时间复杂度都为 O(nlogn),空间复杂度为 O(n)。

简述图

图是由顶点集合和顶点之间的边集合组成的一种数据结构,分为有向图和无向图。

有向图:边具有方向性

无向图:边不具有方向性

简述邻接矩阵

用一个二维数组存放图顶点间关系的数据,这个二维数组称为邻接矩阵。 对于无向图,邻接矩阵是对称矩阵

简述邻接表

邻接表是通过链表表示图连接关系的一种方。对于表头结点所对应的顶点存在相邻顶点,则把相邻顶点依次存放于表头结点所指向的单向链表中。

简述图的深度优先搜索DFS

将图中每个顶点的访问标志设为 FALSE, 之后搜索图中每个顶点,如果未被访问,则以该顶点V0为起始点出发,访问此顶点,然后依次从V0的各个未被访问的邻接点出发深度优先搜索遍历图,直至图中所有和V0有路径相通的顶点都被访问到。

简述图的广度优先搜索

从图中的某个顶点V0出发,并在访问此顶点之后依次访问V0的所有未被访问过的邻接点,之后按这些顶点被访问的先后次序依次访问它们的邻接点,直至图中所有和V0有路径相通的顶点都被访问到。

简述最小生成树和其对应的算法

对于有 n 个结点的原图,生成原图的极小连通子图,其包含原图中的所有 n 个结点,并且有保持图连通的最少的边。

普里姆算法:取图中任意一个顶点 v 作为生成树的根,之后往生成树上添加新的顶点 w。在添加的顶点 w 和已经在生成树上的顶点v 之间必定存在一条边,并且该边的权值在所有连通顶点 v 和 w 之间的边中取值最小。之后继续往生成树上添加顶点,直至生成树上含有 n-1 个顶点为止。

克鲁斯卡尔算法:先构造一个只含 n 个顶点的子图 SG,然后从权值最小的边开始,若它的添加不使 SG 中产生回路,则在 SG 上加上这条边,如此重复,直至加上 n-1 条边为止。

简述最短路径算法

Dijkstral算法为求解一个点到其余各点最小路径的方法,其算法为:

假设我们求解的是顶点v到其余各个点的最短距离。n次循环至n个顶点全部遍历:

  1. 从权值数组中找到权值最小的,标记该边端点k
  2. 打印该路径及权值
  3. 如果存在经过顶点k到顶点i的边比v->i的权值小
  4. 更新权值数组及对应路径

简述堆

堆是一种完全二叉树形式,其可分为最大值堆和最小值堆。

最大值堆:子节点均小于父节点,根节点是树中最大的节点。

最小值堆:子节点均大于父节点,根节点是树中最小的节点。

简述set

Set是一种集合。集合中的对象不按特定的方式排序,并且没有重复对象。

操作系统

什么是操作系统?请简要概述一下

操作系统是管理计算机硬件和软件资源的计算机程序,提供一个计算机用户与计算机硬件系统之间的接口。

向上对用户程序提供接口,向下接管硬件资源。

操作系统本质上也是一个软件,作为最接近硬件的系统软件,负责处理器管理、存储器管理、设备管理、文件管理和提供用户接口。

操作系统有哪些分类?

操作系统常规可分为批处理操作系统、分时操作系统、实时操作系统。

若一个操作系统兼顾批操作和分时的功能,则称该系统为通用操作系统。

常见的通用操作系统有:Windows、Linux、MacOS等。

什么是内核态和用户态?

为了避免操作系统和关键数据被用户程序破坏,将处理器的执行状态分为内核态和用户态。

内核态是操作系统管理程序执行时所处的状态,能够执行包含特权指令在内的一切指令,能够访问系统内所有的存储空间。

用户态是用户程序执行时处理器所处的状态,不能执行特权指令,只能访问用户地址空间。

用户程序运行在用户态,操作系统内核运行在内核态。

如何实现内核态和用户态的切换?

处理器从用户态切换到内核态的方法有三种:系统调用、异常和外部中断。

  1. 系统调用是操作系统的最小功能单位,是操作系统提供的用户接口,系统调用本身是一种软中断。

  2. 异常,也叫做内中断,是由错误引起的,如文件损坏、缺页故障等。

  3. 外部中断,是通过两根信号线来通知处理器外设的状态变化,是硬中断。

并发和并行的区别

  1. 并发(concurrency):指宏观上看起来两个程序在同时运行,比如说在单核cpu上的多任务。但是从微观上看两个程序的指令是交织着运行的,指令之间交错执行,在单个周期内只运行了一个指令。这种并发并不能提高计算机的性能,只能提高效率(如降低某个进程的相应时间)。

  2. 并行(parallelism):指严格物理意义上的同时运行,比如多核cpu,两个程序分别运行在两个核上,两者之间互不影响,单个周期内每个程序都运行了自己的指令,也就是运行了两条指令。这样说来并行的确提高了计算机的效率。所以现在的cpu都是往多核方面发展。

什么是进程?

进程是操作系统中最重要的抽象概念之一,是资源分配的基本单位,是独立运行的基本单位。

进程的经典定义就是一个执行中程序的实例。系统中的每个程序都运行在某个进程的上下文(context)中。

上下文是由程序正确运行所需的状态组成的。这个状态包括存放在内存中的程序的代码和数据,它的栈、通用目的寄存器的内容、程序计数器、环境变量以及打开文件描述符的集合。

进程一般由以下的部分组成:

  1. 进程控制块PCB,是进程存在的唯一标志,包含进程标识符PID,进程当前状态,程序和数据地址,进程优先级、CPU现场保护区(用于进程切换),占有的资源清单等。

  2. 程序段

  3. 数据段

进程的基本操作

以Unix系统举例:

  1. 进程的创建:fork()。新创建的子进程几乎但不完全与父进程相同。子进程得到与父进程用户级虚拟地址空间相同的(但是独立的)一份副本,包括代码和数据段、堆、共享库以及用户栈。子进程还获得与父进程任何打开文件描述符相同的副本,这就意味着当父进程调用 fork 时,子进程可以读写父进程中打开的任何文件。父进程和新创建的子进程之间最大的区别在于它们有不同的 PID。fork函数是有趣的(也常常令人迷惑), 因为它只被调用一次,却会返回两次:一次是在调用进程(父进程)中,一次是在新创建的子进程中。在父进程中,fork 返回子进程的 PID。在子进程中,fork 返回 0。因为子进程的 PID 总是为非零,返回值就提供一个明 确的方法来分辨程序是在父进程还是在子进程中执行。
pid_t fork(void);
  1. 回收子进程:当一个进程由于某种原因终止时,内核并不是立即把它从系统中清除。相反,进程被保持在一种已终止的状态中,直到被它的父进程回收(reaped)。当父进程回收已终止的子进程时,内核将子进程的退出状态传递给父进程,然后抛弃已终止的进程。一个进程可以通过调用 waitpid 函数来等待它的子进程终止或者停止。
pid_t waitpid(pid_t pid, int *statusp, int options);
  1. 加载并运行程序:execve 函数在当前进程的上下文中加载并运行一个新程序。
int execve(const char *filename, const char *argv[], const char *envp[]);
  1. 进程终止:
void exit(int status);

简述进程间通信方法

每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程A把数据从用户空间拷到内核缓冲区,进程B再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信。

不同进程间的通信本质:进程之间可以看到一份公共资源;而提供这份资源的形式或者提供者不同,造成了通信方式不同。

进程间通信主要包括管道、系统IPC(包括消息队列、信号量、信号、共享内存等)、以及套接字socket。

进程如何通过管道进行通信

管道是一种最基本的IPC机制,作用于有血缘关系的进程之间,完成数据传递。调用pipe系统函数即可创建一个管道。有如下特质:

  1. 其本质是一个伪文件(实为内核缓冲区)

  2. 由两个文件描述符引用,一个表示读端,一个表示写端。

  3. 规定数据从管道的写端流入管道,从读端流出。

管道的原理: 管道实为内核使用环形队列机制,借助内核缓冲区实现。

管道的局限性:

  1. 数据自己读不能自己写。

  2. 数据一旦被读走,便不在管道中存在,不可反复读取。

  3. 由于管道采用半双工通信方式。因此,数据只能在一个方向上流动。

  4. 只能在有公共祖先的进程间使用管道。

进程如何通过共享内存通信?

它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据得更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。

特点:

  1. 共享内存是最快的一种IPC,因为进程是直接对内存进行操作来实现通信,避免了数据在用户空间和内核空间来回拷贝。

  2. 因为多个进程可以同时操作,所以需要进行同步处理。

  3. 信号量和共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。

什么是信号

一个信号就是一条小消息,它通知进程系统中发生了一个某种类型的事件。 Linux 系统上支持的30 种不同类型的信号。 每种信号类型都对应于某种系统事件。低层的硬件异常是由内核异常处理程序处理的,正常情况下,对用户进程而言是不可见的。信号提供了一种机制,通知用户进程发生了这些异常。

  1. 发送信号:内核通过更新目的进程上下文中的某个状态,发送(递送)一个信号给目的进程。发送信号可以有如下两种原因:

  2. 内核检测到一个系统事件,比如除零错误或者子进程终止。

  3. —个进程调用了kill 函数, 显式地要求内核发送一个信号给目的进程。一个进程可以发送信号给它自己。

  4. 接收信号:当目的进程被内核强迫以某种方式对信号的发送做出反应时,它就接收了信号。进程可以忽略这个信号,终止或者通过执行一个称为信号处理程序(signal handler)的用户层函数捕获这个信号。

如何编写正确且安全的信号处理函数

  1. 处理程序要尽可能简单。 避免麻烦的最好方法是保持处理程序尽可能小和简单。例如,处理程序可能只是简单地设置全局标志并立即返回;所有与接收信号相关的处理都由主程序执行,它周期性地检查(并重置)这个标志。

  2. 在处理程序中只调用异步信号安全的函数。 所谓异步信号安全的函数(或简称安全的函数)能够被信号处理程序安全地调用,原因有二:要么它是可重入的(例如只访问局部变量),要么它不能被信号处理程序中断。

  3. 保存和恢复errno。 许多Linux 异步信号安全的函数都会在出错返回时设置errno在处理程序中调用这样的函数可能会干扰主程序中其他依赖于分。解决方法是在进人处理程序时把errno 保存在一个局部变量中,在处理程序返回前恢复它。注意,只有在处理程序要返回时才有此必要。如果处理程序调用_exit终止该进程,那么就不需要这样做了。

  4. 阻塞所有的信号,保护对共享全局数据结构的访问。 如果处理程序和主程序或其他处理程序共享一个全局数据结构,那么在访问(读或者写)该数据结构时,你的处理程序和主程序应该暂时阻塞所有的信号。这条规则的原因是从主程序访问一个数据结构d 通常需要一系列的指令,如果指令序列被访问d 的处理程序中断,那么处理程序可能会发现d 的状态不一致,得到不可预知的结果。在访问d 时暂时阻塞信号保证了处理程序不会中断该指令序列。

  5. 用volatile 声明全局变量。 考虑一个处理程序和一个main 函数,它们共享一个全局变量g 。处理程序更新g,main 周期性地读g, 对于一个优化编译器而言,main 中g的值看上去从来没有变化过,因此使用缓存在寄存器中g 的副本来满足对g 的每次引用是很安全的。如果这样,main 函数可能永远都无法看到处理程序更新过的值。可以用volatile 类型限定符来定义一个变量,告诉编译器不要缓存这个变量。例如:volatile 限定符强迫编译器毎次在代码中引用g时,都要从内存中读取g的值。一般来说,和其他所有共享数据结构一样,应该暂时阻塞信号,保护每次对全局变量的访问。

volatile int g;
  1. 用sig_atomic_t声明标志。在常见的处理程序设计中,处理程序会写全局标志来记录收到了信号。主程序周期性地读这个标志,响应信号,再清除该标志。对于通过这种方式来共享的标志,C 提供一种整型数据类型sig_atomic_t对它的读和写保证会是原子的(不可中断的)。

  2. 信号的一个与直觉不符的方面是未处理的信号是不排队的。因为 pending 位向量中每种类型的信号只对应有一位,所以每种类型最多只能有一个未处理的信号。关键思想是如果存在一个未处理的信号就表明至少有一个信号到达了。

进程调度的时机

  1. 当前运行的进程运行结束。

  2. 当前运行的进程由于某种原因阻塞。

  3. 执行完系统调用等系统程序后返回用户进程。

  4. 在使用抢占调度的系统中,具有更高优先级的进程就绪时。

  5. 分时系统中,分给当前进程的时间片用完。

不能进行进程调度的情况

  1. 在中断处理程序执行时。

  2. 在操作系统的内核程序临界区内。

  3. 其它需要完全屏蔽中断的原子操作过程中。

进程的调度策略

  1. 先到先服务调度算法

  2. 短作业优先调度算法

  3. 优先级调度算法

  4. 时间片轮转调度算法

  5. 高响应比优先调度算法

  6. 多级队列调度算法

  7. 多级反馈队列调度算法

进程调度策略的基本设计指标

  1. CPU利用率

  2. 系统吞吐率,即单位时间内CPU完成的作业的数量。

  3. 响应时间。

  4. 周转时间。是指作业从提交到完成的时间间隔。从每个作业的角度看,完成每个作业的时间也是很关键

  5. 平均周转时间

  6. 带权周转时间

  7. 平均带权周转时间

进程的状态与状态转换

进程在运行时有三种基本状态:就绪态、运行态和阻塞态。

  1. 运行(running)态:进程占有处理器正在运行的状态。进程已获得CPU,其程序正在执行。在单处理机系统中,只有一个进程处于执行状态; 在多处理机系统中,则有多个进程处于执行状态。

  2. 就绪(ready)态:进程具备运行条件,等待系统分配处理器以便运行的状态。 当进程已分配到除CPU以外的所有必要资源后,只要再获得CPU,便可立即执行,进程这时的状态称为就绪状态。在一个系统中处于就绪状态的进程可能有多个,通常将它们排成一个队列,称为就绪队列。

  3. 阻塞(wait)态:又称等待态或睡眠态,指进程不具备运行条件,正在等待某个时间完成的状态。

各状态之间的转换:

  1. 就绪→执行 处于就绪状态的进程,当进程调度程序为之分配了处理机后,该进程便由就绪状态转变成执行状态。

  2. 执行→就绪 处于执行状态的进程在其执行过程中,因分配给它的一个时间片已用完而不得不让出处理机,于是进程从执行状态转变成就绪状态。

  3. 执行→阻塞 正在执行的进程因等待某种事件发生而无法继续执行时,便从执行状态变成阻塞状态。

  4. 阻塞→就绪 处于阻塞状态的进程,若其等待的事件已经发生,于是进程由阻塞状态转变为就绪状态。

什么是孤儿进程?僵尸进程?

  1. 孤儿进程: 父进程退出,子进程还在运行的这些子进程都是孤儿进程,孤儿进程将被init进程(1号进程)所收养,并由init进程对他们完成状态收集工作。

  2. 僵尸进程: 进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait 获waitpid 获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中的这些进程是僵尸进程。

什么是线程?

  1. 是进程划分的任务,是一个进程内可调度的实体,是CPU调度的基本单位,用于保证程序的实时性,实现进程内部的并发。

  2. 线程是操作系统可识别的最小执行和调度单位。每个线程都独自占用一个虚拟处理器:独自的寄存器组,指令计数器和处理器状态。

  3. 每个线程完成不同的任务,但是属于同一个进程的不同线程之间共享同一地址空间(也就是同样的动态内存,映射文件,目标代码等等),打开的文件队列和其他内核资源。

为什么需要线程?

线程产生的原因:进程可以使多个程序能并发执行,以提高资源的利用率和系统的吞吐量;但是其具有一些缺点:

  1. 进程在同一时刻只能做一个任务,很多时候不能充分利用CPU资源。

  2. 进程在执行的过程中如果发生阻塞,整个进程就会挂起,即使进程中其它任务不依赖于等待的资源,进程仍会被阻塞。

引入线程就是为了解决以上进程的不足,线程具有以下的优点:

  1. 从资源上来讲,开辟一个线程所需要的资源要远小于一个进程。

  2. 从切换效率上来讲,运行于一个进程中的多个线程,它们之间使用相同的地址空间,而且线程间彼此切换所需时间也远远小于进程间切换所需要的时间(这种时间的差异主要由于缓存的大量未命中导致)。

  3. 从通信机制上来讲,线程间方便的通信机制。对不同进程来说,它们具有独立的地址空间,要进行数据的传递只能通过进程间通信的方式进行。线程则不然,属于同一个进程的不同线程之间共享同一地址空间,所以一个线程的数据可以被其它线程感知,线程间可以直接读写进程数据段(如全局变量)来进行通信(需要一些同步措施)。

简述线程和进程的区别和联系

  1. 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程依赖于进程而存在。

  2. 进程在执行过程中拥有独立的地址空间,而多个线程共享进程的地址空间。(资源分配给进程,同一进程的所有线程共享该进程的所有资源。同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量。)

  3. 进程是资源分配的最小单位,线程是CPU调度的最小单位。

  4. 通信:由于同一进程中的多个线程具有相同的地址空间,使它们之间的同步和通信的实现,也变得比较容易。进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信(需要一些同步方法,以保证数据的一致性)。

  5. 进程编程调试简单可靠性高,但是创建销毁开销大;线程正相反,开销小,切换速度快,但是编程调试相对复杂。

  6. 进程间不会相互影响;一个进程内某个线程挂掉将导致整个进程挂掉。

  7. 进程适应于多核、多机分布;线程适用于多核。

进程和线程的基本API

进程API以Unix系统为例,线程相关的API属于Posix线程(Pthreads)标准接口。

进程原语 线程原语 描述
fork pthread_create 创建新的控制流
exit pthread_exit 从现有的控制流中退出
waitpid pthread_join 从控制流中得到退出状态
atexit pthread_cancel_push 注册在退出控制流时调用的函数
getpid pthread_self 获取控制流的ID
abort pthread_cancel 请求控制流的非正常退出

多线程模型

  1. 多对一模型。将多个用户级线程映射到一个内核级线程上。该模型下,线程在用户空间进行管理,效率较高。缺点就是一个线程阻塞,整个进程内的所有线程都会阻塞。几乎没有系统继续使用这个模型。

  2. 一对一模型。将内核线程与用户线程一一对应。优点是一个线程阻塞时,不会影响到其它线程的执行。该模型具有更好的并发性。缺点是内核线程数量一般有上限,会限制用户线程的数量。更多的内核线程数目也给线程切换带来额外的负担。linux和Windows操作系统家族都是使用一对一模型。

  3. 多对多模型。将多个用户级线程映射到多个内核级线程上。结合了多对一模型和一对一模型的特点。

线程间通信的方式

  1. 临界区:通过多线程的串行化来访问公共资源,速度快,适合控制数据访问。

  2. 互斥量Synchronized/Lock:采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问。

  3. 信号量Semphare:为控制具有有限数量的用户资源而设计的,它允许多个线程在同一时刻去访问同一个资源,但一般需要限制同一时刻访问此资源的最大线程数目。

  4. 事件(信号),Wait/Notify:通过通知操作的方式来保持多线程同步,还可以方便的实现多线程优先级的比较操作

进程同步的方法

操作系统中,进程是具有不同的地址空间的,两个进程是不能感知到对方的存在的。有时候,需要多个进程来协同完成一些任务。 当多个进程需要对同一个内核资源进行操作时,这些进程便是竞争的关系,操作系统必须协调各个进程对资源的占用,进程的互斥是解决进程间竞争关系的方法。 进程互斥指若干个进程要使用同一共享资源时,任何时刻最多允许一个进程去使用,其他要使用该资源的进程必须等待,直到占有资源的进程释放该资源。 当多个进程协同完成一些任务时,不同进程的执行进度不一致,这便产生了进程的同步问题。需要操作系统干预,在特定的同步点对所有进程进行同步,这种协作进程之间相互等待对方消息或信号的协调关系称为进程同步。进程互斥本质上也是一种进程同步。 进程的同步方法:

  1. 互斥锁

  2. 读写锁

  3. 条件变量

  4. 记录锁(record locking)

  5. 信号量

  6. 屏障(barrier)

线程同步的方法

操作系统中,属于同一进程的线程之间具有相同的地址空间,线程之间共享数据变得简单高效。遇到竞争的线程同时修改同一数据或是协作的线程设置同步点的问题时,需要使用一些线程同步的方法来解决这些问题。

线程同步的方法:

  1. 互斥锁

  2. 读写锁

  3. 条件变量

  4. 信号量

  5. 自旋锁

  6. 屏障(barrier)

进程同步与线程同步有什么区别

进程之间地址空间不同,不能感知对方的存在,同步时需要将锁放在多进程共享的空间。而线程之间共享同一地址空间,同步时把锁放在所属的同一进程空间即可。

死锁是怎样产生的?

死锁是指两个或两个以上进程在执行过程中,因争夺资源而造成的下相互等待的现象。 产生死锁需要满足下面四个条件:

  1. 互斥条件:进程对所分配到的资源不允许其他进程访问,若其他进程访问该资源,只能等待,直至占有该资源的进程使用完成后释放该资源。

  2. 占有并等待条件:进程获得一定的资源后,又对其他资源发出请求,但是该资源可能被其他进程占有,此时请求阻塞,但该进程不会释放自己已经占有的资源。

  3. 非抢占条件:进程已获得的资源,在未完成使用之前,不可被剥夺,只能在使用后自己释放。

  4. 循环等待条件:进程发生死锁后,必然存在一个进程-资源之间的环形链。

如何解决死锁问题?

解决死锁的方法即破坏产生死锁的四个必要条件之一,主要方法如下:

  1. 资源一次性分配,这样就不会再有请求了(破坏请求条件)。

  2. 只要有一个资源得不到分配,也不给这个进程分配其他的资源(破坏占有并等待条件)。

  3. 可抢占资源:即当进程新的资源未得到满足时,释放已占有的资源,从而破坏不可抢占的条件。

  4. 资源有序分配法:系统给每类资源赋予一个序号,每个进程按编号递增的请求资源,释放则相反,从而破坏环路等待的条件

什么是虚拟地址,什么是物理地址?

地址空间是一个非负整数地址的有序集合。

在一个带虚拟内存的系统中,CPU 从一个有N=pow(2,n)个地址的地址空间中生成虚拟地址,这个地址空间称为虚拟地址空间(virtual address space),现代系统通常支持 32 位或者 64 位虚拟地址空间。

一个系统还有一个物理地址空间(physical address space),对应于系统中物理内存的M 个字节。

地址空间的概念是很重要的,因为它清楚地区分了数据对象(字节)和它们的属性(地址)。

一旦认识到了这种区别,那么我们就可以将其推广,允许每个数据对象有多个独立的地址,其中每个地址都选自一个不同的地址空间。这就是虚拟内存的基本思想。

主存中的每字节都有一个选自虚拟地址空间的虚拟地址和一个选自物理地址空间的物理地址。

什么是虚拟内存?

为了更加有效地管理内存并且少出错,现代系统提供了一种对主存的抽象概念,叫做虚拟内存(VM)。虚拟内存是硬件异常、硬件地址翻译、主存、磁盘文件和内核软件的完美交互,它为每个进程提供了一个大的、一致的和私有的地址空间。通过一个很清晰的机制,虚拟内存提供了三个重要的能力:

  1. 它将主存看成是一个存储在磁盘上的地址空间的高速缓存,在主存中只保存活动区域,并根据需要在磁盘和主存之间来回传送数据,通过这种方式,它高效地使用了主存。

  2. 它为每个进程提供了一致的地址空间,从而简化了内存管理。

  3. 它保护了每个进程的地址空间不被其他进程破坏。

为什么要引入虚拟内存?

  1. 虚拟内存作为缓存的工具

  2. 虚拟内存被组织为一个由存放在磁盘上的N个连续的字节大小的单元组成的数组。

  3. 虚拟内存利用DRAM缓存来自通常更大的虚拟地址空间的页面。

  4. 虚拟内存作为内存管理的工具。操作系统为每个进程提供了一个独立的页表,也就是独立的虚拟地址空间。多个虚拟页面可以映射到同一个物理页面上。

  5. 简化链接: 独立的地址空间允许每个进程的内存映像使用相同的基本格式,而不管代码和数据实际存放在物理内存的何处。

    • 例如:一个给定的linux系统上的每个进程都是用类似的内存格式,对于64为地址空间,代码段总是从虚拟地址)0x400000开始,数据段,代码段,栈,堆等等。
  6. 简化加载: 虚拟内存还使得容易向内存中加载可执行文件和共享对象文件。要把目标文件中.text和.data节加载到一个新创建的进程中,Linux加载器为代码和数据段分配虚拟页VP,把他们**标记为无效(未被缓存)** ,将页表条目指向目标文件的起始位置。

    • 加载器从不在磁盘到内存实际复制任何数据,在每个页初次被引用时,虚拟内存系统会按照需要自动的调入数据页。
  7. 简化共享: 独立地址空间为OS提供了一个管理用户进程和操作系统自身之间共享的一致机制。

    • 一般:每个进程有各自私有的代码,数据,堆栈,是不和其他进程共享的,这样OS创建页表,将虚拟页映射到不连续的物理页面。

    • 某些情况下,需要进程来共享代码和数据。例如每个进程调用相同的操作系统内核代码,或者C标准库函数。OS会把不同进程中适当的虚拟页面映射到相同的物理页面。

  8. 简化内存分配: 虚拟内存向用户提供一个简单的分配额外内存的机制。当一个运行在用户进程中的程序要求额外的堆空间时(如malloc),OS分配一个适当k大小个连续的虚拟内存页面,并且将他们映射到物理内存中任意位置的k个任意物理页面,因此操作系统没有必要分配k个连续的物理内存页面,页面可以随机的分散在物理内存中

  9. 虚拟内存作为内存保护的工具。不应该允许一个用户进程修改它的只读段,也不允许它修改任何内核代码和数据结构,不允许读写其他进程的私有内存,不允许修改任何与其他进程共享的虚拟页面。每次CPU生成一个地址时,MMU会读一个PTE,通过在PTE上添加一些额外的许可位来控制对一个虚拟页面内容的访问十分简单。

常见的页面置换算法

当访问一个内存中不存在的页,并且内存已满,则需要从内存中调出一个页或将数据送至磁盘对换区,替换一个页,这种现象叫做缺页置换。当前操作系统最常采用的缺页置换算法如下:

  • 先进先出(FIFO)算法:

  • 思路:置换最先调入内存的页面,即置换在内存中驻留时间最久的页面。

  • 实现:按照进入内存的先后次序排列成队列,从队尾进入,从队首删除。

  • 特点:实现简单;性能较差,调出的页面可能是经常访问的

  • 最近最少使用(LRU)算法:

  • 思路: 置换最近一段时间以来最长时间未访问过的页面。根据程序局部性原理,刚被访问的页面,可能马上又要被访问;而较长时间内没有被访问的页面,可能最近不会被访问。

  • 实现:缺页时,计算内存中每个逻辑页面的上一次访问时间,选择上一次使用到当前时间最长的页面

  • 特点:可能达到最优的效果,维护这样的访问链表开销比较大

当前最常采用的就是LRU算法。

  • 最不常用算法(Least Frequently Used, LFU

  • 思路:缺页时,置换访问次数最少的页面

  • 实现:每个页面设置一个访问计数,访问页面时,访问计数加1,缺页时,置换计数最小的页面

  • 特点:算法开销大,开始时频繁使用,但以后不使用的页面很难置换

请说一下什么是写时复制?

  • 如果有多个进程要读取它们自己的那部门资源的副本,那么复制是不必要的。每个进程只要保存一个指向这个资源的指针就可以了。只要没有进程要去修改自己的“副本”,就存在着这样的幻觉:每个进程好像独占那个资源。从而就避免了复制带来的负担。如果一个进程要修改自己的那份资源“副本”,那么就会复制那份资源,并把复制的那份提供给进程。不过其中的复制对进程来说是透明的。这个进程就可以修改复制后的资源了,同时其他的进程仍然共享那份没有修改过的资源。所以这就是名称的由来:在写入时进行复制。

  • 写时复制的主要好处在于:如果进程从来就不需要修改资源,则不需要进行复制。惰性算法的好处就在于它们尽量推迟代价高昂的操作,直到必要的时刻才会去执行。

  • 在使用虚拟内存的情况下,写时复制(Copy-On-Write)是以页为基础进行的。所以,只要进程不修改它全部的地址空间,那么就不必复制整个地址空间。在fork()调用结束后,父进程和子进程都相信它们有一个自己的地址空间,但实际上它们共享父进程的原始页,接下来这些页又可以被其他的父进程或子进程共享。

实时操作系统的概念

实时操作系统(Real-time operating system, RTOS),又称即时操作系统,它会按照排序运行、管理系统资源,并为开发应用程序提供一致的基础。 实时操作系统与一般的操作系统相比,最大的特色就是“实时性”,如果有一个任务需要执行,实时操作系统会马上(在较短时间内)执行该任务,不会有较长的延时。这种特性保证了各个任务的及时执行。

优先级反转是什么?如何解决

由于多进程共享资源,具有最高优先权的进程被低优先级进程阻塞,反而使具有中优先级的进程先于高优先级的进程执行,导致系统的崩溃。这就是所谓的优先级反转(Priority Inversion)。其实,优先级反转是在高优级(假设为A)的任务要访问一个被低优先级任务(假设为C)占有的资源时,被阻塞.而此时又有优先级高于占有资源的任务(C)而低于被阻塞的任务(A)的优先级的任务(假设为B)时,于是,占有资源的任务就被挂起(占有的资源仍为它占有),因为占有资源的任务优先级很低,所以,它可能一直被另外的任务挂起.而它占有的资源也就一直不能释放,这样,引起任务A一直没办法执行.而比它优先低的任务却可以执行。

目前解决优先级反转有许多种方法。其中普遍使用的有2种方法:一种被称作优先级继承(priority inheritance);另一种被称作优先级极限(priority ceilings)。

  1. 优先级继承(priority inheritance) 优先级继承是指将低优先级任务的优先级提升到等待它所占有的资源的最高优先级任务的优先级.当高优先级任务由于等待资源而被阻塞时,此时资源的拥有者的优先级将会自动被提升。

  2. 优先级天花板(priority ceilings)优先级天花板是指将申请某资源的任务的优先级提升到可能访问该资源的所有任务中最高优先级任务的优先级.(这个优先级称为该资源的优先级天花板)。

计算机网络

简述OSI七层协议

OSI七层协议包括:物理层,数据链路层,网络层,运输层,会话层,表示层, 应用层

简述TCP/IP五层协议

TCP/IP五层协议包括:物理层,数据链路层,网络层,运输层,应用层

物理层有什么作用

主要解决两台物理机之间的通信,通过二进制比特流的传输来实现,二进制数据表现为电流电压上的强弱,到达目的地再转化为二进制机器码。网卡、集线器工作在这一层。

数据链路层有什么作用

在不可靠的物理介质上提供可靠的传输,接收来自物理层的位流形式的数据,并封装成帧,传送到上一层;同样,也将来自上层的数据帧,拆装为位流形式的数据转发到物理层。这一层在物理层提供的比特流的基础上,通过差错控制、流量控制方法,使有差错的物理线路变为无差错的数据链路。提供物理地址寻址功能。交换机工作在这一层。

网络层有什么作用

将网络地址翻译成对应的物理地址,并决定如何将数据从发送方路由到接收方,通过路由选择算法为分组通过通信子网选择最佳路径。路由器工作在这一层。

传输层有什么作用

传输层提供了进程间的逻辑通信,传输层向高层用户屏蔽了下面网络层的核心细节,使应用程序看起来像是在两个传输层实体之间有一条端到端的逻辑通信信道。

会话层有什么作用

建立会话:身份验证,权限鉴定等; 保持会话:对该会话进行维护,在会话维持期间两者可以随时使用这条会话传输局; 断开会话:当应用程序或应用层规定的超时时间到期后,OSI会话层才会释放这条会话。

表示层有什么作用

对数据格式进行编译,对收到或发出的数据根据应用层的特征进行处理,如处理为文字、图片、音频、视频、文档等,还可以对压缩文件进行解压缩、对加密文件进行解密等。

应用层有什么作用

提供应用层协议,如HTTP协议,FTP协议等等,方便应用程序之间进行通信。

TCP与UDP区别

TCP作为面向流的协议,提供可靠的、面向连接的运输服务,并且提供点对点通信 UDP作为面向报文的协议,不提供可靠交付,并且不需要连接,不仅仅对点对点,也支持多播和广播

为何TCP可靠

TCP有三次握手建立连接,四次挥手关闭连接的机制。 除此之外还有滑动窗口和拥塞控制算法。最最关键的是还保留超时重传的机制。 对于每份报文也存在校验,保证每份报文可靠性。

为何UDP不可靠

UDP面向数据报无连接的,数据报发出去,就不保留数据备份了。 仅仅在IP数据报头部加入校验和复用。 UDP没有服务器和客户端的概念。 UDP报文过长的话是交给IP切成小段,如果某段报废报文就废了。

简述TCP粘包现象

TCP是面向流协议,发送的单位是字节流,因此会将多个小尺寸数据被封装在一个tcp报文中发出去的可能性。 可以简单的理解成客户端调用了两次send,服务器端一个recv就把信息都读出来了。

TCP粘包现象处理方法

固定发送信息长度,或在两个信息之间加入分隔符。

简述TCP协议的滑动窗口

滑动窗口是传输层进行流量控制的一种措施,接收方通过通告发 送方自己的窗口大小,从而控制发送方的发送速度,防止发送方发送速度过快而导致自己被淹没。

简述TCP协议的拥塞控制

拥塞是指一个或者多个交换点的数据报超载,TCP又会有重传机制,导致过载。 为了防止拥塞窗口cwnd增长过大引起网络拥塞,还需要设置一个慢开始门限ssthresh状态变量.

当cwnd < ssthresh 时,使用慢开始算法。 当cwnd > ssthresh 时,停止使用慢开始算法而改用拥塞避免算法。 当cwnd = ssthresh 时,即可使用慢开始算法,也可使用拥塞避免算法。

慢开始:由小到大逐渐增加拥塞窗口的大小,每接一次报文,cwnd指数增加。

拥塞避免:cwnd缓慢地增大,即每经过一个往返时间RTT就把发送方的拥塞窗口cwnd加1。

快恢复之前的策略:发送方判断网络出现拥塞,就把ssthresh设置为出现拥塞时发送方窗口值的一半,继续执行慢开始,之后进行拥塞避免。

快恢复:发送方判断网络出现拥塞,就把ssthresh设置为出现拥塞时发送方窗口值的一半,并把cwnd设置为ssthresh的一半,之后进行拥塞避免。

简述快重传

如果在超时重传定时器溢出之前,接收到连续的三个重复冗余ACK,发送端便知晓哪个报文段在传输过程中丢失了,于是重发该报文段,不需要等待超时重传定时器溢出再发送该报文。

TCP三次握手过程

  1. 第一次握手:客户端将标志位SYN置为1,随机产生一个值序列号seq=x,并将该数据包发送给服务端,客户端 进入syn_sent状态,等待服务端确认。
  2. 第二次握手:服务端收到数据包后由标志位SYN=1知道客户端请求建立连接,服务端将标志位SYN和 ACK都置为1,ack=x+1,随机产生一个值seq=y,并将该数据包发送给客户端以确认连接请求,服务端进入syn_rcvd状态。
  3. 第三次握手:客户端收到确认后检查,如果正确则将标志位ACK为1,ack=y+1,并将该数据包发送给服务端,服务端进行检查如果正确则连接建立成功,客户端和服务端进入established状态,完成三次握手,随后客户端和服务端之间可以开始传输 数据了

为什么TCP握手需要三次,两次行不行?

不行。TCP进行可靠传输的关键就在于维护一个序列号,三次握手的过程即是通信双方相互告知序列号起始值, 并确认对方已经收到了序列号起始值。

如果只是两次握手, 至多只有客户端的起始序列号能被确认, 服务器端的序列号则得不到确认。

简述半连接队列

TCP握手中,当服务器处于SYN_RCVD 状态,服务器会把此种状态下请求连接放在一个队列里,该队列称为半连接队列。

简述SYN攻击

SYN攻击即利用TCP协议缺陷,通过发送大量的半连接请求,占用半连接队列,耗费CPU和内存资源。

优化方式:

  1. 缩短SYN Timeout时间
  2. 记录IP,若连续受到某个IP的重复SYN报文,从这个IP地址来的包会被一概丢弃。

TCP四次挥手过程

  1. 第一次挥手:客户端发送一个FIN,用来关闭客户端到服务端的数据传送,客户端进入fin_wait_1状态。
  2. 第二次挥手:服务端收到FIN后,发送一个ACK给客户端,确认序号为收到序号+1,服务端进入Close_wait状态。此时TCP连接处于半关闭状态,即客户端已经没有要发送的数据了,但服务端若发送数据,则客户端仍要接收。
  3. 第三次挥手:服务端发送一个FIN,用来关闭服务端到客户端的数据传送,服务端进入Last_ack状态。
  4. 第四次挥手:客户端收到FIN后,客户端进入Time_wait状态,接着发送一个ACK给服务端,确认后,服务端进入Closed状态,完成四次挥手。

为什么TCP挥手需要4次

主要原因是当服务端收到客户端的 FIN 数据包后,服务端可能还有数据没发完,不会立即close。

所以服务端会先将 ACK 发过去告诉客户端我收到你的断开请求了,但请再给我一点时间,这段时间用来发送剩下的数据报文,发完之后再将 FIN 包发给客户端表示现在可以断了。之后客户端需要收到 FIN 包后发送 ACK 确认断开信息给服务端。

为什么四次挥手释放连接时需要等待2MSL

MSL即报文最大生存时间。设置2MSL可以保证上一次连接的报文已经在网络中消失,不会出现与新TCP连接报文冲突的情况。

简述DNS协议

DNS协议是基于UDP的应用层协议,它的功能是根据用户输入的域名,解析出该域名对应的IP地址,从而给客户端进行访问。

简述DNS解析过程

1、客户机发出查询请求,在本地计算机缓存查找,若没有找到,就会将请求发送给dns服务器

2、本地dns服务器会在自己的区域里面查找,找到即根据此记录进行解析,若没有找到,就会在本地的缓存里面查找

3、本地服务器没有找到客户机查询的信息,就会将此请求发送到根域名dns服务器

4、根域名服务器解析客户机请求的根域部分,它把包含的下一级的dns服务器的地址返回到客户机的dns服务器地址

5、客户机的dns服务器根据返回的信息接着访问下一级的dns服务器

6、这样递归的方法一级一级接近查询的目标,最后在有目标域名的服务器上面得到相应的IP信息

7、客户机的本地的dns服务器会将查询结果返回给我们的客户机

8、客户机根据得到的ip信息访问目标主机,完成解析过程

简述HTTP协议

http协议是超文本传输协议。它是基于TCP协议的应用层传输协议,即客户端和服务端进行数据传输的一种规则。该协议本身HTTP 是一种无状态的协议。

HTTP 协议本身是无状态的,为了使其能处理更加复杂的逻辑,HTTP/1.1 引入 Cookie 来保存状态信息。

Cookie是由服务端产生的,再发送给客户端保存,当客户端再次访问的时候,服务器可根据cookie辨识客户端是哪个,以此可以做个性化推送,免账号密码登录等等。

简述session

session用于标记特定客户端信息,存在在服务器的一个文件里。 一般客户端带Cookie对服务器进行访问,可通过cookie中的session id从整个session中查询到服务器记录的关于客户端的信息。

简述http状态码和对应的信息

1XX:接收的信息正在处理

2XX:请求正常处理完毕

3XX:重定向

4XX:客户端错误

5XX:服务端错误

常见错误码: 301:永久重定向 302:临时重定向 304:资源没修改,用之前缓存就行 400:客户端请求的报文有错误 403:表示服务器禁止访问资源 404:表示请求的资源在服务器上不存在或未找到

转发和重定向的区别

转发是服务器行为。服务器直接向目标地址访问URL,将相应内容读取之后发给浏览器,用户浏览器地址栏URL不变,转发页面和转发到的页面可以共享request里面的数据。

重定向是利用服务器返回的状态码来实现的,如果服务器返回301或者302,浏览器收到新的消息后自动跳转到新的网址重新请求资源。用户的地址栏url会发生改变,而且不能共享数据。

简述http1.0

规定了请求头和请求尾,响应头和响应尾(get post)

每一个请求都是一个单独的连接,做不到连接的复用

简述http1.1的改进

HTTP1.1默认开启长连接,在一个TCP连接上可以传送多个HTTP请求和响应。使用 TCP 长连接的方式改善了 HTTP/1.0 短连接造成的性能开销。

支持管道(pipeline)网络传输,只要第一个请求发出去了,不必等其回来,就可以发第二个请求出去,可以减少整体的响应时间。

服务端无法主动push

简述HTTP短连接与长连接区别

HTTP中的长连接短连接指HTTP底层TCP的连接。

短连接: 客户端与服务器进行一次HTTP连接操作,就进行一次TCP连接,连接结束TCP关闭连接。

长连接:如果HTTP头部带有参数keep-alive,即开启长连接网页完成打开后,底层用于传输数据的TCP连接不会直接关闭,会根据服务器设置的保持时间保持连接,保持时间过后连接关闭。

简述http2.0的改进

提出多路复用。多路复用前,文件时串行传输的,请求a文件,b文件只能等待,并且连接数过多。引入多路复用,a文件b文件可以同时传输。

引入了二进制数据帧。其中帧对数据进行顺序标识,有了序列id,服务器就可以进行并行传输数据。

http与https的区别

http所有传输的内容都是明文,并且客户端和服务器端都无法验证对方的身份。 https具有安全性的ssl加密传输协议,加密采用对称加密, https协议需要到ca申请证书,一般免费证书很少,需要交费。

简述TLS/SSL, HTTP, HTTPS的关系

SSL全称为Secure Sockets Layer即安全套接层,其继任为TLSTransport Layer Security传输层安全协议,均用于在传输层为数据通讯提供安全支持。

可以将HTTPS协议简单理解为HTTP协议+TLS/SSL

https的连接过程

  1. 浏览器将支持的加密算法信息发给服务器
  2. 服务器选择一套浏览器支持的加密算法,以证书的形式回发给浏览器
  3. 客户端(SSL/TLS)解析证书验证证书合法性,生成对称加密的密钥,我们将该密钥称之为client key,即客户端密钥,用服务器的公钥对客户端密钥进行非对称加密。
  4. 客户端会发起HTTPS中的第二个HTTP请求,将加密之后的客户端对称密钥发送给服务器
  5. 服务器接收到客户端发来的密文之后,会用自己的私钥对其进行非对称解密,解密之后的明文就是客户端密钥,然后用客户端密钥对数据进行对称加密,这样数据就变成了密文。
  6. 服务器将加密后的密文发送给客户端
  7. 客户端收到服务器发送来的密文,用客户端密钥对其进行对称解密,得到服务器发送的数据。这样HTTPS中的第二个HTTP请求结束,整个HTTPS传输完成

Get与Post区别

Get:指定资源请求数据,刷新无害,Get请求的数据会附加到URL中,传输数据的大小受到url的限制。

Post:向指定资源提交要被处理的数据。刷新会使数据会被重复提交。post在发送数据前会先将请求头发送给服务器进行确认,然后才真正发送数据。

Get方法参数有大小限制吗

一般HTTP协议里并不限制参数大小限制。但一般由于get请求是直接附加到地址栏里面的,由于浏览器地址栏有长度限制,因此使GET请求在浏览器实现层面上看会有长度限制。

了解REST API吗

REST API全称为表述性状态转移(Representational State Transfer,REST)即利用HTTP中get、post、put、delete以及其他的HTTP方法构成REST中数据资源的增删改查操作:

  • Create : POST
  • Read : GET
  • Update : PUT/PATCH
  • Delete: DELETE

浏览器中输入一个网址后,具体发生了什么

  1. 进行DNS解析操作,根据DNS解析的结果查到服务器IP地址
  2. 通过ip寻址和arp,找到服务器,并利用三次握手建立TCP连接
  3. 浏览器生成HTTP报文,发送HTTP请求,等待服务器响应
  4. 服务器处理请求,并返回给浏览器
  5. 根据HTTP是否开启长连接,进行TCP的挥手过程
  6. 浏览器根据收到的静态资源进行页面渲染

评论