计算机基础
操作系统历史:
手工操作--穿孔卡片
概述:
将对应于程序和数据的已穿孔的纸带(或卡片)装入输入机,然后启动输入机把程序和数据输入计算机内存,接着通过控制台开关启动程序针对数据运行;计算完毕,打印机输出计算结果;用户取走结果并卸下纸带(或卡片)后,才让下一个用户上机。
缺点:
手工操作的慢速度和计算机的高速度
批处理系统:
加载在计算机上的一个系统软件,在它的控制下,计算机能够自动地、成批地处理一个或多个用户的作业(这作业包括程序、数据和命令)。
联机批处理系统:
自动读取磁带储存,但cpu还是空闲。作业的输入/输出由CPU来处理。成批地读入、输出。
脱机批处理系统:
增加一台不与主机直接相连而专门用于与输入/输出设备打交道的卫星机。IO脱离cpu,但cpu读取数据时还是空闲
多道程序系统:
指允许多个程序同时进入内存并允许它们交替在CPU中运行。单处理机系统中多道程序
多道批处理系统:
多个任务IO时切换,但不提供人机交互功能,一个任务运行时独占cpu
分时系统:
把cpu运行时间分成很短的时间片,按时间片轮流把cpu分配给各联机作业使用。
对用户响应的及时性,即不至于用户等待每一个命令的处理时间过长。切换任务会降低cpu效率
实时系统:
cpu实时响应,即使空闲也不会执行其他任务。一般都是单片机,PLC等
通用操作系统:
兼容多道批处理系统、分时系统、实时系统的两种或三种功能的系统
进程、线程与协程:
CPU相关知识:
并行度:
一核只能同时干一件事,能切换干很多事情是有着不同程序的执行上下文。
多级反馈队列:
设置多个就绪队列,并为各个队列赋予不同的优先级。优先级越高,时间片越短,越先执行。
程序打开的过程:
创建或双击后,处于就绪状态。cpu开始进程调度,完成后释放资源。
如果未完成,时间片到,返回就绪状态,等待下一次调度。
如果发生事件请求,释放cpu,阻塞直到事件完成,再回到就绪状态。
进程:
概述:
一个程序实例一个进程,里面包括各种资源的调用。仅是资源的合集。
进程要操作cpu,必须先创建一个线程。操作系统动态执行的基本单元。每一个进程都有它自己的地址空间。
多个线程之间共享所有的资源,进程启动时随着启动一个单线程,一般叫主线程,通过线程创建线程。
由于进程之间的数据需要各自持有一份,所以创建进程需要的非常大的开销。
进程切换:
内核必须有能力挂起正在CPU上运行的进程,并恢复以前挂起的某个进程的执行。这种行为被称为进程切换。
从一个进程的运行转到另一个进程上运行,这个过程中经过下面这些变化:
1. 保存处理机上下文,包括程序计数器和其他寄存器。
2. 更新PCB信息。
3. 把进程的PCB移入相应的队列,如就绪、在某事件阻塞等队列。
4. 选择另一个进程执行,并更新其PCB。
5. 更新内存管理的数据结构。
6. 恢复处理机上下文。
进程的阻塞:
正在执行的进程,由于期待的某些事件未发生,如请求系统资源失败、等待某种操作的完成、新数据尚未到达
或无新工作做等,则由系统自动执行阻塞原语(Block),使自己由运行状态变为阻塞状态。
可见,进程的阻塞是进程自身的一种主动行为,也因此只有处于运行态的进程(获得CPU),
才可能将其转为阻塞状态。当进程进入阻塞状态,是不占用CPU资源的。
阻塞:某些工作(发请求)须交给操作系统硬件来完成,将请求从用户空间拷贝到内核缓冲区,
然后由网卡发给对方的内核缓冲区,再到程序的地址空间。
通信方式:
管道:
一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
有命名管道和非命名管道之分,非命名管道只能用于父子进程通讯,命名管道可用于非父子进程。
命名管道就是FIFO,一种先进先出的队列。它类似于一个管道,只允许数据的单向流动。每个FIFO都有一个名字,允许不相关的进程访问同一个FIFO,因此也成为命名管道。
速度慢,容量有限
消息队列queue:
用于两个进程之间的通讯,首先在一个进程中创建一个消息队列,然后再往消息队列中写数据,而另一个进程则从那个消息队列中取数据。
容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题。
共享内存:
最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。
能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全,当然,共享内存区同样可以用作线程间通讯,不过没这个必要,线程间本来就已经共享了一块内存的。
套接字socket:
信号:
不能传递复杂消息,只能用来同步。
孤儿进程,僵尸进程?
孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
孤儿进程并不会有什么危害。
僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程。
在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。
但是仍然为其保留一定的信息(包括进程号the process ID,退出状态the termination status of the process,运行时间the amount of CPU time taken by the process等)。直到父进程通过wait / waitpid来取时才释放。
但这样就导致了问题,如果进程不调用wait / waitpid的话, 那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,
如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。
僵死进程并不是问题的根源,罪魁祸首是产生出大量僵死进程的那个父进程。因此,当我们寻求如何消灭系统中大量的僵死进程时,答案就是把产生大 量僵死进程的那个元凶枪毙掉(也就是通过kill发送SIGTERM或者SIGKILL信号啦)。
枪毙了元凶进程之后,它产生的僵死进程就变成了孤儿进程,这些孤儿进程会被init进程接管,init进程会wait()这些孤儿进程,释放它们占用的系统进程表中的资源,这样,这些已经僵死的孤儿进程 就能释放了。
线程:
概述:
操作系统能够进行运算调度的最小单位。一堆指令,进程中的实际运作单位。(60年代基本单位是进程,但开销大,改进了)
可看作cpu执行一堆指令所需的执行内容。一个线程(执行内容)包含cpu的寄存器值和栈。
栈保留着当前线程的独有变量。而代码、数据、文件等放在进程。
线程之间共享进程内的资源。
资源私有:
所属线程的栈区、程序计数器、栈指针以及函数运行使用的寄存器,以上这些信息有一个统一的名字,就是线程上下文,thread context。
在程序块开始时自动分配内存,结束时自动释放内存
向低地址扩展
分段栈:
golang中的栈会调用runtime.morestack和runtime.newstack创建一个新的栈空间,这些栈空间是不连续的,但是当前 goroutine 的多个栈空间会以双向链表的形式串联起来。
分段栈虽然能够按需为当前 goroutine 分配内存并且及时减少内存的占用。
但是它也存在一个比较大的问题:
如果当前 goroutine 的栈几乎充满,那么任意的函数调用都会触发栈的扩容,当函数返回后又会触发栈的收缩,如果在一个循环中调用函数,栈的分配和释放就会造成巨大的额外开销,这被称为热分裂问题(Hot split)。
连续栈:
每当程序的栈空间不足时,初始化一片比旧栈大两倍的新栈并将原栈中的所有值都迁移到新的栈中,新的局部变量或者函数调用就有了充足的内存空间。
资源共有:
除此之外,剩下的都是线程间共享资源。
堆区:
变量的地址,也就是指针,任何一个线程都可以访问指针指向的数据,因此堆区也是线程共享的属于进程的资源。分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间。
运行时堆,用malloc创建的
堆的申请释放工作由程序员控制
由程序员分配和释放,用于存放进程运行中被动态分配的内存段
在golang中有gc实现自动释放,场景是一些大文件读取时,会动态申请的内存空间。
堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。
BBS区未初始化数据区:
全局未初始化变量
未初始化静态变量
数据区:
存放的就是所谓的被初始化后的静态变量、全局变量、常量
代码区:
编译后的可执行机器指令。存放程序的代码,即CPU执行的机器指令,并且是只读的
与进程的区别:
根本区别:进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位
资源开销:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。
包含关系:如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。
内存共享:同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的,要想通信必须通过一个中间代理
子代的区别:
1. 子进程之间的数据不共享,对父进程的克隆。线程之间则共享数据.
2. 新线程很任意创建,子进程要想创建必须对父进程克隆所有的数据。
3. 一个线程可以操作其他的线程,进程只能子进程。
4. 对主线程操作(优先级,取消等)会影响其他线程,但父进程和子进程间没影响。
协程:
协程的适用场景:
当程序中存在大量不需要CPU的操作时(IO),适用于协程;代码级别的线程
协程和线程的主要区别:
一个是运行在内核态,一个是运行在用户态
线程和进程的操作是由程序触发系统接口,最后的执行者是系统;协程的操作则是程序员。
内存:
虚拟内存:
概述:
计算机系统内存管理的一种技术。对操作系统来说,虚拟内存就是一张张的对照表,P1 获取 A 内存里的数据时应该去物理内存的 A 地址找,而找 B 内存里的数据应该去物理内存的 C 地址。
通过虚拟内存机制,每个进程都以为自己占用了全部内存,进程访问内存时,操作系统都会把进程提供的虚拟内存地址转换为物理地址,再去对应的物理地址上获取数据。
通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。
通过引入虚拟内存,每个进程都有自己独立的虚拟地址空间,这个空间理论上可以无限大,因为它并不要钱。一个进程同一时刻不可能所有变量数据都会访问到,只需要在访问某部分数据时,把这一块虚拟内存映射到物理内存,
其他没有实际访问过的虚拟地址空间并不会占用到物理内存,这样对物理内存的消耗就大大减少了 。
其它暂时不需要的内容交换到硬盘存储即可。
与没有使用虚拟内存技术的系统相比,使用这种技术使得大型程序的编写变得更容易,对真正的物理内存(例如RAM)的使用也更有效率。此外,虚拟内存技术可以使多个进程共享同一个运行库,并通过分割不同进程的内存空间来提高系统的安全性。
引入虚拟内存后,让内存的并发访问问题的粒度从多进程级别,降低到多线程级别。
这是更快分配内存的第一个层次。
作用:
虚拟内存不仅通过内存地址转换解决了多个进程访问内存冲突的问题,还带来更多的益处。
有助于进程进行内存管理:
内存完整性:每个进程都认为自己获取的内存是一块连续的地址。
安全:由于进程访问内存时,都要通过页表来寻址,操作系统在页表的各个项目上添加各种访问权限标识位,就可以实现内存的权限控制。
通过swap扩充内存。
Swap交换内存:
含义:
指的是一個交換分區或文件
分类:
交换分区(swap分区)和交换文件(swap文件)
作用:
主要是在內存不夠用的時候,將部分內存上的數據交換到swap空間上,以便讓系統不會因內存不夠用而導致oom或者更致命的情況出現。
实现:
有分页式、段式、段页式3种
页式调度是将逻辑和物理地址空间都分成固定大小的页。主存按页顺序编号,而每个独立编址的程序空间有自己的页号顺序,通过调度辅存中程序的各页可以离散装入主存中不同的页面位置,并可据表一一对应检索。
优点是页内零头小,页表对程序员来说是透明的,地址变换快,调入操作简单;没有外碎片,每个内碎片不超过页的大小。
缺点是各页不是程序的独立模块,不便于实现程序和数据的保护。程序全部装入内存,要求有相应的硬件支持。
页式管理的基本思想是:为了更好地利用分区存储管理中所产生的"零头"问题,允许把一个作业存放在不连续的内存块中,又可以连续运行,它允许只调入用户作业中常用部分,不常用部分不长期驻留内存,有效提高了内存的利用率。
段式调度是按程序的逻辑结构划分地址空间,段的长度是随意的,并且允许伸长,它的优点是消除了内存零头,易于实现存储保护,便于程序动态装配;缺点是调入操作复杂,会产生碎片。
把程序按内容或过程(函数)关系分成段,每段有自己的名字。一个用户作业或进程所包含的段对应于一个二维线性虚拟空间,也就是一个二维虚拟存储器。段式管理程序以段为单位分配内存,
然后通过地址映射机构把段式虚拟存储地址转化为内存中的实际地址。
将这两种方法结合起来便构成段页式调度。在段页式调度中把物理空间分成页,程序按模块分段,每个段再分成与物理空间页同样小的页面。段页式调度综合了段式和页式的优点。其缺点是增加了硬件成本,软件也较复杂。
大型通用计算机系统多数采用段页式调度。
Linux 系统主要采用了分页管理,但是由于 Intel 处理器的发展史,Linux 系统无法避免分段管理。
操作系统限制:
在32位操作系统中,程序能使用的最大内存是 4GB,也就是2的32次方。
内存管理:
伙伴系统:
伙伴系统是一个结合了2的方幂个分配器和空闲缓冲区合并计技术的内存分配方案, 其基本思想很简单. 内存被分成含有很多页面的大块, 每一块都是2个页面大小的方幂.
如果找不到想要的块, 一个大块会被分成两部分, 这两部分彼此就成为伙伴. 其中一半被用来分配, 而另一半则空闲. 这些块在以后分配的过程中会继续被二分直至产生一个所需大小的块.
当一个块被最终释放时, 其伙伴将被检测出来, 如果伙伴也空闲则合并两者.
内存buffer和cache:
Buffer cache缓冲区缓存
主要是设计用来在系统对块设备进行读写的时候,对块进行数据缓存的系统来使用。
Page cache页面缓存
Page cache 主要用来作为文件系统上的文件数据的缓存来用,尤其是针对当进程对文件有 read/write 操作的时候。
一般情况下两个缓存系统是一起配合使用的,比如当我们对一个文件进行写操作的时候,page cache 的内容会被改变,而 buffer cache 则可以用来将 page 标记为不同的缓冲区,并记录是哪一个缓冲区被修改了。
堆和栈:
程序运行时,每个线程分配一个栈,每个进程分配一个堆。也就是说,栈是线程独占的,堆是线程共用的。
此外,栈创建的时候,大小是确定的,数据超过这个大小,就发生stack overflow错误,而堆的大小是不确定的,需要的话可以不断增加。
栈:栈是一种线性的数据结构,读取规则是先进后出。栈中的数据占用的内存空间的大小是确定的,便于代码执行时的入栈、出栈操作,并由系统自动分配和自动释放内存可以及时得到回收,相对于堆来说,更加容易管理内存空间。
栈是向低地址扩展的数据结构,是一块连续的内存的区域。
堆:堆是一种树形数据结构,读取相对复杂。堆是动态分配内存,内存大小不一,也不会自动释放。栈中的数据长度不定,且占空间比较大。便于开辟内存空间,更加方便存储。
堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。
堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便.
用户空间与内核空间:
概述:
操心系统将虚拟内存划分为两部分,一部分为内核空间,一部分为用户空间。
根据寻址空间来划分
用户态和系统态有什么区别:
1.内核态与用户态是操作系统的两种运行级别,当程序运行在3级特权级上时,就可以称之为运行在用户态。因为这是最低特权级,是普通的用户进程运行的特权级,大部分用户直接面对的程序都是运行在用户态。
2.当程序运行在0级特权级上时,就可以称之为运行在内核态。
3.运行在用户态下的程序不能直接访问操作系统内核数据结构和程序。当我们在系统中执行一个程序时,大部分时间是运行在用户态下的,在其需要操作系统帮助完成某些它没有权力和能力完成的工作时就会切换到内核态。
4.这两种状态的主要差别是:
处于用户态执行时,进程所能访问的内存空间和对象受到限制,其所处于占有的处理机是可被抢占的 ;
而处于核心态执行中的进程,则能访问所有的内存空间和对象,且所占有的处理机是不允许被抢占的。
用户态到内核态的切换:
概述:
系统在运行时由用户态转到内核态,最主要方式有3种,其中系统调用可以认为是用户进程主动发起的,异常和外围设备中断则是被动的。
分类:
1.系统调用
这是用户态进程主动要求切换到内核态的一种方式,用户态进程通过系统调用申请使用操作系统提供的服务程序完成工作。比如前例中fork()实际上就是执行了一个创建新进程的系统调用。
而系统调用的机制其核心还是使用了操作系统为用户特别开放的一个中断来实现,例如Linux的int 80h中断。
2.异常
当CPU在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常。
3.外围设备的中断
当外围设备完成用户请求的操作后,会向CPU发出相应的中断信号,这时CPU会暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序,
如果先前执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了由用户态到内核态的切换。比如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后续操作等。。
IO多路复用:
背景:
文件描述符fd
一个用于表述指向文件的引用的抽象化概念。
文件描述符在形式上是一个非负整数。实际上,它是一个索引值,
指向着内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,
内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。
但是文件描述符这一概念往往只适用于UNIX、Linux这样的操作系统。
# 注:记录表是有序列表,包含不同文件的文件句柄(对象),有着唯一的文件描述符
# 文件描述符在内核接收数据后发生变化
#
缓存 I/O
缓存 I/O 又被称作标准 I/O,大多数文件系统的默认 I/O 操作都是缓存 I/O。在 Linux 的缓存 I/O 机制中,
操作系统会将 I/O 的数据缓存在文件系统的页缓存(page cache)中,
也就是说,数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。
# 即操作系统内核的缓冲区,减少频繁I/O操作而引起频繁的系统调用
缓存 I/O 的缺点:
数据在传输过程中需要在应用程序地址空间和内核进行多次数据拷贝操作,
这些数据拷贝操作所带来的 CPU 以及内存开销是非常大的。
事件驱动模型:
概述:
包含一个事件循环,当外部事件发生时使用回调机制来触发相应的处理。
在事件驱动版本的程序中,任务交错执行,但仍然在一个单独的线程控制中。
当处理I/O或者其他昂贵的操作时,注册一个回调到事件循环中,然后当I/O操作完成时继续执行。回调描述了该如何处理某个事件。
事件循环阻塞等待,当事件到来时将它们分配给等待处理事件的回调函数。
优点:
这种方式让程序尽可能的得以执行而不需要用到额外的线程。
事件驱动型程序比多线程程序更容易推断出行为,因为程序员不需要关心线程安全问题。
流程:
1.遇到IO阻塞时,注册事件(句柄)到事件循环中,继续执行其他动作;# 其他动作包括继续发请求和启动事件循环
2.事件循环正式启动后,事件循环轮询进行监听(监听涉及描述符和内核);
# 事件触发后循环轮询查找哪个事件句柄发生变化(select)或者自动触发给循环(epoll);
3.当IO阻塞完时改变\触发事件(句柄),事件循环轮询所有事件,找到后,调用相应函数;
4.执行完相应函数,再继续循环。
特点:
1.由事件的触发顺序决定处理的顺序,这一特性往往被用于保证某些过程的原子化。
2.事件驱动的程序的行为,完全受外部输入的事件控制,事件驱动的系统中,存在大量这种程序,
并以事件作为主要的通信方式。
3.事件驱动的程序,必定会直接或者间接拥有一个事件队列,用于存储未能及时处理的事件。
4.比较典型的事件驱动的程序就是一个死循环,并以一个线程的形式存在,包括接收和处理两部分
- 按照一定的条件接收并选择一个要处理的事件
- 事件的处理过程
当没有任何事件触发时,程序会因查询事件队列失败而进入睡眠状态,从而释放cpu
IO模式:
概述:
对于一次IO访问(以read举例),数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。
所以说,当一个read操作发生时,它会经历两个阶段:
1. 等待数据准备 (Waiting for the data to be ready)(判断是否异步)
2. 将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)(判断是否阻塞)
正因为这两个阶段,linux系统产生了下面五种网络模式的方案。
1.阻塞 I/O(blocking IO)
2.非阻塞 I/O(nonblocking IO)
3.I/O 多路复用( IO multiplexing)
4.信号驱动 I/O( signal driven IO)
5.异步 I/O(asynchronous IO)
1.阻塞 I/O(blocking IO)
概述:
当用户进程调用了recvfrom这个系统调用,kernel就开始了
IO的第一个阶段:准备数据
(对于网络IO来说,很多时候数据在一开始还没有到达。比如,还没有收到一个完整的UDP包。
这个时候kernel就要等待足够的数据到来)。这个过程需要等待,也就是说数据被拷贝到
操作系统内核的缓冲区中是需要一个过程的。而在用户进程这边,整个进程会被阻塞
(当然,是进程自己选择的阻塞)。
IO的第二个阶段:当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,
然后kernel返回结果,用户进程才解除block的状态,重新运行起来。
常见的阻塞形式:
网络I/O阻塞、磁盘I/O阻塞、用户输入阻塞等。
特点:
在IO执行的两个阶段都被block。
2.非阻塞 I/O(nonblocking IO)
概述:
设置socket使其变为non-blocking
当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。
从用户进程角度讲 ,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,
于是它可以再次发送read操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存,然后返回。
特点:
用户进程需要不断的主动询问kernel数据好了没有。
当返回error时,可以try except捕捉到,然后干其他事情,过段时间再返回进行read看看有没有
缺点:
任务完成的响应延迟增大了,因为每过一段时间才去轮询一次read操作,而任务可能在两次轮询之间的任意时间完成。这会导致整体数据吞吐量的降低。
3.I/O 多路复用( IO multiplexing)
概述:
当用户进程调用了select/poll/epoll函数,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,
当任何一个socket中的数据准备好了,select就会返回。
这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。
特点:
通过select,poll,epoll机制一个进程能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态,select()函数就可以返回。
需要在文件描述符就绪后再次调用IO读写操作,这个读写过程是阻塞的。
与异步io的区别:
I/O 多路复用在后一步阻塞。而异步IO则都不阻塞,有信号时数据已经在程序内存准备好了。
4.异步 I/O(asynchronous IO)
概述:
用户进程发起read操作之后,立刻就可以开始去做其它的事。
而另一方面,从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。
然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。
真正异步需调用操作系统接口,完成数据检测、内核准备等工作
特点:
通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。
无需再次进行读写系统调用,它的实现就会负责内核空间到用户空间的数据拷贝,并通知(内核通知应用程序,属于被动通知)应用程序数据就绪。
Select、Poll、Epoll模型:
Select:
概述:
监视的文件描述符分3类,分别是writefds、readfds、和exceptfds。创建3个文件描述符集并拷贝到内核中,分别监听读、写、异常动作。
当select函数返回后,将之前传入的fd_set拷贝传出到用户态并返回就绪的文件描述符总数。可以通过遍历fdset,来找到就绪的描述符。
描述符的复制给内核,以及描述符的线性扫描,导致开销与描述符的多少有关。
触发模式:
水平触发
优点:
良好跨平台支持
缺点:
打开的socket数量有限,与打开的最多文件数相同,默认1024
Poll:
概述:
它和select在本质上没有多大差别,但是poll没有最大文件描述符数量的限制。
返回后,之前传入的fd数组拷贝传出用户态并返回就绪的文件描述符总数。遍历。
将传入的struct pollfd结构体数组拷贝到内核中进行监听。
触发模式:
水平触发。
适合场景:
一般不用,过渡阶段
Epoll:
概述:
1.执行epoll_create会在内核的高速cache区中建立一颗红黑树以及就绪链表(该链表存储已经就绪的文件描述符)。
2.接着用户执行的epoll_ctl函数添加文件描述符会在红黑树上增加相应的结点,并注册了回调函数。
3.采用基于事件的就绪通知方式,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,检测到某文件描述符可读or可写时会调用回调函数,该回调函数将文件描述符放在就绪链表中,进程调用epoll_wait()时便得到通知。
4.epoll_wait只用观察就绪链表中有无数据即可,最后将链表的数据返回给数组并返回就绪的数量。内核将就绪的文件描述符放在传入的数组中,所以只用遍历依次处理即可。这里返回的文件描述符是通过mmap让内核和用户空间共享同一块内存实现传递的,减少了不必要的拷贝。
底层结构:
int epoll_create(int size) 创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大,当创建好epoll句柄后,它就会占用一个fd值,
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); 函数是对指定描述符fd执行op操作。
epfd:是epoll_create()的返回值。
op:表示op操作,用三个宏来表示:添加EPOLL_CTL_ADD,删除EPOLL_CTL_DEL,修改EPOLL_CTL_MOD。分别添加、删除和修改对fd的监听事件。
fd:是需要监听的fd(文件描述符)
epoll_event:是告诉内核需要监听什么事
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout); 等待epfd上的io事件,最多返回maxevents个事件。
参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,
这个maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间
触发模式:
epoll可以同时支持水平触发和边缘触发
适合场景:
虽然epoll的性能最好,但是在连接数少并且连接都十分活跃的情况下,select和poll的性能可能比epoll好,毕竟epoll的通知机制需要很多函数回调。
三者区别:
1.select,poll实现需要自己不断轮询所有fd集合,直到设备就绪,期间可能要睡眠和唤醒多次交替。而epoll其实也需要调用epoll_wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但是它是设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在epoll_wait中进入睡眠的进程。虽然都要睡眠和交替,但是select和poll在“醒着”的时候要遍历整个fd集合,而epoll在“醒着”的时候只要判断一下就绪链表是否为空就行了,这节省了大量的CPU时间。这就是回调机制带来的性能提升。
2.poll的实现和select非常相似,poll没有最大文件描述符数量的限制。原因是它是基于链表来存储的.而select是fd_set数据结构是一个有大小的,相当与一个定长所数组。
3.select & poll在unix系统上都有,epoll只在linux上。
4.文件描述符是否拷贝,还是直接维护
触发方式:
水平触发(level trigger,LT):
概述:
如果文件描述符已经就绪可以非阻塞的执行IO操作了,此时会触发通知.允许在任意时刻重复检测IO的状态,没有必要每次描述符就绪后尽可能多的执行IO。select,poll就属于水平触发.
触发的时机:
对于读操作,只要缓冲内容不为空,LT模式返回读就绪。
对于写操作,只要缓冲区还不满,LT模式会返回写就绪。
缺点:有大量你不需要读写的就绪文件描述符,而它们每次都会返回,这样会大大降低处理程序检索自己关心的就绪文件描述符的效率。
优点:
当进行socket通信的时候,保证了数据的完整输出,进行IO操作的时候,如果还有数据,就会一直的通知你。
缺点:
于只要还有数据,内核就会不停的从内核空间转到用户空间,所有占用了大量内核资源,试想一下当有大量数据到来的时候,每次读取一个字节,这样就会不停的进行切换。内核资源的浪费严重。
边缘触发(edge trigger,ET):
概述:
如果文件描述符自上次状态改变后有新的IO活动到来,此时会触发通知.在收到一个IO事件通知后要尽可能的执行IO操作,因为如果在一次通知中没有执行完IO,
那么就需要等到下一次新的IO活动到来才能获取到就绪的描述符。
信号驱动式IO就属于边缘触发.
触发的时机:
对于读操作
当缓冲区由不可读变为可读的时候,即缓冲区由空变为不空的时候。
当有新数据到达时,即缓冲区中的待读数据变多的时候。
当缓冲区有数据可读,且应用进程对相应的描述符进行EPOLL_CTL_MOD 修改EPOLLIN事件时。
对于写操作
当缓冲区由不可写变为可写时。
当有旧数据被发送走,即缓冲区中的内容变少的时候。
当缓冲区有空间可写,且应用进程对相应的描述符进行EPOLL_CTL_MOD 修改EPOLLOUT事件时。
优点:
每次内核只会通知一次,大大减少了内核资源的浪费,提高效率。
缺点:
不能保证数据的完整。不能及时的取出所有的数据。容易漏数据不安全。
适合场景:
处理大数据。使用non-block模式的socket。
网络:
OSI 7层模型:
物理层:网卡,网线,集线器,中继器,调制解调器,建立、维护、断开物理连接。
数据链路层:网桥,交换机,建立逻辑连接、进行硬件地址寻址、差错校验 [2] 等功能。
网络层:路由器,进行逻辑地址寻址,实现不同网络之间的路径选择。IP ICMP
传输层:定义传输数据的协议端口号,以及流控和差错校验。协议有:TCP UDP
会话层:建立、管理、终止会话。
表示层: 数据的编码、解码、加解密、压解缩
应用层 协议HTTP FTP TFTP SMTP SNMP DNS Telnet HTTPS POP3 DHCP
Socket:
概述:
socket是应用层与TCP/IP协议族通信的中间软件抽象层,实现了复杂的TCP/IP协议族,把TCP/IP层复杂的操作抽象为几个简单的接口,供应用层调用实现进程在网络中的通信。
socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,应用程序通常通过"套接字"向网络发出请求或者应答网络请求。
一个文件,双向管道文件(server通过文件描述符进行文件的通信)。
socket起源于UNIX,在Unix一切皆文件哲学的思想下,socket是一种"打开—读/写—关闭"模式的实现,服务器和客户端各自维护一个"文件",在建立连接打开后,可以向自己文件写入内容供对方读取或者读取对方内容,通讯结束时关闭文件。
域名:
概述:
为了使在internet上机器的标识更加适合人们使用,网络主机通常被分配IP地址的同时还分配了一个名称
域名解析:
域名解析就是域名到IP地址的转换过程,在浏览器上输入网站域名时,会去请求DNS服务器获取该域名对应的IP地址,再去访问改地址。
DNS域名服务:
DNS域名服务负责实现主机名和IP地址间的翻译工作,而且负责将名称和i地址的对应关系的数据库在整个internet上分发
DNS是一个庞大的世界范围的分布式数据库,每个机构都维护数据库的一小部分,其中列有机构内部机器的DNS记录
IP:
一台机器在一个网络内唯一的标识,分为网络地址(高位两位)和主机部分(后两位,又可分为子网和主机地址)
对于B类地址来说是这样,子网掩码是255.255.255.0
其他可能不同,如绕回的127.0.0.1
网关ip:
不同局域网之间要想通信必须通过网关
可以有多个网关地址(与多个网络相连),只有一个地址可以作为默认路由
只有绕回的设备没有网关地址,独立的网络同样也没有网关地址
公网与内网ip关系:
一个局域网里所有电脑的内网IP是互不相同的,由路由器分配,但共用一个外网IP。
每台电脑的两个IP同时存在,一个对内(路由器分配的ip),一个对外(路由器即网关上的对应的以太网接口的外网ip)。
对于发出的请求:
1.如果是目的ip是localhost,本地路由表路由到lo绕回设备,请求分发到本地,请求ip是localhost
127.0.0.1同理
2.如果是目的ip是172.31.xx.xx,本地路由表路由到 路由器(网关)或 lo绕回设备,最终回到本地,请求ip都是172.31.xx.xx
- 路由到网关,请求ip是172.31.xx.xx(本机内网ip,由路由器分配的)
- 路由到lo,请求ip是172.31.xx.xx(这个不是localhost还不能理解,还没到达路由器怎么分配,可能是ip层获取到本地ip)
3.如果目的ip是其他外网ip,路由到进出口网关,请求ip是本地外网ip
内网与外网之间的通信:
公有 IP 和私有 IP 的区别
公有地址(Public address):由 Inter NIC(Internet Network Information Center 因特网信息中心)负责。这些 IP 地址分配给注册并向Inter NIC提出申请的组织机构,
公有 IP 全球唯一,通过它直接访问因特网(直接能上网)。
私有地址(Private address):内网地址
端口映射:
端口映射是 NAT 的一种,它将外网主机的 IP 地址的一个端口映射到内网中一台机器,提供相应的服务。当用户访问该 IP 的这个端口时,服务器自动将请求映射到对应局域网内部的机器上。
现在市场上的家庭路由器都具备 NAT 功能,也可以实现端口映射。
TPLINK的在虚拟服务器里面,填好外网端口、内网端口、以及内网ip即可
路由器,至少有两个端口:WAN 口和 LAN 口。
WAN:接外部 IP 地址用,通常指的是出口,转发来自内部 LAN 接口的 IP 数据包,这个口的 IP 是唯一的。
LAN:接内部 IP 地址用,LAN 内部是交换机。
Port端口:
概述:
端口具体是指接口电路中的一些寄存器,这些寄存器分别用来存放数据信息,控制信息和状态信息,相应的?分别称为数据端口、控制端口和状态端口。
电脑运行的系统程序,运行到这些端口是,会查看端口是否打开或者关闭,如果关闭,系统继续进行,如果打开,则接受外部数据并执行。
范围:
tcp层使用到的,范围是0-65535,2的16次方。
端口的唯一性的标识不是端口号,而是端口号和协议名称的组合。
分配方式:
端口号有两种基本分配方式:第一种叫全局分配这是一种集中分配方式,由一个公认权威的中央机构根据用户需要进行统一分配,并将结果公布于众,
第二种是本地分配,又称动态连接,即进程需要访问传输层服务时,向本地操作系统提出申请,操作系统返回本地唯一的端口号,进程再通过合适的系统调用,将自己和该端口连接起来(binding,绑定)。
参考pid分配:
除了ID外,它还包括跟这个ID相关的task列表、引用计数器和一个可以方便查找的hashed list。
问题:
服务器响应请求accept后,是新起一个端口号吗?
不是,需要TCP四元组才能确定一个TCP连接,源端口和目标端口都是必须的,也不会新开端口。
socket.accept()调用之后会返回一个新的“通讯描述符”(socket),而以前的那个socket仍然会重新调用accept()方法继续去监听,充当“监听描述符”。
mac:
在网卡上的全球唯一的地址,即网卡地址。网络层的ip负责计算机间的数据传输,
交换机(第二层)通过arp协议找到公网ip对应的mac后,传输数据给对应计算机
子网掩码:
一个比特模板,用来和ip进行按位与运行后比较结果,判断是否同一个子网。结果是局域网的网段,比较前三位
子网地址与网络地址不同,前两个字节是网络地址,后两个字节是主机地址
如128.17.75.20,根据子网掩码如255.255.255.0,则前三位是子网地址,表示128.17网络的75号子网,20是主机地址
绕回设备的地址总是127.0.0.1,子网掩码总是255.0.0.0,没有子网地址.
网段与掩码:
要想判断两个ip地址是不是在同一个网段,只需将ip地址与子网掩码做与运算(同为1才为1),如果得出的结果一样,则这两个ip地址是同一个子网当中。
示例:
192.168.1.9子网掩码:255.255.255.0,192.168.1.10子网掩码:255.255.255.0,192.168.1.3子网掩码255.255.248.0
要将十进制的ip地址转换为二进制的
做与运算
11000000.10101000.00000001.00001001
11111111.11111111.11111111.00000000 与运算
11000000.10101000.00000001.00000000
11000000.10101000.00000001.00001010
11111111.11111111.11111111.00000000 与运算
11000000.10101000.00000001.00000000 结果相同
11000000.10101000.00000001.00000011
11111111.11111111.11111000.00000000 # 值得注意的是,掩码的与运算决定了192.168.5.1/21和192.168.3.1/21的ip范围都是192.168.0.1~192.168.7.255
11000000.10101000.00000000.00000000 两者结果不同,不是同一个子网,但是可以ping通,因为这是跨子网通信,需要默认网关的转发(本质是路由转发)。traceroute可以查看。抓包只能看到ip没有变化。
通信原理(涉及路由器转发):
网络上通讯工作在物理层和数据链路层,源地址和目标地址是通过源和目的的mac地址进行通讯的。
当源主机访问目标主机时,首先看两者的IP在不在同一网段,结果是:
同一个网段,查本地arp缓存,查看IP和Mac的对应表,向网络上发包,包中包括原mac及目标mac。
两者不在同一网段,则把目标地址转为网关地址(也就是平时说的向网关发包),然后查找本地arp缓存,再继续。网关负责转发包。
掩码示例:
由与运算(同为1才为1)决定范围。
128.0.0.1/1的掩码为128.0.0.0,ip范围为128.0.0.1-255.255.255.254,广播地址为255.255.255.255
127.0.0.1/1的掩码为128.0.0.0,ip范围为0.0.0.1-127.255.255.254,广播地址为255.255.255.255
128的二进制为10000000,比128大的数与掩码1的运算结果都相同。
127的二进制为01111111,比127小的数与掩码1与运算的结果都相同。
1的掩码为 10000000,
另外一种情况,126的二进制为01111110,
那么可以用129掩码10000001,那么比126大的结果都不同,比126小的结果都相同
示例2:
10.32.0.0/12,掩码为255.240.0.0,二进制为11110000,32的二进制为00100000,最多只能到47的00101111,48为00110000,云运算结果就不同了。
其实也可以简单从255-240=15,然后32+15=47得到范围。
如果一个数加上(255-掩码),如果超过255,那么属于(255-掩码)这段范围。
应用:
1./etc/exports
如192.168.0.63/255.255.255.0与192.168.0.x/24(16-31)位于同一网段中
11000000.10101000.00000000.00111111
2.与192.168.0.0/8
11000000.10101000.00000000.00000000
11111111.00000000.00000000.00000000
虽然结果不同,但仍然同一网段,原因可能是192.168.0.0属于c类地址,不能用8
或者不同网段时,默认使用最少的掩码。
华为集群看情况是使用/etc/exports的掩码,
A类,只算第一段。
B类,只算第一、二段。
C类,算第一、二、三段。
3.192.168.1.1/21的掩码为什么为
广播地址:
当在子网中一个机器向所有分组广播时使用这个地址,通常情况下,用255替换子网地址的主机地址即可,其他可能用子网地址作广播地址
如子网地址为128.17.75.0的广播地址为128.17.75.255
绕回设备没有广播地址
什么是arp协议?
地址解析协议,即ARP(Address Resolution Protocol),是根据IP地址获取物理地址的一个TCP/IP协议。
什么是cdn?
内容分发网络
icmp协议:
互联网控制消息协议,它与传输协议(如TCP和UDP)显著不同:它一般不用于在两点间传输数据。它通常不由网络程序直接使用,除了 ping 和 traceroute 这两个特别的例子。
所以,icmp报文一般认为属于IP层协议。
TCP协议:
概述:
面向连接的
连接前需要确认。
基于字节流:
不关心数据结构和边界,对于数据传输频繁的程序来讲,使用TCP可能会容易粘包(需要开启Nagle 算法)。
可靠性。
优点:
可靠,稳定 TCP的可靠体现在TCP在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、窗口、重传、拥塞控制机制。
缺点:
慢,效率低,占用系统资源高,易被攻击。
可靠性:
概述:
主要提供了检验和、序列号/确认应答、超时重传、最大消息长度、滑动窗口控制等方法实现了可靠性传输。
检验和:
通过检验和的方式,接收端可以检测出来数据是否有差错和异常,假如有差错就会直接丢弃TCP段,重新发送。
TCP在计算检验和时,会在TCP首部加上一个12字节的伪首部。检验和总共计算3部分:TCP首部、TCP数据、TCP伪首部。
序列号/确认应答:
只要发送端有一个包传输,接收端没有回应确认包(ACK包),都会重发。或者接收端的应答包,发送端没有收到也会重发数据。这就可以保证数据的完整性。
每个TCP段分配一个自增的sequence number,再加上ack可以确定顺序
对收到的数据进行排序:
由于IP数据报文在网络中经历的时间可能不一样,所以数据到达接收端可能会失序。而接收方的传输层会根据报文段中的序号,进行重新排序。
重复的而数据直接丢弃
超时重传:
指发送出去的数据包到接收到确认包之间的时间,如果超过了这个时间会被认为是丢包了(可能是发包丢失了,或者ack丢失),需要重传。
最大消息长度:
在建立TCP连接的时候,双方约定一个最大的长度(MSS)作为发送的单位,重传的时候也是以这个单位来进行重传。理想的情况下是该长度的数据刚好不被网络层分块。
滑动窗口控制:
概述:
滑动窗口协议是传输层进行流控的一种措施,接收方通过通告发送方自己的可以接受缓冲区大小(这个字段越大说明网络吞吐量越高),从而控制发送方的发送速度,不过如果接收端的缓冲区一旦面临数据溢出,窗口大小值也会随之被设置一个更小的值通知给发送端,从而控制数据发送量(发送端会根据接收端指示,进行流量控制)。
序号最小的分组收到ACK后才滑动,不然整个窗口重发
零窗口(TCP Zero Window):
TCP会启动一个零窗口(TCP Zero Window)定时探测器,向接收方询问窗口大小,当接收方窗口恢复的时候,就可以再次发送数据。
拥塞控制:
概述:
先发出少量数据,就像探路一样,先摸清当前的网络拥堵状态后,再决定按照多大的速度传送数据。防止过多的数据注入到网络中。
拥塞窗口是发送方使用的一种流量控制算法。而在每次发送数据时,发送窗口取拥塞窗口与接送段接收窗口最小者。
发送方的发送窗口的上限值 = Min[rwnd, cwnd]
手段:
慢启动算法:
1) 连接建好的开始先初始化拥塞窗口cwnd大小为1 ,表明可以传一个MSS(Maximum Segment Size,最大报文长度)大小的数据。
2) 每当收到一个ACK,cwnd大小加一,呈线性上升。
3) 每当过了一个往返延迟时间RTT(Round-Trip Time),cwnd大小直接翻倍,乘以2,呈指数让升。(因为滑动窗口的数据都ack了)
4) 还有一个ssthresh(slow start threshold),是一个上限,当cwnd >= ssthresh时,就会进入“拥塞避免算法”
慢开始门限ssthresh:
为了防止拥塞窗口cwnd增长过大引起网络拥塞,还需要设置一个慢开始门限ssthresh状态变量.
当cwnd < ssthresh 时,使用慢开始算法。
当cwnd > ssthresh 时,停止使用慢开始算法而改用拥塞避免算法。
当cwnd = ssthresh 时,即可使用慢开始算法,也可使用拥塞避免算法。
拥塞避免算法:
当拥塞窗口大小cwnd大于等于慢启动阈值ssthresh后,就进入拥塞避免算法。算法如下:
1) 收到一个ACK,则cwnd = cwnd + 1 / cwnd
2) 每当过了一个往返延迟时间RTT,cwnd大小加一。
过了慢启动阈值后,拥塞避免算法可以避免窗口增长过快导致窗口拥塞,而是缓慢的增加调整到网络的最佳值。
发生拥塞状态时:
对丢包事件进行判断:
如果是收到重复确认
进入乘法减小算法(TCP Reno算法,TCP Tahoe算法采用慢开启算法,重新进入慢启动阶段)
1.cwnd大小缩小为当前的一半
2.ssthresh设置为缩小后的cwnd大小
3.然后进入快速恢复算法Fast Recovery。
如果是重传超时(retransmission timeout,RTO)
两个算法都是将拥塞窗口降为1个MSS,然后进入慢启动阶段。
快速重传:
快重传算法首先要求接收方每收到一个失序的报文段就立即发出重复确认(为的是使发送方及早的知道有报文段没有到达对方)而不要等到自己发送数据时才捎带确认。
快重传算法规定,发送方只要一连收到三个重复确认就应当立即重传对方尚未收到的报文段,而不必继续等待为其设置的重传计时器到期。
示例:
M2已经确认,接收多个失序(M4,M5,M6)等多个报文时,就马上发出重复确认M2的包,发送方收到3个后立刻发送M3
快速恢复算法:
Reno算法新引入的一个阶段,在将丢失的分段重传后,启动一个超时定时器,并等待该丢失分段包的分段确认后,再进入拥塞控制阶段。如果仍然超时,则回到慢启动阶段。
在进入快速恢复之前,cwnd和ssthresh已经被更改为原有cwnd的一半。
流程:
1.重传重复确认对应序列号的分段,并设置定时器等待该重发分段包的分段确认包
2.确认后,再进入拥塞控制阶段。如果仍然超时,则回到慢启动阶段。
TCP New Reno算法:
对TCP Reno中快速恢复阶段的重传进行改善的一种改进算法。
在Reno的快速恢复中,一旦出现3次重复确认,TCP发送方会重发重复确认对应序列号的分段并设置定时器等待该重发分段包的分段确认包,当该分段确认包收到后,就立即退出快速恢复阶段,进入拥塞控制阶段。
问题:
但如果某个导致重复确认的分段包到遇到重复确认期间所发送的分段包存在多个丢失的话,则这些丢失只能等待超时重发,并且导致拥塞窗口多次进入拥塞控制阶段而多次下降。比如发送的分段8,9,10到达后,返回4分段的重复确认,5,6,7分段只会重传5分段。
解决:
一旦出现3次重复确认,TCP发送方先记下3次重复确认时已发送但未确认的分段的最大序列号,然后重发重复确认对应序列号的分段包。如果只有该重复确认的分段丢失,则接收方接收该重发分段包后,会立即返回最大序列号的分段确认包,从而完成重发;但如果重复确认期间的发送包有多个丢失,接收方在接收该重发分段后,会返回非最大序列号的分段确认包,从而发送方继续保持重发这些丢失的分段,直到最大序列号的分段确认包的返回,才退出快速恢复阶段。
性能:
New Reno在低错误率时运行效率和“选择确认”(Selective ACKnowledgement,SACK)相当,在高错误率仍优于Reno。
其他快速恢复算法的实现:
1.cwnd = cwnd + 3 MSS,加3 MSS的原因是因为收到3个重复的ACK。
2.重传DACKs指定的数据包。
3.如果再收到DACKs,那么cwnd大小增加一。(表明有其他高序号的包又到达了)
4.如果收到新的ACK,表明重传的包成功了,那么退出快速恢复算法。将cwnd(即一半的那个值)设置为ssthresh,然后进入拥塞避免算法。
两者窗口的区别:
拥塞避免是发送方使用 的流量控制,而通告窗口则是接收方进行的流量控制。前者是发送方感受到的网络拥塞的估 计,而后者则与接收方在该连接上的可用缓存大小有关。
三次握手:
前提:
服务器端在调用listen之后(不阻塞),内核会建立两个队列,SYN队列和ACCEPT队列,其中ACCPET队列的长度由backlog指定。
服务器端在调用accpet之后,将阻塞,等待ACCPT队列有元素。
流程:
第一次握手:client在调用connect方法之后,将连接请求发送给Server,等待Server确认。发送后client进入SYN-SENT
第二次握手:Server收到请求后,把请求方放入SYN队列中,并给客户端回复一个确认帧ACK+SYN,server进入SYN-RCVD
第三次握手:Client收到确认后,connect返回,发送ACK给Server,Client进入ESTABLISHED状态。
server收到ACK后,会把请求方从SYN队列中移出,放至ACCEPT队列中。而accept函数也等到了自己的资源,从阻塞中唤醒,从ACCEPT队列中取出请求方,重新建立一个新的sockfd,并返回。
sockfd进入ESTABLISHED状态
此时client可以发送数据到接收方缓冲区,sockfd通过IO多路复用被唤醒,进行接收。挥手也是和对应的sockfd进行通信。
问题:
为什么第三次握手?
主要防止已经失效的第一次连接请求报文(网络阻塞)突然又传送到了服务器,从而产生错误。
客户端第一个「SYN」包丢了?
进入重传「SYN」包。根据《TCP/IP详解卷Ⅰ:协议》中的描述,此时会尝试三次,间隔时间分别是 5.8s、24s、48s,三次时间大约是 76s 左右,而大多数伯克利系统将建立一个新连接的最长时间,限制为 75s。
服务端收到「SYN」并回复的「SYN,ACK」包丢了?
如果这个回复的「SYN,ACK」包丢了,站在客户端的角度,会认为是最开始的那个「SYN」丢了,那么就继续重传
对服务端而言,如果发送的「SYN,ACK」包丢了,在超时时间内没有收到客户端发来的「ACK」包,也会触发重传,此时客户端处于 SYN_RCVD 状态,会依次等待 3s、6s、12s 后,重新发送「SYN,ACK」包。同时由于客户端在没有收到「SYN,ACK」时,也会进行重传,当客户端重传的「SYN」收到后,会立即重新发送「SYN,ACK」包。
最后一次ACK丢失了怎么办?
Server 端会根据 TCP的超时重传机制,会等待3秒、6秒、12秒后重新发送SYN+ACK包,以便Client重新发送ACK包。超时后关闭连接,防止SYN洪泛攻击。
Client 向 server端发送数据,当服务端处于 SYN-RCVD 状态下时,接收到客户端真实发送来的数据包时,会认为连接已建立,并进入 ESTABLISHED 状态。
当客户端在 ESTABLISHED 状态下,开始发送数据包时,会携带上一个「ACK」的确认序号,所以哪怕客户端响应的「ACK」包丢了,服务端在收到这个数据包时,能够通过包内 ACK 的确认序号,正常进入 ESTABLISHED 状态。
三次握手过程中可以携带数据吗?
第三次握手的时候,是可以携带数据的。但是,第一次、第二次握手绝对不可以携带数据
原因:
假如第一次握手可以携带数据的话,如果有人要恶意攻击服务器,那他每次都在第一次握手中的 SYN 报文中放入大量的数据,然后疯狂重复发 SYN 报文的话(因为攻击者根本就不用管服务器的接收、发送能力是否正常,它就是要攻击你),这会让服务器花费很多时间、内存空间来接收这些报文。
监听的socket和accept生成的新socket如何区分?
TCP四元组:源IP、源端口号、目的IP、目的端口号。
新的socket源IP、源端口号固定,与其他的请求一样,但目标IP和目标端口号这个组合肯定会不同。
请求的数据通过物理层到达后,解包拿到TCP层报文,找到目标端口,再找到端口产生的所有连接中,符合四元组的文件fd,然后传送到缓冲区。
缓冲区是每个socket一个吗?
内核管理的每一个TCP文件描述符都是一个struct, 它记录TCP相关的信息(如序列号、当前窗口大小等等),以及一个接收缓冲区和一个写缓冲区。
缓冲区有配置的大小,所以fd的上限和内存有关系。
四次挥手:
流程:
第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,等待答复。client进入FIN_WAIT1
第二次挥手:Server收到FIN后,发送一个ACK给Client,Server进入CLOSE_WAIT状态。server答复收到请求后,进入CLOSE-WAIT
第三次挥手:client收到ack后进入FIN-WAIT-2,Server将最后的数据发送完毕后,发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。
第四次挥手:Client收到FIN后,Client接着发送一个ACK给Server,进入TIME_WAIT状态,Server收到ack后进入CLOSED状态,完成四次挥手。
注意此时TCP连接还没有释放,必须经过2? *?MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。
问题:
服务端第一次回复的 ACK 丢了?
客户端没有收到「ACK」应答,会尝试重传之前的「FIN」请求,服务端收到后,又会再重传「ACK」。
而此时服务端已经进入 CLOSED-WAIT 状态,开始做断开连接前的准备工作。当准备好之后,会回复「FIN,ACK」,注意这个消息是携带了之前「ACK」的响应序号的。
只要这个消息没丢,客户端可以凭借「FIN,ACK」包中的响应序号,直接从 FIN-WAIT-1 状态,进入 TIME-WAIT 状态,开始长达 2MSL 的等待。
客户端收到 ACK 后,服务端跑路了?
客户端在收到「ACK」后,进入了 FIN-WAIT-2 状态,等待服务端发来的「FIN」包,而如果服务端跑路了,这个包永远都等不到。
在 TCP 协议中,是没有对这个状态的处理机制的。但是协议不管,系统来凑,操作系统会接管这个状态,例如在 Linux 下,就可以通过 tcp_fin_timeout 参数,来对这个状态设定一个超时时间。
TIME_WAIT为什么是2msl?
最后发送的ACK可能会丢失,若丢失,被动关闭连接的一方就会重发。
那么,网络中可能存在来自发起方的数据段,当这些发起方的数据段被服务端处理后又会向客户端发送响应,所以一来一回需要等待 2 倍的时间。去向ACK消息最大存活时间(MSL) + 来向FIN消息的最大存活时间(MSL)。
需要处理对端可能重传的FIN报文或其它一些因网络原因而延迟的数据报文,不处理这些报文可能导致前后两个使用相同四元组的连接中的后一个连接出现异常。防止延迟的数据段被其他使用相同源地址、源端口、目的地址以及目的端口的 TCP 连接收到。
异常:
服务端因为没有收到 ACK 消息,所以仍然认为当前连接是合法的,客户端重新发送 SYN 消息请求握手时会收到服务端的 RST 消息,连接建立的过程就会被终止
客户端最后回复的 ACK 丢了,服务端怎么办?
服务端因为没有收到「ACK」的回复,会重试一段时间,直到服务端重试超时后主动断开。
客户端假设一直收到FIN,发的ACK一直到不了,那么服务器最后超时不发FIN,客户端最后2msl后断开。
或者等待新的客户端接入后,收到服务端重试的「FIN」消息后,回复「RST」消息,在收到「RST」消息后,复位服务端的状态。
为什么很多close_wait状态?
原因:
某种情况下client关闭了socket链接,但是服务器忙与读或者写,没有进一步发出ack信号(阻塞)。
close_wait如果多,服务器代码有问题,无法发送FIN。同时意味着fin_wait_2多。
可能的场景:
1.服务端接口耗时较长,客户端client主动(超时)断开了连接,而服务端还在阻塞(等待唤醒),此时,服务端就会出现 close_wait。
比如mysql的事务没有正确处理,例如:没有close/rollback/commit,导致连接没有释放,请求量到达一定程度后,连接池满。新请求等待可用的连接超时后,请求主动断开,但服务端还在阻塞等待可用的连接,没有发送ACK。
2.服务端代码问题,client发送FIN后,服务端没有执行close方法回复ack。
一般与底层socket开发框架有关。
可以通过类ServerSocket尝试。
影响:
通常来说,一个CLOSE_WAIT会维持至少2个小时的时间(这个时间外网服务器通常会做调整,要不然太危险了)。
close_wait的危害在于,在一个进程上打开的文件描述符超过一定数量,(在linux上默认是1024,可修改),新来的socket连接就无法建立了,因为每个socket连接也算是一个文件描述符。
linux上,每个进程,都有其自身的资源限制,比如最大可以打开的文件描述符数量。
解决:
1.通过修改一下TCP/IP的参数,来缩短这个时间:修改tcp_keepalive_*系列参数有助于解决这个问题。
2.如果是代码没有close,记得close。
3.如果是代码阻塞,尝试解决一下阻塞的地方。
HTTP keep-alive和TCP keepalive的区别?
如果在一段时间(保活时间:tcp_keepalive_time)内此连接都不活跃,开启保活功能的一端会向对端(一般客户端和服务器都有)发送一个保活探测报文。
若由于网络原因或其他原因导致,发送端无法正常收到保活探测报文的响应。那么在一定探测时间间隔(tcp_keepalive_intvl)后,将继续发送保活探测报文。
直到收到对端的响应,或者达到配置的探测循环次数上限(tcp_keepalive_probes)都没有收到对端响应,这时对端会被认为不可达,TCP连接随存在但已失效,需要将连接做中断处理。
为什么是三次握手,四次挥手?
Server端断开连接需要准备。
TIME_WAIT是服务器端的状态 or 客户端的状态?
RE:time_wait 是「主动关闭 TCP 连接」一方的状态,可能是「客服端」的,也可能是「服务器端」的
一般情况下,都是「客户端」所处的状态;「服务器端」一般设置「不主动关闭连接」
在高并发情况下,服务器端存在大量的TIME_WAIT状态的TCP连接,应该如何处理?
背景知识:
TCP的socket套接字对就是一个四元组:源IP、port和目标IP、port。
引起原因:
在高并发短连接的TCP服务器上,当服务器处理完请求后立刻主动正常关闭连接。这个场景下会出现大量socket处于TIME_WAIT状态。如果客户端的并发量持续很高,此时部分客户端就会显示连接不上。
在这个场景中,短连接表示“业务处理+传输数据的时间 远远小于 TIMEWAIT超时的时间”的连接。
TIME_WAIT是主动关闭一方具有的状态,但是Nginx作为7层代理对外它是服务器而对内它是客户端,nginx要转发请求,根据tcp必须要经过三次握手,使用到一个端口号。
客户端解决:
长链接,在HTTP 请求的头部,connection 设置为 keep-alive,保持存活一段时间:现在的浏览器,一般都这么进行了。
服务端解决:
允许 time_wait 状态的 socket 被重用。
打开重复用:echo 1 /proc/sys/net/ipv4/tcp_tw_reuse,同时要确保时间戳tcp_timestamps打开,记录时间
缩减 time_wait 时间,设置为 1 MSL(即,2 mins),快速回收。
等待更少时间:echo 1 /proc/sys/net/ipv4/tcp_tw_recycle 对外服务不能打开
负载均衡来抗这些高并发的短请求。
Nginx通常会开启Keep-alive来让客户端对TCP连接进行复用,对Keep-alive最大请求数量
混淆点:
单个进程能打开的文件句柄数:ulimit
2MSL和resue或者recycle会不会有冲突?
重用TIME_WAIT状态的端口以及快速回收会引发收到该相同4元组之前的重复IP报文。
解决方法:
TCP序列号,也就是Seq位置的数字。
TCP头中的序列号位有长度限制(32位),其最大值为2的32次方个,这就意味着它是循环使用的,也很容易在短时间内完成一个循环(序列号反转),在1Gbps的网络里17秒就可以完成一个循环,所以单纯的通过检查序列号不能完全实现阻挡老IP分组的数据,因为高速网络中这个循环完成的太快,而一个IP分组的最长TTL是2MSL,通常是1分钟,所以最主要还是靠时间戳。
时间戳,所以这也是为什么在开启resue和recycle的时候要求开启时间戳功能。
如果收到的报文时间戳小于最近连接的时间戳就会被丢弃
标志码:
位码即TCP标志码,有6种标示
SYN(synchronous建立联机)\ACK(acknowledgement 确认)\PSH(push传送)\FIN(finish结束\)RST(reset重置)\URG(urgent紧急)
Sequence number(顺序号码)\Acknowledge number(确认号码)
三次握手:
client端:发送 SYN=1 ACK=0 seq=x给Server端 closed-> SYN-SENT -> ESTAB-LISHED
Server端:答复 SYN=1 ACK=1 seq=y,ack=x+1 closed -> LISTEN -> SYN-RCVD -> ESTAB-LISHED
client端:发送 ACK=1 seq=x+1 ack=y+1
数据传输:
write():发送 seq = x+1 ACK=y+1
read():发送 ACK=x+1
四次挥手:
client端:发送 FIN=1 ACK=1 seq=x+2 ack=y+1 ESTAB-LISHED -> FIN-WAIT-1 -> FIN-WAIT-2 -> TIME-WAIT -> CLOSED
Server端:答复 ACK=x+3 seq=y+1 ESTAB-LISHED -> CLOSE-WAIT -> LAST-ACK -> CLOSED
Server端:确认 FIN=1 seq=y+1 ack=x+2
client端:发送 ACK=1 ack=y+2 seq=x
# 为什么挥手要四次:全双工,client不能发送,但还可以收,server端可能数据还没发完,先发ack,需准备好才能回复seq断开。
# 连接时由于是listen状态,ack可以和seq一起发送
TCP粘包:
现象:
发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。
示例:
客户端不断发送单词,服务端偶尔会看到连在一起的单词。
处理时机:
如果发送方发送的多组数据本来就是同一块数据的不同部分,比如说一个文件被分成多个部分发送,这时当然不需要处理粘包现象
如果多个分组毫不相干,甚至是并列关系,那么这个时候就一定要处理粘包现象了
原因:
每个TCP socket在内核中都有一个发送缓冲区(SO_SNDBUF )和一个接收缓冲区(SO_RCVBUF)(不同端口独立)
发送端:
MTU (Maxitum Transmission Unit,最大传输单元)是链路层对一次可以发送的最大数据的限制。
Negale 算法:
为了尽可能发送大块数据,避免网络中充斥着许多小数据块。若连续几次发送的数据都很少,通常TCP会根据优化算法把这些数据合成一包后一次发送出去,这样接收方就收到了粘包数据。
注意此时还没有报文头部,只有消息体
接收方:
由于接收方用户进程不及时接收数据,从而导致粘包现象。
注意这时解开了报文,剩下了消息体
IP层为什么没有粘包?
如果消息过长,IP层会按 MTU 长度把消息分成 N 个切片,每个切片带有自身在包里的位置(offset)和同样的IP头信息。
各个切片在网络中进行传输。每个数据包切片可以在不同的路由中流转,然后在最后的终点汇合后再组装。
在接收端收到第一个切片包时会申请一块新内存,创建IP包的数据结构,等待其他切片分包数据到位。
等消息全部到位后就把整个消息包给到上层(传输层)进行处理。
IP 层从按长度切片到把切片组装成一个数据包的过程中,都只管运输,都不需要在意消息的边界和内容,都不在意消息内容了,那就不会有粘包一说了。
UDP为什么不会粘包:
不会使用块的合并优化算法,每一个消息都是独立发送的。
没有发送缓冲区,有接收缓冲区,在UDP协议的接收端,采用链式结构记录每个到达的UDP数据包,使得接收端应用程序一次只能从socket接收缓冲区中读取一个数据包。
换句话说,发送者已经发送(写入)多次,接收者必须接收(读取)多次(无论在 recv 中指定的缓冲区有多大)
udp报文头部有消息长度。
解决:
发送方:
1.关闭Negale 算法。TCP_NODELAY
2.编程设置可以不等待缓冲直接发送,降低了网络发送效率,影响应用程序的性能
接收方:
1.自定义通信协议,比如定长协议,每来一个请求后,接下来读取指定长度的内容。
2.特殊分隔符协议(开始符、结束符),如果重复了,发送端在发送时还会加入各种校验字段(校验和或者对整段完整数据进行 CRC 之后获得的数据)放在标志位后面,在接收端拿到整段数据后校验下确保它就是发送端发来的完整数据。
3.消息分为头部和消息体,头部添加Length字段,比如http的Content-Length
或者前几个字节表示消息长度
4.校验码
TCP报文:
TCP和UDP发送和接收数据的单位被分为分组包(packet),每个分组包包含一段发送给其他机器的信息,并且在分组包的头中指定了目的和源端口地址
互联网协议(IP)在协议从层次中位于TCP和UDP下面,负责在网络上传输TCP或UDP分组包并为它们路由:
为了实现这些功能,IP将每个TCP或UDP分组包封装在另一个分组包中(被称为IP报文),并且包含一个具有路由和目的地信息的报文头。
IP报文头包含源和目的机器的IP地址。IP不知道关于端口地址的任何信息
IP除了管理每个主机的IP报文外还负责在主机间路由分组包:
1.TCP/IP网络是一组机器通过物理媒介(诸如以太网或串行线路)连接起来的,在TCP/IP的概念中,每个网络有自己内部的路由和传送分组包的机制
2.网络间通过网关(被称为路由器)相连,一个网关是一个直接和两个或多个网络相连的主机,网关可以在网络之间传递信息,为分组包从一个网络到
另一个网络选路
如:
网关可以是一个具体多个以太网接口的工作站,每个接口被接在不同的网络中,操作系统利用这种连通性是机器起到网关的作用
3.如果一个网关的路由表处理不了该分组包,会交给其他网关(本机路由表指向),直到到达目的网络,这就是internet的基本结构
IP使用IP地址的网络部分判定如何在机器间路由分组包,为了做到这些,网络上的每台机器都有一个路由表,其中包含一个网络和对应网络的网关机器的列表
要将一个分组包寻址到一个特定机器,IP查看目的地址的网络部分。
命令:
netstat -rn
Destination Gateway Genmask Flags MSS Window irtt Iface
128.17.75.0 128.17.75.20 255.255.255.0 UN 1500 0 0 eth0 # 访问其他局域网ip
default 128.17.75.98 0.0.0.0 UGN 1500 0 0 eht0 # 访问外网ip都要经过128.17.75.98
127.0.0.1 127.0.0.1 255.0.0.0 UH 3584 0 0 lo # 绕回
128.17.75.20 127.0.0.1 255.255.255.0 UH 3584 0 0 lo # 访问自身局域网ip
Destination:目的网络
Gateway:任何发送给128.17.75网络(主机地址为0,表示任意)的请求都会通过128.17.75.20进行路由
Genmask:
flags:提供关于该记录的目的信息,U表示路由是激活的,N表示该目的是一个网络
MSS:表示在特定连接上已经传输的字节数
window:指示在必须发送确认前可以发送的帧数
irtt:给出该路由的被使用的统计数字
iface:列出了该路由使用的网络设备,以太网是eth0、eth1等,lo是一个绕回设备
如果在路由表上有这个网络对应的记录,IP将这个分组路由到相应的网关,否则,IP将这个分组路由给缺省网关
每个机器在路由表有一个到自身的记录:
127.0.0.1是一个绕后地址,使用lo设备,避免以太网的连接循环(通过eth0接口),浪费网络带宽
本机ipv4地址(128.17.75.20)的gateway也是127.0.0.1,也使用lo设备
通过route向路由表加入记录
格式:
route add [-net [-host ] destination [ gw gateway ] [ metric metric ] options
metric指定了本路由的尺度,当到达一个特定位置有多条路由时要使用尺度值,系统将使用较低尺度值的路由
示例:
route add default gw 128.17.75.98
route del default gw 128.17.75.98
/etc/hosts文件
包含了IP地址与对应主机名的列表,通常来说只包含本地机器、本地域名服务器,或网关
127.0.0.1 localhost
128.17.75.20 www.czl.com
/etc/networks文件
列出本地网络与其他网络的名称和地址,添加后route命令可以通过名称来替代IP
test 128.17.75.20
route add test ...
报头结构
源端口16 目标端口16
序列号32
确认号32
TCP偏移量4 保留6 标志6 窗口16
校验和16 紧急16
选项0或32
数据
TCP偏移量:指定了段头的长度
标志位:6个
UDP协议:
概述:
UDP面向无连接,传输效率高(发送前时延小),传输数据之前源端和终端不建立连接,如ping,直播(丢包减少帧而已),通过减少握手来保证流媒体的实时性
UDP是面向报文的。发送方的UDP对应用程序交下来的报文,在添加首部后就向下交付给IP层。
UDP信息包的标题很短,只有8个字节(包含源端口、目标端口、长度、检验和),相对于TCP的20个字节信息包的额外开销很小(额外有序号、确认号、数据偏移、窗口等)。
吞吐量不受拥挤控制算法的调节,只受应用软件生成数据的速率、传输带宽、源端和终端主机性能的限制。
由于传输数据不建立连接,因此也就不需要维护连接状态,包括收发状态等,因此一台服务机可同时向多个客户机传输相同的消息。
优点:
快,比TCP稍安全 UDP没有TCP的握手、确认、窗口、重传、拥塞控制等机制,UDP是一个无状态的传输协议,所以它在传递数据时非常快。
缺点:
不可靠,不稳定 因为UDP没有TCP那些可靠的机制,在数据传递时,如果网络质量不好,就会很容易丢包。
如何实现可靠?
丢包 –> 需要确认和重传机制,就是和Tcp类似的Ack机制
乱序 –> 加上一个数据包序列号SEQ
针对数据完整性 –> 加上一个16或者32位的CRC验证字段,如果直接发一个超过mtu大小的包,就会在协议层被分片。
如果有一个分片在传输中出错了即校验不正确(这是较容易发生的),整个传输的udp包就被丢弃。
所以一般按照1k多大小发送
每个UDP socket都有一个接收缓冲区,没有发送缓冲区,从概念上来说就是只要有数据就发,不管对方是否可以正确接收,所以不缓冲,不需要发送缓冲区。
当套接口接收缓冲区满时,新来的数据报无法进入接收缓冲区,此数据报就被丢弃。UDP是没有流量控制的;快的发送者可以很容易地就淹没慢的接收者,导致接收方的UDP丢弃数据报。
http协议:
概述:
一套超文本传输的协议。
一个属于应用层的面向对象的协议,HTTP 协议一共有五大特点:1、支持客户/服务器模式;2、简单快速;3、灵活;4、无连接;5、无状态。
无连接:
无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
请求时建连接、请求完释放连接,以尽快将资源释放出来服务其他客户端。
每次访问图片都需要建立一次 TCP 连接就显得很低效。后来,Keep-Alive 被提出用来解决这效率低的问题。
Keep-Alive 功能使客户端到服务器端的连接持续有效,当出现对服务器的后继请求时,Keep-Alive 功能避免了建立或者重新建立连接。
客户端和服务器之间的 HTTP 连接就会被保持,不会断开(超过 Keep-Alive 规定的时间,意外断电等情况除外),当客户端发送另外一个请求时,就使用这条已经建立的连接。
无状态:
无状态是指协议对于事务处理没有记忆能力,服务器不知道客户端是什么状态。即我们给服务器发送 HTTP 请求之后,服务器根据请求,会给我们发送数据过来,但是,发送完,不会记录任何信息。
HTTP 是一个无状态协议,这意味着每个请求都是独立的,Keep-Alive 没能改变这个结果。
缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。
两种用于保持 HTTP 连接状态的技术就应运而生了,一个是 Cookie,而另一个则是 Session。
请求报文详解:
格式:
包含请求行、请求头、空行、请求体 四个部分。
请求行:
格式:
请求方式 资源路径 协议/版本
如:POST /web01/login.html HTTP/1.1
请求方式:协议规定7种,常用两种:GET和POST
请求头:
描述了客户端向服务器发送请求时使用的编码,以及发送内容的长度,referer,等等。
Content-Type\User-Agent\Host\Referer\Cookie
常见请求头
Referer浏览器通知服务器,当前请求来自何处。如果是直接访问,则不会有这个头。常用于:防盗链
Cookie 与会话有关技术,用于存放浏览器缓存的cookie信息。
User-Agent浏览器通知服务器,客户端浏览器与操作系统相关信息
请求体:
http请求流程:
1.URL解析,域名通过DNS查询解析 -->
2.发起TCP的3次握手 -->
3.建立TCP连接后发起http请求 -->
应用层:发送 HTTP 请求
传输层:TCP 传输报文
网络层:IP协议
链路层:以太网协议,根据以太网协议将数据分为以“帧”为单位的数据包,每一帧分为两个部分:标头:数据包的发送者、接受者、数据类型。数据:数据包具体内容
4.服务器响应http请求,浏览器得到html代码 -->
5.浏览器边解析html代码,边请求html代码中的资源(如js、css、图片等)(多线程) --> 遇到js会阻塞,直到下载完成并执行成功
6.浏览器对页面进行渲染呈现给用户
html\css\javascripts渲染执行
HTTP状态码:
1、(信息类):表示接收到请求并且继续处理
100——(继续)客户必须继续发出请求
101——(切换协议)客户要求服务器根据请求转换HTTP协议版本
2、(响应成功):表示动作被成功接收、理解和接受
200——(成功)表明该请求被成功地完成,所请求的资源发送回客户端
201——(已创建)请求成功并且服务器创建了新的资源。
202——(已接受)服务器已接受请求,但尚未处理。
203——(非授权信息)服务器已成功处理了请求,但返回的信息可能来自另一来源。
204——(无内容) 服务器成功处理了请求,但没有返回任何内容。
205——(重置内容)服务器完成了请求,用户代理必须复位当前已经浏览过的文件
206——(部分内容)服务器已经完成了部分用户的GET请求
3、(重定向类):为了完成指定的动作,必须接受进一步处理
300——(多种选择) 请求的资源可在多处得到
301——(永久移动) 本网页被永久性转移到另一个URL
302——(临时移动)请求的网页被转移到一个新的地址,但客户访问仍继续通过原始URL地址,重定向,新的URL会在response中的Location中返回,浏览器将会使用新的URL发出新的Request。
303——(查看其他位置)建议客户访问其他URL或访问方式
304——(未修改)自从上次请求后,请求的网页未修改过,服务器返回此响应时,不会返回网页内容,代表上次的文档已经被缓存了,还可以继续使用
305——(使用代理)请求的资源必须从服务器指定的地址得到
306——前一版本HTTP中使用的代码,现行版本中不再使用
307——(临时重定向)申明请求的资源临时性删除
4、(客户端错误类):请求包含错误语法或不能正确执行
400——(错误请求)客户端请求有语法错误,不能被服务器所理解
401——(未授权)请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用
HTTP 401.2 - 未授权:服务器配置问题导致登录失败
HTTP 401.3 - ACL 禁止访问资源
HTTP 401.4 - 未授权:授权被筛选器拒绝
HTTP 401.5 - 未授权:ISAPI 或 CGI 授权失败
402——保留有效ChargeTo头响应
403——(禁止)禁止访问,服务器收到请求,但是拒绝提供服务
HTTP 403.1 禁止访问:禁止可执行访问
HTTP 403.2 - 禁止访问:禁止读访问
HTTP 403.3 - 禁止访问:禁止写访问
HTTP 403.4 - 禁止访问:要求 SSL
HTTP 403.5 - 禁止访问:要求 SSL 128
HTTP 403.6 - 禁止访问:IP 地址被拒绝
HTTP 403.7 - 禁止访问:要求客户证书
HTTP 403.8 - 禁止访问:禁止站点访问
HTTP 403.9 - 禁止访问:连接的用户过多
HTTP 403.10 - 禁止访问:配置无效
HTTP 403.11 - 禁止访问:密码更改
HTTP 403.12 - 禁止访问:映射器拒绝访问
HTTP 403.13 - 禁止访问:客户证书已被吊销
HTTP 403.15 - 禁止访问:客户访问许可过多
HTTP 403.16 - 禁止访问:客户证书不可信或者无效
HTTP 403.17 - 禁止访问:客户证书已经到期或者尚未生效
404——(未找到)一个404错误表明可连接服务器,但服务器无法取得所请求的网页,请求资源不存在。eg:输入了错误的URL
405——(方法禁用)用户在Request-Line字段定义的方法不允许
406——(不接受)根据用户发送的Accept拖,请求资源不可访问
407——(需要代理授权)类似401,用户必须首先在代理服务器上得到授权
408——(请求超时)客户端没有在用户指定的饿时间内完成请求
409——(冲突)对当前资源状态,请求不能完成
410——(已删除)服务器上不再有此资源且无进一步的参考地址
411——(需要有效长度) 服务器拒绝用户定义的Content-Length属性请求
412——(未满足前提条件) 一个或多个请求头字段在当前请求中错误
413——(请求实体过大) 服务器无法处理请求,因为请求实体过大,超出服务器的处理能力。
414——(请求的 URI 过长) 请求的 URI(通常为网址)过长,服务器无法处理。
415——(不支持的媒体类型) 请求的格式不受请求页面的支持。
416——(请求范围不符合要求) 如果页面无法提供请求的范围,则服务器会返回此状态代码。
417——(未满足期望值) 服务器未满足"期望"请求标头字段的要求.
5、(服务端错误类):服务器不能正确执行一个正确的请求
HTTP 500 - (服务器内部错误)服务器遇到错误,无法完成请求
HTTP 500.100 - 内部服务器错误 - ASP 错误
HTTP 500-11 服务器关闭
HTTP 500-12 应用程序重新启动
HTTP 500-13 - 服务器太忙
HTTP 500-14 - 应用程序无效
HTTP 500-15 - 不允许请求 global.asa
HTTP 501 - 未实现
HTTP 502 - 网关错误
HTTP 503 (服务不可用) 服务器目前无法使用(由于超载或停机维护)。通常,这只是暂时状态。
HTTP 504 (网关超时) 服务器作为网关或代理,但是没有及时从上游服务器收到请求。
HTTP 505 (HTTP 版本不受支持) 服务器不支持请求中所用的 HTTP 协议版本。
http协议是基于是tcp还是udp,为什么?
HTTP是无状态的协议,即可以基于TCP也可以基于UDP,不过大部分实现都是基于TCP的。使用TCP,不用考虑数据包乱序,丢失这些问题,实现起来更简单,高效。
如果数据的内容不大(<65536),而且容忍数据丢失,可以在udp包中使用http的协议格式来封装数据。具体的实例可以参考 upnp协议。
一个 TCP 连接可以对应几个 HTTP 请求?
在 HTTP/1.0 中,一个服务器在发送完一个 HTTP 响应后,会断开 TCP 链接。但是这样每次请求都会重新建立和断开 TCP 连接,代价过大。
HTTP/1.1 就把Connection: Keep-Alive头写进标准,并且默认开启持久连接,除非请求中写明 Connection: close,那么浏览器和服务器之间是会维持一段时间的 TCP 连接,不会一个请求结束就断掉。
一个 TCP 连接中 HTTP 请求发送可以一起发送么(比如一起发三个请求,再三个响应一起接收)?
HTTP2 中由于 Multiplexing多路传输特性 特点的存在,多个 HTTP 请求可以在同一个 TCP 连接中并行进行。
升级:
管线化->多路复用
为何?
http请求是一个报文,多个报文发送给服务器nginx,nginx分开多个进行转发,最终函数调用。
浏览器对同一 Host 建立 TCP 连接到数量有没有限制?
Chrome 最多允许对同一个 Host 建立六个 TCP 连接。不同的浏览器有一些区别。
get和post的区别:
浏览器的 GET 和 POST 的区别:
(1)作用不同。GET 用于获取资源,POST 用于更新资源;
(2)携带数据的方式不同。GET 一般将数据已参数的形式放到 URL 中,虽然 HTTP 标准并未对 URL 长度做限制,但是浏览器在实现时,一般会对 URL 的长度做限制,所以携带的数据有限;POST 将数据放到 Body 中,无长度限制;
(3)安全性不同。GET 比 POST 更不安全,因为参数直接暴露在 URL 上,所以不能用来传递敏感信息;
(4)幂等性不同。GET 对访问的数据没有副作用,具有幂等性。POST 用于更新操作往往是有副作用的,不幂等。因为幂等性的差别,GET 产生的 URL 地址可以保存为书签,而 POST 不可以。GET 请求会被浏览器主动 cache,而 POST 不会,除非手动设置。
后台接口中的 GET 和 POST 的区别:
在后台接口调用中,我们可以利用 HTTP 协议进行通信,此时 GET/POST 不光能用在前端和后端的交互中,还能用在后端各个子服务的调用中。当用HTTP实现接口发送请求时,就没有浏览器中那么多限制了,只要是符合 HTTP 格式的就可以发送。
所以该应用场景下,GET 与 POST 除了语义上区别,在作用上并无区别,GET 可以使用 body 协议数据用于更新远端资源,POST 也可以把数据放到 URL 参数中用于获取远端资源,这完全取决于被调接口的具体实现。
结论:
GET 和 POST 方法没有实质区别,只是报文格式不同。
误区:
POST 方法比 GET 方法安全?除了https都是不安全的
GET 方法的长度限制是怎么回事?HTTP 协议没有 Body 和 URL 的长度限制,对 URL 限制的大多是浏览器和服务器的原因。
POST 方法会产生两个 TCP 数据包?HTTP 协议中没有明确说明 POST 会产生两个 TCP 数据包,header 和 body 分开发送是部分浏览器或框架的请求方法
websocket协议:
WebSocket和HTTP一样都是基于TCP的应用层协议。
路由器和交换机的区别?
一个是二层,一个是三层
路由器和交换机的区别?
路由器 : 连接外部网络,有接入外部的线
交换机 : 连接内部网络,可以没有接入外部的线
HTTPS:
如果Server(以后简称服务器)给Client(以后简称 客户端)的消息是密文的,只有服务器和客户端才能读懂,就可以保证数据的保密性。
那么,问题来了,服务器把数据加密后,客户端如何读懂这些数据呢?这时服务器必须要把加密的密钥(对称密钥,后面会详细说明)告诉客户端,
客户端才能利用对称密钥解开密文的内容。
但是,服务器如果将这个对称密钥以明文的方式给客户端,还是会被中间人截获,中间人也会知道对称密钥,依然无法保证通信的保密性。
但是,如果服务器以密文的方式将对称密钥发给客户端,客户端又如何解开这个密文,得到其中的对称密钥呢?
两个密钥的区别:
这里的密钥,指的是非对称加解密的密钥,是用于TLS握手阶段的; 对称密钥,指的是对称加解密的密钥,是用于后续传输数据加解密的。
在非对称加解密算法里,公钥加密的数据,有且只有唯一的私钥才能够解密,
服务器只要把公钥发给客户端,客户端就可以用这个公钥来加密进行数据传输的对称密钥。
客户端利用公钥将对称密钥发给服务器时,即使中间人截取了信息,也无法解密,因为私钥只部署在服务器,其他任何人都没有私钥,因此,只有服务器才能够解密。
流程:
1.客户端发起ssl连接
2.服务器发送公钥给客户端
3.客户端利用公钥加密对称密钥,发送给服务器
4.服务器利用对称密钥传输数据给客户端
问题:
中间人在收到服务器发送给客户端的公钥(这里是“正确的公钥”)后,并没有发给客户端,
而是中间人将自己的公钥(这里中间人也会有一对公钥和私钥,这里称呼为“伪造公钥”)发给客户端。
之后,客户端把对称密钥用这个“伪造公钥”加密后,发送过程中经过了中间人,中间人就可以用自己的私钥解密数据并拿到对称密钥,
此时中间人再把对称密钥用“正确的公钥”加密发回给服务器。此时,客户端、中间人、服务器都拥有了一样的对称密钥,后续客户端和服务器的所有加密数据,
中间人都可以通过对称密钥解密出来。
解决:
为了解决此问题,我们引入了数字证书的概念。服务器首先生成公私钥,将公钥提供给相关机构(CA),CA将公钥放入数字证书并将数字证书颁布给服务器,
此时服务器就不是简单的把公钥给客户端,而是给客户端一个数字证书,数字证书中加入了一些数字签名的机制,保证了数字证书一定是服务器给客户端的。
中间人发送的伪造证书,不能够获得CA的认证,此时,客户端和服务器就知道通信被劫持了。
对称加密算法的优缺点:
优点:计算量小、加密速度快、加密效率高。
缺点:
(1)交易双方都使用同样密钥,安全性得不到保证;
(2)每次使用对称加密算法时,都需要使用其他人不知道的惟一密钥,这会使得发收信息双方所拥有的钥匙数量呈几何级数增长,密钥管理成为负担。
非对称加密算法:
缺点:CPU计算资源消耗非常大。非对称加密算法对加密内容的长度有限制,不能超过公钥长度。
所以非对称加解密(极端消耗CPU资源)目前只能用来作对称密钥交换或者CA签名,不适合用来做应用层内容传输的加解密。
身份认证:
https协议中身份认证的部分是由CA数字证书完成的,证书由公钥、证书主体、数字签名等内容组成。
在客户端发起SSL请求后,服务端会将数字证书发给客户端,客户端会对证书进行验证(验证这张证书是否是伪造的?也就是公钥是否是伪造的),
如果证书不是伪造的,客户端就获取用于对称密钥交换的非对称密钥(获取公钥)。
数字证书有三个作用:
1、身份授权。确保浏览器访问的网站是经过CA验证的可信任的网站。
2、分发公钥。每个数字证书都包含了注册者生成的公钥(验证确保是合法的,非伪造的公钥)。在SSL握手时会通过certificate消息传输给客户端。
3、验证证书合法性。客户端接收到数字证书后,会对证书合法性进行验证。只有验证通过后的证书,才能够进行后续通信过程。
数字证书验证:
申请者拿到CA的证书并部署在网站服务器端,那浏览器发起握手并接收到证书后,如何确认这个证书就是CA签发的呢?怎样避免第三方伪造这个证书?
答案就是数字签名(digital signature)。数字签名是证书的防伪标签,目前使用最广泛的SHA-RSA(SHA用于哈希算法,RSA用于非对称加密算法)。
数字签名的制作和验证过程如下:
1、数字签名的签发。首先是使用哈希函数对待签名内容进行安全哈希,生成消息摘要,然后使用CA自己的私钥对消息摘要进行加密。
2、数字签名的校验。使用CA的公钥解密签名,然后使用相同的签名函数对签名证书内容进行签名,并和服务端数字签名里的签名内容进行比较,
如果相同就认为校验成功。
具体过程:
客户端发起ssl/TLS通信(tcp三次握手、客户端发送client hello,服务器发送server hello)
服务端通过SSL通信,将SSL版本及加密算法版本中的一组发送至客户端.
服务端发送客户端Certificate报文,报文中包含公开密钥证书.
1.客户端验证证书真伪性
2.秘钥交换
(1)首先,客户端利用CA数字证书实现身份认证,利用非对称加密协商对称密钥。
(2)客户端会向服务器传输一个“pubkey”随机数,服务器收到之后,利用特定算法生成另外一个“pubkey”随机数,
客户端利用这两个“pubkey”随机数生成一个 pre-master 随机数。
(3)客户端利用自己在 client hello 里面传输的随机数 random_C,以及收到的 server hello 里面的随机数 random_S,外加 pre-master 随机数,
利用对称密钥生成算法生成 对称密钥enc_key:enc_key=Fuc(random_C, random_S, Pre-Master),发给服务器
(4)服务端会通过私钥解密出Pre-master sercret随机密码串,通过Pre-master sercret解密密发来的握手信息,并验证Hash是否与浏览器发来的一致.
之后通过密码加密一段握手信息,发给客户端.
(5)客户端解密并计算握手信息的Hash,如果与Server发来的Hash一致,此时握手过程结束
3.生成session ticket
为了避免重新握手而造成的访问效率低下,这时候引入了session ID的概念
每一次对话都有一个编号(session ID)。如果对话中断,下次重连的时候,只要客户端给出这个编号,且服务器有这个编号的记录,
双方就可以重新使用已有的“对话密钥”,而不必重新生成一把。
# session ID通过加密传输,就算被劫持了,不使用对称密钥进行加密发送,还是劫持不了(黑客不知道对称密钥)
session ID是目前所有浏览器都支持的方法,但是它的缺点在于session ID往往只保留在一台服务器上。
所以,如果客户端的请求发到另一台服务器,就无法恢复对话。session ticket就是为了解决这个问题而诞生的,目前只有Firefox和Chrome浏览器支持。
后续建立新的https会话,就可以利用 session ID 或者 session Tickets , 对称秘钥可以再次使用,从而免去了 https 公私钥交换、CA认证等等过程,
极大地缩短 https 会话连接时间。
4.利用对称秘钥传输数据