进程和线程的区别
- 进程是资源分配的最小单位,线程是程序执行的最小单位(资源调度的最小单位)
- 进程有自己独立的地址空间,每启动一个进程,系统将为其分配地址空间,建立数据表来维护代码段,堆栈段和数据段。
- 而线程是共享进程中的数据,使用相同的地址空间,CPU开销小。
- 线程之间的通信更方便,同一进程下的线程共享全局变量,静态变量等数据,进程之间的通信则需要以通信的方式进行。
- 多进程比多线程更健壮,多线程中一个线程死亡意味着进程的死亡。而多进程程序不同进程之间互不影响。
进程间的通信方式(IPC)
- 管道:在内核中申请一块固定大小的缓冲区,程序拥有写入和读取的权利。
- 匿名管道:用于亲缘关系进程,父子进程。
- 有名管道:可用于无亲缘关系的进程间。
- 信号:信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
- 信号量:在内核中创建一个信号量集合(本质是个数组),数组的元素(信号量)都是1,使用P操作进行-1,使用V操作+1。
- PV操作用于同一进程实现互斥。
- PV操作用于不同进程实现同步。
- 消息队列:在内核中创建一队列,队列中每个元素是一个数据报,不同的进程可以通过句柄去访问这个队列。
消息队列提供了⼀个从⼀个进程向另外⼀个进程发送⼀块数据的⽅法。- 不足:消息长度有上限
- 共享内容:将同一块物理内存一块映射到不同的进程的虚拟地址空间中,实现不同进程间对同一资源的共享。
- 共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式。
- 不用从用户态到内核态的频繁切换和拷贝数据,直接从内存中读取就可以。
- 共享内存是临界资源,所以需要操作时必须要保证原子性。使用信号量或者互斥锁都可以。
- 生命周期随内核。
- 套接字:套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。主要用于客户端和服务端之间的进程通信。
线程间的通信方式
- 锁机制:包括互斥锁、条件变量、读写锁
- 互斥锁提供了以排他方式防止数据结构被并发修改的方法。
- 读写锁允许多个线程同时读共享数据,而对写操作是互斥的。
- 条件变量可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。
- 信号量机制(Semaphore):包括无名线程信号量和命名线程信号量
- 信号机制(Signal):类似进程间的信号处理
线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制。
进程的状态和转换关系
-
就绪——执行:对就绪状态的进程,当进程调度程序按一种选定的策略从中选中一个就绪进程,为之分配了处理机后,该进程便由就绪状态变为执行状态;
-
执行——阻塞:正在执行的进程因发生某等待事件而无法执行,则进程由执行状态变为阻塞状态。如:
- 进程提出输入/输出请求而变成等待外部设备传输信息的状态
- 进程申请资源(主存空间或外部设备)得不到满足时变成等待资源状态
- 进程运行中出现了故障(程序出错或主存储器读写错等)变成等待干预状态等等
-
阻塞——就绪:处于阻塞状态的进程,在其等待的事件已经发生,如:
- 输入/输出完成
- 资源得到满足或错误处理完毕时
处于等待状态的进程并不马上转入执行状态,而是先转入就绪状态,然后再由系统进程调度程序在适当的时候将该进程转为执行状态;
-
执行——就绪:正在执行的进程,因时间片用完而被暂停执行,或在采用抢先式优先级调度算法的系统中,当有更高优先级的进程要运行而被迫让出处理机时,该进程便由执行状态转变为就绪状态。
进程调度算法和比较
-
先来先去服务(FCFS)
先来先去服务调度算法是一种最简单的调度算法,也称为先进先出或严格排队方案。当每个进程就绪后,它加入就绪队列。当前正运行的进程停止执行,选择在就绪队列中存在时间最长的进程运行。该算法既可以用于作业调度,也可以用于进程调度。先来先去服务比较适合于常作业(进程),而不利于段作业(进程)。 -
时间片轮转法(RR)
轮转法是基于适中的抢占策略的,以一个周期性间隔产生时钟中断,当中断发生后,当前正在运行的进程被置于就绪队列中,然后基于先来先去服务策略选择下一个就绪作业的运行。这种技术也称为时间片,因为每个进程再被抢占之前都给定一片时间。- 过程:1、排成一个队列。2、每次调度时将CPU分派给队首进程。3、时间片结束时,发生时钟中断。4、暂停当前进程的执行,将其送到就绪队列的末尾,并通过上下文切换执行当前就绪的队首进程。
- 说明:1、进程阻塞情况发生时,未用完时间片也要出让CPU。2、能够及时响应,但没有考虑作业长短等问题。3、系统的处理能力和系统的负载状态影响时间片长度。
-
最短进程优先(SJF)
最短进程优先是一个非抢占策略,他的原则是下一次选择预计处理时间最短的进程,因此短进程将会越过长作业,跳至队列头。该算法即可用于作业调度,也可用于进程调度。但是他对长作业不利,不能保证紧迫性作业(进程)被及时处理,作业的长短只是被估算出来的。 -
最短剩余时间优先(SRTF)
最短剩余时间是针对最短进程优先增加了抢占机制的版本。在这种情况下,进程调度总是选择预期剩余时间最短的进程。当一个进程加入到就绪队列时,他可能比当前运行的进程具有更短的剩余时间,因此只要新进程就绪,调度程序就能可能抢占当前正在运行的进程。像最短进程优先一样,调度程序正在执行选择函数是必须有关于处理时间的估计,并且存在长进程饥饿的危险。 -
高优先权优先调度算法HPF和高响应比优先调度算法HRRN
(1)两种方式:非抢占式优先权算法、抢占式优先权算法(关键点:新作业产生时)
(2)类型:静态优先权:创建进程时确定,整个运行期间保持不变。动态优先权:创建进程时赋予的优先权可随进程的推进或随其等待时间的增加而改变。
(3)高响应比优先调度算法HRRNHRRN为每个作业引入动态优先权,使作业的优先级随着等待时间的增加而以速率a提高:优先权 =(等待时间+要求服务时间)/要求服务时间= 响应时间 / 要求服务时间。
什么时候计算各进程的响应比优先权?(作业完成时、新作业产生时(抢占、非抢占)、时间片完成时、进程阻塞时) -
多级反馈队列算法FB
- 过程:1、准备调度:先将它放入第一个队列的末尾,按FCFS原则排队等待调度。2、IF时间片内完成,便可准备撤离系统。3、IF时间片内未能完成,调度程序便将该进程转入第二队列的末尾等待再次被调度执行。4、当第一队列中的进程都执行完,系统再按FCFS原则调度第二队列。在第二队列的稍放长些的时间片内仍未完成,再依次将它放入第三队列。5、依次降到第n队列后,在第n队列中便采取按时间片轮转的方式运行。
- 说明:1、设置多个就绪队列,各队列有不同的优先级,优先级从第一个队列依次降低。2、赋予各队列进程执行时间片大小不同, 优先权越高,时间片越短。3、仅当优先权高的队列(如第一队列)空闲时,调度程序才调度第二队列中的进程运行。4、高优先级抢占时,被抢占的进程放回原就绪队列末尾。
死锁(死锁条件、避免死锁、死锁检测、死锁预防)
死锁的定义:
多个进行相互等待对方资源,在得到所有资源继续运行之前,都不会释放自己已有的资源,这样造成了循环等待的现象,称为死锁。
产生死锁的四大必要条件:
-
资源互斥/资源不共享
每个资源要么已经分配给了一个进程,要么是可用的,只有这两种状态,资源不可以被共享使用,所以所谓的互斥是指:资源不共享,如果被使用,只能被一个进程使用。 -
占有和等待/请求并保持
已经得到资源的进程还能继续请求新的资源,所以个人觉得叫占有并请求也许更好理解。 -
资源不可剥夺
当一个资源分配给了一个进程后,其它需要该资源的进程不ß能强制性获得该资源,除非该资源的当前占有者显示地释放该资源。 -
环路等待
死锁发生时,系统中一定有由两个或两个以上的进程组成的一条环路,环路上的每个进程都在等待下一个进程所占有的资源。
防止死锁的方法:
-
破坏互斥条件
- 方法:如果允许系统资源都能共享使用,则系统不会进入死锁状态。
- 缺点:有些资源根本不能同时访问,如打印机等临界资源只能互斥使用。所以,破坏互斥条件而预防死锁的方法不太可行,而且在有的场合应该保护这种互斥性。
-
破坏请求并保持条件
- 方法:釆用预先静态分配方法,即进程在运行前一次申请完它所需要的全部资源,在它的资源未满足前,不把它投入运行。一旦投入运行后,这些资源就一直归它所有,也不再提出其他资源请求,这样就可以保证系统不会发生死锁。
- 缺点:系统资源被严重浪费,其中有些资源可能仅在运行初期或运行快结束时才使用,甚至根本不使用。而且还会导致“饥饿”现象,当由于个别资源长期被其他进程占用时,将致使等待该资源的进程迟迟不能开始运行。
-
破坏不可剥夺条件
- 方法:当一个已保持了某些不可剥夺资源的进程,请求新的资源而得不到满足时,它必须释放已经保持的所有资源,待以后需要时再重新申请。这意味着,一个进程已占有的资源会被暂时释放,或者说是被剥夺了,或从而破坏了不可剥夺条件。
- 缺点:该策略实现起来比较复杂,释放已获得的资源可能造成前一阶段工作的失效,反复地申请和释放资源会增加系统开销,降低系统吞吐量。这种方法常用于状态易于保存和恢复的资源,如CPU的寄存器及内存资源,一般不能用于打印机之类的资源。
-
破坏循环等待条件
- 方法:为了破坏循环等待条件,可釆用顺序资源分配法。首先给系统中的资源编号,规定每个进程,必须按编号递增的顺序请求资源,同类资源一次申请完。也就是说,只要进程提出申请分配资源Ri,则该进程在以后的资源申请中,只能申请编号大于Ri的资源。
- 缺点:这种方法存在的问题是,编号必须相对稳定,这就限制了新类型设备的增加;尽管在为资源编号时已考虑到大多数作业实际使用这些资源的顺序,但也经常会发生作业使用资源的顺序与系统规定顺序不同的情况,造成资源的浪费;此外,这种按规定次序申请资源的方法,也必然会给用户的编程带来麻烦。
死锁的检测方式
绘制资源分配图,进行简化。
死锁定理:
- 如果资源分配图中没有环路,则系统没有死锁。
- 如果资源分配图中出现了环路,则系统可能有死锁。
- 如果资源分配图不可完全简化,则系统死锁。
死锁的解除
- 资源剥夺法
挂起某些死锁进程,并抢占它的资源,将这些资源分配给其他的死锁进程。但应防止被挂起的进程长时间得不到资源,而处于资源匮乏的状态。 - 撤销进程法
强制撤销部分、甚至全部死锁进程并剥夺这些进程的资源。撤销的原则可以按进程优先级和撤销进程代价的高低进行。 - 进程回退法
让一(多)个进程回退到足以回避死锁的地步,进程回退时自愿释放资源而不是被剥夺。要求系统保持进程的历史信息,设置还原点。
文件和文件系统概述
文件管理
文件管理,由于系统的内存有限并且不能长期保存,故平时总是把它们以文件的形式存放在外存中,需要时再将它们调入内存。如何高效的对文件进行管理是操作系统实现的目标。
文件 是进程创建的信息逻辑单元
文件和文件系统
现代OS几乎都是通过文件系统来组织和管理在计算机中所存储的大量程序和数据的。文件系统的管理功能是通过把它所管理的程序和数据组织成一系列文件的方法来实现的。而文件则是指具有文件名的若干相关元素的集合。元素通常是记录,而记录是一组有意义的数据项的集合。可以把数据组成分为数据项、记录、文件。
-
数据项,数据项是最低级数据组织形式。分为基本数据项(用于描述一个对象某种属性的字符集,是数据组织中可以明明的最小逻辑数据单位,即原子数据,又称为数据元素或字段)和组合数据项(由若干个基本数据项组成)
-
记录,是一组相关数据项的集合,用于描述一个对象在某方面的属性,为了能够唯一标识一个记录,需要在一个记录的各个数据项中确定一个或几个数据项,把他们的集合称为关键字,关键字是能够唯一标识一个记录的数据项。
-
文件,文件是具有文件名的一组相关元素的集合,分为有结构文件(又称记录式文件:文件由一组相似记录组成 。如报考某学校的所有考生的报考信息记录)和无结构文件(又称流式文件:被看成是一个字符流。比如一个二进制文件或字符文件)。有结构文件由若干个相关记录组成,无结构文件则被看成一个字符流。文件是文件系统的最大数据单位。文件应该具有自己的属性,包括文件类型(如源文件、目标文件、可执行文件等),文件长度(文件的当前长度,也可能是最大允许长度),文件的物理位置(指示文件在哪一个设备上及在该设备的哪个位置的指针),文件的建立时间(文件最后一次修改时间)。
一个文件可对应若干个记录,一个记录可对应若干个数据项。
文件系统管理的对象有:
- 文件 - 作为文件管理的直接对象
- 目录 - 为了方便用户对文件的存取和检索,在文件系统中配置目录,每个目录项中,必须含有文件名及该文件所在的物理地址,对目录的组织和管理是方便和提高对文件存取速度的关键
- 磁盘 - 文件和目录必定占用存储空间,对这部分空间的有效管理,不仅能提高外存的利用率,而且能提高对文件的存取速度
文件的属性、基本操作以及文件的打开和关闭
文件的属性
- 名称:文件名称唯一,以容易读取的形式保存。
- 标识符:标识文件系统内文件的唯一标签,通常为数字,它是对人不可读的一种内部名称。
- 类型:被支持不同类型的文件系统所使用。
- 位置:指向设备和设备上文件的指针。
- 大小:文件当前大小(用字节、字或块表示),也可包含文件允许的最大值。
- 保护:对文件进行保护的访问控制信息。
- 时间、日期和用户标识:文件创建、上次修改和上次访问的相关信息,用于保护、 安全和跟踪文件的使用。
文件的基本橾作
- 创建文件,在创建一个新文件时,系统首先要为新文件分配必要的外存空间,并在文件系统的目录中,为之建立一个目录项,目录项中应该记录新文件的文件名及其在外存的地址等属性。
- 删除文件,当已不再需要某文件时,可将其从文件系统中删除,在删除时,系统应先从目录中找到要删除文件的目录项,使之成为空项,然后回收该文件所占用的存储空间。
- 读文件,读文件时,须在相应系统调用中给出文件名和应读入的内存目标地址。此时,系统要查找目录,找到指定目录项,从中得到被读文件在外存中的位置。在目录项中,还有一个指针用于对文件进行读/写。
- 写文件,写文件时,须在相应系统调用中给出文件名和其在内存源地址。此时,系统要查找目录,找到指定目录项,从再利用目录中的写指针进行写操作。
- 截断文件,如果一个文件的内容已经陈旧而需要全部更新时,一种方法是将此文件删除,再重新创建一个新文件,但如果文件名和属性均无改变,则可采取截断文件的方法,其将原有的文件长度设置为0,放弃原有文件的内容。
- 设置文件的读/写位置,用于设置文件读/写指针的位置,以便每次读/写文件时,不需要从始端开始而是从所设置的位置开始操作。可以改顺序存取为随机存取。
文件的打开和关闭
来源:当前OS所提供的大多数对文件的操作,其过程大致都是这样两步:首先,检索文件目录来找到指定文件的属性及其在外存上的位置;然后,对文件实施相应的操作,如读/写文件等,当用户要求对一个文件实施多次读/写或其他操作时,每次都要从检索目录开始,为了避免多次重复地检索目录,在大多数OS中都引入了打开这一文件系统调用,当用户第一次请求对某文件系统进行操作时,先利用open系统调用将该文件打开。
打开是指系统将指名文件的属性(包括该文件在外存上的物理位置)从外存拷贝到内存打开文件表的一个表目中,并将该表目的编号(索引号)返回给用户,以后,当用户再要求对该文件进行操作时,便可利用系统所返回的索引号向系统提出操作请求,系统便可直接利用该索引号到打开文件表中去查找,从而避免了对该文件的再次检索,如果用户不再需要对该文件实施操作,可利用关闭系统调用来关闭此文件,OS将会把该文件从打开文件表中的表目上删除掉。
文件的组织:文件的物理和逻辑结构,存取方法和VFAT表
文件的组织是指文件的构造方式,从用户观点出发观察到的文件组织结构称为文件的逻辑结构;而文件在外存上的存储组织形式称为文件的物理结构,又称文件的存储结构。
- 文件的逻辑结构和存取方法
从用户观点出发观察到的文件组织结构称为文件的逻辑结构,逻辑结构的文件称逻辑文件。逻辑文件从结构上分成二种形式,一种是无结构的流式文件,另一种是有结构的记录式文件。
流式文件是指对文件内信息不再划分单位,它是依次的一串字符流构成的文件。记录式文件是用户把文件内的信息按逻辑上独立的含义划分信息单位,每个单位称为一个逻辑记录(简称记录)。所有记录通常都是描述一个实体集的,有着相同或不同数目的数据项,记录的长度可分为定长和不定长记录两类。
记录文件有顺序、索引、索引顺序、直接、分区和堆文件几种。- 堆(The Pile)文件(累积文件)
堆文件是最简单的记录文件,它是串结构的顺序文件。数据按先来后到的次序组织,每个记录所包含的数据项是自我标识的,数据项的长度可以明确指定或使用界定符区分。在堆文件中访问所需要的记录需穷尽搜索,这种文件组织不适合大多数应用。 - 顺序文件(Sequential File)
顺序文件的记录定长,记录中的数据项的类型长度与次序固定,一般还有一个可以唯一标识记录的数据项,称为键(key),记录按键值的约定次序组织。顺序文件常用于批处理应用,对于查询或更新某个记录的请求的处理性能不佳。 - 索引文件(Indexed File)
索引文件对主文件中的记录按需要的数据项(一个或几个)建索引表。这时记录可为不定长的,它为每个记录设置一个表项。索引文件本身是顺序文件组织。 - 索引顺序文件(Indexed Sequential File)
索引顺序文件是基于键的约定次序组织的,为之建立一张索引表,为每个不同键值的记录组的第一个记录设置一个表项,为该组的其它记录设置了溢出区域,在溢出区域内记录按顺序文件方式组织。它是顺序文件和索引文件的结合。索引顺序文件既适用于交互方式应用,也适用于批处理方式应用。 - 直接文件/哈希文件(The Direct/Hashed File)
检索时给出记录编号,通过哈希函数计算出该记录在文件中的相对位置。它可以对记录在直接访问存储设备上的物理地址直接(随机)访问。直接文件常用于需要高速访问文件而且每次访问一条记录的应用中。
- 堆(The Pile)文件(累积文件)
用户通过对文件的存取来完成对文件的修改、追加和搜索等操作。常用的存取方法有顺序存取法、随机存取法(直接存取法)和按键存取法三种。
- 顺序存取法是按照文件的逻辑地址顺序存取,在记录式文件中,这反映为按记录的排列顺序来存取,在无结构的字符流文件中,顺序存取反映当前读写指针的变化。
- 随机存取法允许用户根据记录的编号来存取文件的任一记录,或者是根据存取命令把读写指针移到欲读写处来读写。
- 按键存取法是一种用在复杂文件系统,特别是数据库管理系统中的存取方法,文件的存取是根据给定的键或记录名进行的。
UNIX、Linux和Windows等操作系统都采用顺序存取和随机存取两种方法。
-
文件的物理结构
文件在存储介质上的组织方式称为文件的存储结构或称文件的物理结构、物理文件。
外存分配方法有连续分配、链接分配、索引分配,相应物理文件有:顺序文件、链接文件、索引文件。-
连续分配—顺序文件
把逻辑文件中连续的信息存储到磁盘连续的物理盘块中所形成的文件称为顺序文件。这种文件保证了逻辑文件中逻辑记录(流式文件为逻辑块、页)顺序和存储器中文件占用盘块顺序的一致性。为使系统能查找文件中任一记录,在文件控制块FCB(或在目录)中存放文件第一个记录所存放的盘块号ADRR和文件总的盘块数N。
顺序文件只适用于长度不变的只读文件。
优点是管理简单,顺序存取速度快。
缺点是增删记录相当困难,磁盘存储空间的利用率不高,有外零头。 -
链接分配-链接文件
在将逻辑文件存储到外存上时,不要求为整个文件分配连续的空间,而是可以装入到离散的多个盘块中,只在每个盘块最后一个单元设置链接指针(这称为隐式链接) ,然后用链接指针将这些离散的盘块链接成一个队列,这样形成的物理文件称为链接文件。管理链接文件只需在文件控制块FCB中设二项,一是存储文件头块信息的盘块号,另一是存储文件尾块信息的盘块号。
链接文件只适用于顺序存取文件
优点是盘存储空间利用率高,文件增删改记录方便。
缺点是在随机存取某一个记录前需要化多次盘I/O操作读该记录前的文件信息以取得该记录的盘块号,才能存取该记录。如要读取逻辑块号第3块的信息,就要先进行3次盘I/O操作以读取存放第3块逻辑块信息的盘块号. -
索引分配-索引文件
索引文件是实现非连续存储的另一种方法,系统为加快记录的检索过程,为每个文件建立了一张索引表,每个逻辑块在索引表中占有一个表项,登记存放该逻辑块的盘块号。在FCB中放置了索引表指针,它指向索引表始址,索引表存放在盘块中。
当索引表很大时,需要用多个盘块。管理有多个盘块的索引表有二种方法:- 一种方法是将存放索引表的盘块用链接指针链接起来称为链接索引。链接索引可以顺序地读取索引表各索引表项,但读取后面的索引表项类同链接文件需要化费多次盘I/O操作。
- 另一种方法是采用多级索引,即为索引表本身建立索引表,从而形成了两级索引,如所形成的两级索引表还不能存放在一个盘块中,则需要为二级索引表建索引表,而形成三级索引。
索引文件由于它既适合顺序存取记录又适合按任意次序随意存取记录,也便于增删文件的记录,所以索引结构文件应用范围较广。索引文件的缺点是当文件很大时索引表很庞大,占用了许多盘空间,而在文件很小时,多级索引级别又不变,带来索引块的另头和存取速度减慢。
- UNIX/Linux直接间接混合寻址方式
由于80%以上文件是小文件,为了解决能高速存取小文件和管理大文件的矛盾,UNIX将直接寻址、一级索引、二级索引和三级索引结合起来,形成了混合寻址方式。
-
-
VFAT表结构
MS-DOS文件系统的文件物理结构采用FAT表结构。该结构为了克服链接文件随机读取任一逻辑块需要化费多次盘I/O操作的不足,将各盘块中的链接指针集中存放在盘的开始部分,构成一张表,称为FAT表。(这称为显式链接)FAT表每一项存放链接指针(下一个簇号),每个FAT表项占12位或16位,称为FAT12或FAT16。对于软盘因为容量小,簇数也少,采用12位FAT表,对于硬盘则采用16位FAT表。
FAT表文件系统原为小硬盘的目录结构而设计,由于簇的数目最多只能用16位表示,即最多只能有64K个簇,要用FAT表管理大的磁盘分区,只能采取增大每簇所包含的扇区数,一般根据磁盘的类型和容量大小来决定簇的大小。
文件的目录和管理
- 文件控制块FCB
为了实现“按名存取”,系统必须为每个文件设置用于描述和控制文件的数据结构,它至少要包括文件名和存放文件的盘物理地址,这个数据结构称为文件控制块FCB,文件控制块的有序集合称为文件目录,即一个文件控制块FCB就是一个文件目录项。文件控制块FCB中包含的信息有以下三类:
- 基本信息类
文件名:标识一个文件的符号名,在每个系统中文件必须具有唯一的名字。
文件的物理地址:这由于文件的物理结构不同而不同。对于连续文件就是文件的起始块号和文件总块数;对于MS-DOS是文件的起始簇号和文件总字节数;对于UNIX S V是文件所在设备的设备号、13个地址项、文件长度和文件块数等。 - 存取控制信息类
文件的存取权限,象UNIX用户分成文件主、同组用户和一般用户三类,这三类用户的读写执行(RWX)的权限。 - 使用信息类
文件建立日期、最后一次修改日期、最后一次访问的日期;当前使用的信息:打开文件的进程数,在文件上的等待队列等。文件控制块的信息因OS而不同。
- 多级目录
目录结构的组织关系到文件系统的存取速度,关系到文件共享性和安全性,因此组织好文件的目录是设计文件系统的重要环节。
- 单级目录结构
最简单的目录结构是在整个文件系统中只建立一张目录表,每个文件占一个表目,这称为单级目录。单级目录结构简单,能实现目录管理的基本功能–按名存取,但存在查找速度慢,不允许重名和不便于实现文件共享等缺点,因此它只适用于单用户环境。 - 多级目录结构
为了解决以上问题,在多道程序设计系统中常采用多级目录结构,MS-DOS和UNIX等操作系统都采用多级目录结构。这种目录结构象一棵倒置的有根树,该树根向下,每一个节点是一个目录,最末一个结点是文件,下图为UNIX树形多级目录结构。在多级目录中要访问一个文件时,必须指出文件所在的路径名,路径名从根目录开始到该文件的通路上所有各级目录名拼起来得到,各目录名之间与文件名之间可用分隔符隔开。在MS-DOS中分隔符为“\”,在UNIX中分隔符为“/”。例如下图中访问命令文件man的路径名为/usr/lib/man,这也称为文件全名。
在多级目录中存取一个文件需要用文件全名,这就允许用户在自己的目录中使用与其它用户文件相同的文件名,由于各用户使用不同的目录,虽二者使用了相同的文件名,但它们的文件全名仍不相同,这就解决了重名问题。
采用多级目录结构也提高了检索目录的速度:如采用单级目录则查找一个文件最多需查遍系统所有文件名,平均也要查一半文件名。而多级目录查找一个文件最多只要查遍文件路径上各目录的子目录和文件,例如上图中要查找文件man,只要查遍root目录、usr目录和lib目录所在子目录和文件就可以。
每访问一个文件都要使用从根目录开始搜索直到树叶的数据文件为止,包含各中间子目录的全路径名是相当麻烦的,同时由于一个进程运行时访问的文件大多局限在某个范围,基于这一点,可为每个用户(或每个进程)设置一个“当前目录”,又称“工作目录”。进程对各文件的访问都相对于“工作目录”而设置路径,这称为相对路径名。相应地,从根目录开始的路径名称为绝对路径名(absolute path name)。用相对路径可缩短搜索路径,提高搜索速度。例:文件mbox的绝对路径名为/usr/ast/mbox,如设置一个“当前目录”为/usr,则文件mbox的相对路径名为ast/mbox。
- UNIX/Linux树型目录结构
UNIX为了加快目录的寻找速度,UNIX将文件控制块FCB中文件名和文件说明分开。文件说明为索引节点,各文件索引节点集中存放在索引节点区,索引节点按索引节点号排序。而文件名与索引节点号构成目录项,UNIX操作系统的文件名14个字节,索引节点2个字节,共16个字节构成目录项。同一级目录项构成目录文件,在文件区存放。
Linux目录文件中的目录项会变长,以保证系统支持文件名长度可变,最长达255个字符。目录项的前三项是定长的,包含以下信息:(1)索引节点号(4B);(2)目录项长度(2B);(3)文件名长度(2B)。目录项最后是文件名,目录项不能跨越二个块
。
每个文件有一个存放在磁盘索引节点区的索引节点,称为磁盘索引节点,它包括以下内容:(1)文件主标识符和同组用户标识符;(2)文件类型:是普通文件、目录文件、符号连接文件或特别文件(又分块设备文件或字符设备文件);(3)文件主,同组用户和其它人对文件存取权限(读R、写W、执行X);(4)文件的物理地址;(5)文件长度(字节数);(6)文件链接数;(7)文件最近存取和修改时间等。
- 文件别名(文件共享)
文件共享是指不同的用户使用不同的文件名来使用同一文件。在用一般共享目录结构时,一用户增加文件的内容,只改变自己的文件目录,其它用户不知改变。
- 基于索引节点(index node)的共享方式
实现文件共享的一种有效方法是基本文件目录法。该方法把所有目录的内容分为文件名和文件说明两部分。一部分称为符号文件目录(SDF),它包含文件符号名和由系统赋予唯一的内部标识符ID组成。另一部分称为基本文件目录(BDF),它包含目录项中除文件符号名外的所有其它信息,并加上与符号目录相一致的内部标识符ID。标识符ID是各文件在基本文件目录中表目的排序号。UNIX/Linux采用了基本文件目录法目录结构。
采用文件名和文件说明分离的目录结构有利于实现文件共享,这里的文件共享是指不同的用户对同一文件可取不同的文件名,即给文件取别名。文件共享是通过在二个不同子目录下取了不同的文件名,但它们具有相同的索引节点号的方法来实现,UNIX/Linux这种目录结构称为树形带勾连的目录结构。 - 利用符号连接(symbolic link, shortcut)
文件硬连接不利于文件主删除它拥有的文件,因为文件主要删除它拥有的共享文件,必须首先删除(关闭)所有的硬连接,否则就会造成共享该文件的用户的目录表目指针悬空。为此又提出另一种连接方法:符号连接。系统为共享的用户创建一个link类型的新文件,将这新文件登录在该用户共享目录项中,这个link型文件包含连接文件的路径名。该类文件在用ls命令长列表显示时,文件类型为l。当用户要访问共享文件且正要读link型新文件时,操作系统根据link文件类型性质将文件读出的内容作为路径名去访问真正的共享文件。在UNIX中也是使用ln来建立符号连接的共享文件。
采用符号连接可以跨越文件系统,甚至可以通过计算机网络连接到世界上任何地方的机器中的文件,此时只需提供该文件所在的地址,以及在该机器中的文件路径。
符号连接的缺点:其它用户读取符号连接的共享文件比读取硬连接的共享文件需要增多读盘操作。因为其它用户去读符号连接的共享文件时,系统中根据给定的文件路径名,逐个分量地去查找目录,通过多次读盘操作才能找到该文件的索引节点,而用硬连接的共享文件的目录文件表目中已包括了共享文件的索引节点号。
Windows 2000的NTFS,支持文件别名的符号链接方式。
- 内存目录管理
- 目录查询技术
为了实现用户对文件的按名存取,系统必须首先利用用户提供的文件名,对文件目录进行查询,找出该文件的文件控制块FCB,对UNIX系统即要找出该文件的索引节点。然后根据找到的FCB中所记录的文件物理地址,并根据文件物理组织方式找出文件的盘块号,进而换算出文件在磁盘上的物理位置(柱面号、磁头号、扇区号),最后启动磁盘驱动程序,将所需文件读入内存。对目录查询的技术有两种:线性检索法和Hash法。 - 内存的目录管理表
目录查询必须通过在磁盘上反复搜索来完成,这要化费不少次的盘块I/O,这样大大浪费了CPU处理时间,降低了处理速度。为了减轻盘I/O负担,采用把当前使用的文件目录表目复制到内存,建立内存的目录管理表。这样只要第一次使用某个目录表目时需要盘I/O来完成读入内存,以后使用该目录表目只要在内存目录管理表中进行,不需再进行盘块I/O,提高了处理速度,由于只把正在使用的目录复制到内存而不是全部目录读入内存,所以内存目录所占容量也不大。 - 文件操作与目录管理关系
为了让用户灵活方便和有效地使用文件,文件系统提供许多有关文件的系统调用,即文件操作供用户使用。文件系统提供的常用系统调用有“建立”(create)文件、“打开”(open)文件、“写”(write)文件、“读”(read)文件、“关闭”(close)文件、“删除”(delete)文件等。这些操作与目录管理的数据结构关系密切,下面以UNIX为例说明。- 建立文件
当用户提出在某个路径或目录下建立文件时,系统首先检查在该目录下有否相同名的文件,如没有的话则在磁盘目录中登记此文件,对UNIX系统则需给它分配索引节点,以保存文件存放的盘块号,同时在该目录文件中增加一个表目,记录文件名和相应的索引节点号。 - 打开文件
用户使用文件前必须使用“打开”命令打开文件,“打开”命令的任务是磁盘目录中找到该文件的FCB,拷贝到内存,建立内存的目录管理表。对UNIX系统是找到此文件所在的目录文件的表目和磁盘索引节点,然后将它们拷贝到内存,建立系统打开文件表和活动索引节点表有关表目,同时在PCB的用户打开文件表中增加表目,并建立如前图一样的三个表间的联系。 - 读/写文件
用户(或进程)要读写文件时,利用PCB中用户打开文件表的fP指针可以从三表关系中找到所需读/写的文件所的盘块,在系统打开文件表的表目中设置读写指针f_offset[2]分别记录了进程读/写文件的位置。如在写文件时增加或减少文件所用的磁盘块数,则要修改活动索引节点表的表目中的13个地址项i_addr[13]. - 关闭文件
当对文件操作完成后,或暂时不用,均需及时使用“关闭”命令关闭文件,将已修改的内存文件目录信息及时写回到磁盘目录中,并释放文件的内存目录表目空间供其它进程使用。对UNIX子系统,关闭文件,将文件在系统打开文件表和活动索引表的表目内容分别写回到磁盘的相应目录文件和索引节点表区,并删除相应表目供它用,同时进程打开文件表中相应表目也删除,以免进程打开文件数超过定值。如用户打开文件并进行读写后未关闭文件就退出,或者发生断电情况,这样都可能会造成这次读写修改信息的丢失。 - 删除文件
如文件不需要则要用“删除”命令删除文件,根据用户提出的在某目录下的文件名,命令将该文件在目录文件中的表项和相应的索引节点内容删除,释放供它用。
- 建立文件
文件管理系统在操作系统中提供了什么功能
浏览器输入一个地址到看到返回内容中间发生了什么?
- 查询DNS,获取域名对应的IP。
DNS解析机制:- 检查本地hosts文件是否有这个网址的映射,如果有,就调用这个IP地址映射,解析完成。
- 如果没有,则查找本地DNS解析器缓存是否有这个网址的映射,如果有,返回映射,解析完成。
- 如果没有,则查找填写或分配的首选DNS服务器,称为本地DNS服务器。服务器接收到查询时:【如果要查询的域名包含在本地配置区域资源中,返回解析结果,查询结束,此解析具有权威性。】【如果要查询的域名不由本地DNS服务器区域解析,但服务器缓存了此网址的映射关系,返回解析结果,查询结束,此解析不具有权威性。】
- 如果本地DNS服务器也失效:通过迭代的方式一层层向上级DNS服务器请求,在顶级DNS解析服务器后则通过迭代的方式来查找。
- 客户机发送HTTP请求报文:
- 应用层:客户端发送HTTP请求报文
- 传输层:切分长数据,并确保可靠性。
- 网络层:进行路由
- 数据链路层:传输数据
- 物理层:物理传输bit
- 服务器端经过物理层→数据链路层→网络层→传输层→应用层,解析请求报文,发送HTTP响应报文。
- 客户端解析HTTP响应报文
- 浏览器开始显示HTML
- 浏览器重新发送请求获取图片、CSS、JS的数据。
- 浏览器渲染页面
渲染机制:
构建DOM树 -> CSS解析 -> 构建渲染树 -> 渲染树布局 -> 渲染树绘制- 构建DOM树
当浏览器客户端从服务器那接受到HTML文档后,就会遍历文档节点然后生成DOM树,DOM树结构和HTML标签一一对应。需要注意记下几点:- DOM树在构建的过程中可能会被CSS和JS的加载而执行阻塞。(这在后面会详细介绍。)
- display:none 的元素也会在DOM树中。
- 注释也会在DOM树中
- Script标签会在DOM树中
- CSS解析
浏览器会解析CSS文件并生成CSS规则树,在过程中,每个CSS文件都会被分析成StyleSheet对象,每个对象都包括CSS规则,CSS规则对象包括对应的选择器和声明对象以及其他对象。在这个过程需要注意的是:- CSS解析可以与DOM解析同进行。
- CSS解析与script的执行互斥。
- 在Webkit内核中进行了script执行优化,只有在JS访问CSS时才会发生互斥。
- 构建渲染树
通过DOM树和CSS规则树,浏览器就可以通过它两构建渲染树了。浏览器会先从DOM树的根节点开始遍历每个可见节点,让后对每个可见节点找到适配的CSS样式规则并应用。具体的规则有以下几点需要注意:- Render Tree和DOM Tree不完全对应。
- display: none的元素不在Render Tree中
- visibility: hidden的元素在Render Tree中
- 渲染树布局
布局阶段会从渲染树的更节点开始遍历,由于渲染树的每个节点都是一个Render Object对象,包含宽高,位置,背景色等样式信息。所以浏览器就可以通过这些样式信息来确定每个节点对象在页面上的确切大小和位置,布局阶段的输出就是我们常说的盒子模型,它会精确地捕获每个元素在屏幕内的确切位置与大小。需要注意的是:- float元素,absoulte元素,fixed元素会发生位置偏移。
- 我们常说的脱离文档流,其实就是脱离Render Tree。
- 渲染树绘制
在绘制阶段,浏览器会遍历渲染树,调用渲染器的paint()方法在屏幕上显示其内容。渲染树的绘制工作是由浏览器的UI后端组件完成的。
- 构建DOM树
DNS的过程
域名并敲回车时,浏览器需要将其解析为IP地址才能真正通信。小明的电脑通常会配置一个本地域名服务器,其实这个本地域名服务器,只是用于加速域名解析而存在的。为了让读者更清晰地知道域名解析到底是如何工作的,现在假设小明配置的域名(DNS)服务器不是本地服务器,而是13个根域名服务器的任意一个,比如198.41.0.4
第一次查询(根级域名)
小明会给根域名服务器198.41.0.4发送一个DNS查询请求(请解析www.goole.com的IP地址),由于该根服务器(老爷爷) 并没有该记录,但是他却知道自己的一个孩子可能会知道,这个孩子的名字叫com服务器(爸爸)。于是这个根服务器(老爷爷)将皮球踢了回来,告诉小明,他孩子(爸爸)的IP地址,让小明直接联系爸爸。
第二次查询(一级域名)
小明的电脑又一次联系com服务器(爸爸),请求解析www.google.com的IP地址。爸爸在自己亲自管理的权威数据库,只查询到google.com对应的IP地址,但没有www.google.com的IP地址。但爸爸觉得儿子(google.com) 可能知道,将儿子的IP地址返回给小明。
第三次查询(二级域名)
小明的电脑锲而不舍,继续向儿子所在的IP查询。儿子看到域名笑了这不正是自己孩子 www.google.com(孙子)的域名吗?于是返回对应的IP给小明。
意味着小明的电脑光解析域名这一项工作,就跑了三个来回(Round Trip),需要3次RTT时间的延迟。
还需要1.5 次RTT时间延迟建立TCP连接,然后还需要至少2次RTT建立TLS安全连接。还需要至少1次HTTP业务交易RTT时间延迟。
至少需要7.5次RTT时间,平均一次RTT= 200ms, 那么意味着小明最快需要1.5秒的时间可以看到http页面。
本地DNS服务器
小明的电脑配置的DNS服务器是本地服务器,假设为114.114.114.114。小明直接联系该服务器做查询服务,该服务器查询自己的高速缓存,发现小丽刚刚查询过www.google.com的IP地址,在缓存中,于是直接返回。剩下的工作与之前相同。整个通信过程花费1 + 1.5 + 2 +1 = 5.5 RTT 时间延迟。
注意:本地DNS服务器缓存的IP是非权威性的。
DNS缓存机制
DNS缓存机制原理
简单来说,一条域名的DNS记录会在本地有两种缓存:浏览器缓存和操作系统(OS)缓存。在浏览器中访问的时候,会优先访问浏览器缓存,如果未命中则访问OS缓存,最后再访问DNS服务器(一般是ISP提供),然后DNS服务器会递归式的查找域名记录,然后返回。
DNS记录会有一个ttl值(time to live),单位是秒,意思是这个记录最大有效期是多少。经过实验,OS缓存会参考ttl值,但是不完全等于ttl值,而浏览器DNS缓存的时间跟ttl值无关,每种浏览器都使用一个固定值。
Windows访问DNS后会把记录保存一段短暂的时间,可通过ipconfig /displaydns 查看windows的DNS缓存、通过ipconfig /flushdns来清除。
hosts和DNS缓存
在不同系统中hosts优先还是本地DNS缓存优先,是不同的,在Unix下,优先访问顺序是可修改的。默认hosts优先。在Windows系统中,hosts文件会被直接加载到本地DNS缓存中。
TCP/IP四层参考模型
以下是TCP/IP分层模型
┌──────────────┐┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ ││ D │ F │ W │ F │ H │ G │ T │ I │ S │ U │ o │
│ ││ N │ I │ H │ T │ T │ O │ E │ R │ M │ S │ t │
│ 第四层 应用层 ││ S │ N │ O │ P │ T │ P │ L │ C │ T │ E │ h │
│ ││ │ G │ I │ │ P │ H │ N │ │ P │ N │ e │
│ ││ │ E │ S │ │ │ E │ E │ │ │ E │ r │
│ ││ │ R │ │ │ │ R │ T │ │ │ T │ s │
└──────────────┘└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
┌──────────────┐┌─────────────────────┬─────────────────────┐
│ 第三层 传输层 ││ T C P │ U D P │
└──────────────┘└─────────────────────┴─────────────────────┘
┌──────────────┐┌──────────┬─────────────────────┬──────────┐
│ ││ │ ICMP IGMP ARP RARP │ │
│ 第二层 网际层 ││ └─────────────────────┘ │
│ ││ I P │
└──────────────┘└───────────────────────────────────────────┘
┌──────────────┐┌─────────────────────┬─────────────────────┐
│ 第一层 接口层 ││ ARP / RARP │ others │
└──────────────┘└─────────────────────┴─────────────────────┘
TCP/IP四层参考模型
TCP/IP协议被组织成四个概念层,其中有三层对应于ISO参考模型中的相应层。TCP/IP协议族并不包含物理层和数据链路层,因此它不能独立完成整个计算机网络系统的功能,必须与许多其他的协议协同工作。
TCP/IP分层模型的四个协议层分别完成以下的功能:
- 第一层:网络接口层
包括用于协作IP数据在已有网络介质上传输的协议。实际上TCP/IP标准并不定义与ISO数据链路层和物理层相对应的功能。相反,它定义像地址解析协议(Address Resolution Protocol,ARP)这样的协议,提供TCP/IP协议的数据结构和实际物理硬件之间的接口。 - 第二层:网间层
对应于OSI七层参考模型的网络层。本层包含IP协议、RIP协议(Routing Information Protocol,路由信息协议),负责数据的包装、寻址和路由。同时还包含网间控制报文协议(Internet Control Message Protocol,ICMP)用来提供网络诊断信息。 - 第三层:传输层
对应于OSI七层参考模型的传输层,它提供两种端到端的通信服务。其中TCP协议(Transmission Control Protocol)提供可靠的数据流运输服务,UDP协议(Use Datagram Protocol)提供不可靠的用户数据报服务。 - 第四层:应用层
对应于OSI七层参考模型的应用层和表达层。因特网的应用层协议包括Finger、Whois、FTP(文件传输协议)、Gopher、HTTP(超文本传输协议)、Telent(远程终端协议)、SMTP(简单邮件传送协议)、IRC(因特网中继会话)、NNTP(网络新闻传输协议)等,这也是本书将要讨论的重点
TCP状态机以及三次握手和四次挥手
各个状态的解释
- LISTEN:监听来自远方TCP端口的连接请求。
- SYN-SENT:发送连接请求后等待匹配的连接请求。
- SYN-RCVD:收到SYN并发送一个连接请求后等待双方连接确认。
- FIN-WAIT-1:等待远程TCP的连接中断请求,或先前的连接中断请求的确认。(等待接收ACK)
- FIN-WAIT-2:从远程TCP等待连接中断请求。(等待接收FIN)
- CLOSING-WAIT:等待从本地用户发来的连接中断请求。
- CLOSING:等待远程TCP对连接中断请求的确认。
- LAST-ACK:等待原来发向远程TCP的连接中断请求的确认。
- TIME-WAIT:等待足够时间确保远程TCP接受连接中断请求的确认。
- CLOSED:无连接状态。
三次握手
- 第一次握手:
Client什么都不能确认。
Server确认:对方发送正常。 - 第二次握手:
Client确认:自己发送/接收正常,对方发送/接收正常。
Server确认:自己接受正常,对方发送正常。 - 第三次握手:
Client确认:自己发送/接收正常,对方发送/接收正常。
Server确认:自己接受正常,对方发送正常。
四次挥手
当收到对方的FIN报文时,仅表示对方不再发送数据但还能接收收据,我们也未必把全部数据都发给了对方,所以我们可以立即close,也可以发送一些数据给对方后,再发送FIN报文给对方表示同意关闭连接。因此我们的ACK和FIN一般会分开发送。
CLOSING状态出现的情况
为什么需要TIME_WAIT状态
-
为实现TCP这种全双工连接的可靠释放
这样可让TCP再次发送最后的ACK以防这个ACK丢失(另一端超时并重发最后的FIN)这种2MSL等待的另一个结果是这个TCP连接在2MSL等待期间,定义这个连接的插口(客户的IP地址和端口号,服务器的IP地址和端口号)不能再被使用。这个连接只能在2MSL结束后才能再被使用。 -
为使旧的数据包在网络因过期而消失
每个具体TCP实现必须选择一个报文段最大生存时间MSL。它是任何报文段被丢弃前在网络内的最长时间。
TCP序列号(Seq Number)和确认号(Ack Number)
TCP标志位
TCP在其协议头中使用大量的标志位或者说1位(bit)布尔域来控制连接状态,一个包中有可以设置多个标志位,应用最多的3个标志位为:
- SYN - 创建一个连接
- FIN - 终结一个连接
- ACK - 确认接收到的数据
TCP序列号和确认号
-
序列号
TCP会话的每一端都包含一个32位(bit)的序列号,该序列号被用来跟踪该端发送的数据量。每一个包中都包含序列号,在接收端则通过确认号用来通知发送端数据成功接收。序列号有字节序号,报文段序号。
其中第一次链接时,初始序列号是根据时间进行哈希散列计算后得到,属于随机生成。
报文段序号两部分,起始和长度。Seq = 301 Len = 100 代表数据字节序号为[301, 400]。 -
确认号
确认号是接收到TCP段后用来回复确认的。
ACK+1: 当发生Seq=1的包,接受端会传回ACK=2, 也就是将接受到的Seq+1,代表的是期望接受到的下一个包是 Seq=2的包。 (换句话说,和头部的标志位并没有任何关系)。
因为TCP是以流水线发出的,比如发送端顺序的发出 Seq=1、Seq=2、Seq=3。 那么如果ACK确认的序号和收到的包的序号一致的话,那么需要发回 ACK=1、ACK=2、ACK=3 共三个包。但是TCP协议对此进行了优化,只需要发送一个ACK包就能代表说自己已经收到了前面三个包, 那就是发送ACK=4 (期望收到Seq为4的包)。这样节省了ACK确认的数量。
另外TCP是的序号是根据数据流编码的, 假设最开始Seq=0 Len=3, 那么 ACK=4的时候:第一个意思是想表明期待收到下一个Seq为4的包。第二个意思实际上是说,想收到的包开始的那个比特位于数据流中的第四个比特。(下次从数据流中的第四个Byte开始发送)
服务器大量处于TIME_WAIT状态的原因和解决
原因
TIME_WAIT是主动关闭连接的一方保持的状态,对于服务器来说他本身就是“客户端”,在完成某一个中请求之后,他就会发起主动关闭连接,从而进入TIME_WAIT的状态,然后在保持这个状态2MSL(max segment
lifetime)时间之后,彻底关闭回收资源。
危害
- 高并发可以让服务器在短时间范围内同时占用大量端口,而端口有个0~65535的范围,并不是很多,刨除系统和其他服务要用的,剩下的就更少了。
- 在这个场景中,短连接表示“业务处理+传输数据的时间 远远小于 TIMEWAIT超时的时间”的连接。
解决方案
由于TIME_WAIT是主动发起一方的出现的问题,对网络配置参数进行优化
- 开启SYN Cookies:
net.ipv4.tcp_syncookies = 1
表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭; - 允许端口重用:
net.ipv4.tcp_tw_reuse = 1
表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭; - 开启快速回收机制:
net.ipv4.tcp_tw_recycle = 1
表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。 - 缩短TIMEOUT时间:
net.ipv4.tcp_fin_timeout
修改系統默认的TIMEOUT时间(30)
服务器大量处于CLOSE_WAIT状态的原因和解决
原因
一直保持在CLOSE_WAIT状态,那么只有一种情况,就是在对方关闭连接之后服务器程序自己没有进一步发出ack信号。换句话说,就是在对方连接关闭之后,程序里没有检测到,或者程序压根就忘记了这个时候需要关闭连接,于是这个资源就一直被程序占着。
解决方案
检查服务器程序代码,尤其是在确认连接关闭等地方函数,是否有异常分支导致程序没有处理而无法正常进行关闭连接的情况。
TCP协议如何保证可靠性
确保传输可靠性的方式
TCP协议保证数据传输可靠性的方式主要有:
- 校验和
- 序列号&确认应答
- 超时重传
- 连接管理
- 流量控制
- 拥塞控制
校验和
发送的数据包的二进制相加然后取反,目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错,TCP将丢弃这个报文段和不确认收到此报文段。 如果无差错,也不一定代表数据传输成功。
- 计算方式:在数据传输的过程中,将发送的数据段都当做一个16位的整数。将这些整数加起来。并且前面的进位不能丢弃,补在尾端进行相加,最后取反,得到校验和。
- 发送方:在发送数据之前计算检验和,并进行校验和的填充。
- 接收方:收到数据后,对数据以同样的方式进行计算,求出校验和,与发送方的进行比对。
序列号&确认应答
TCP给发送的每一个包进行编号,接收方对数据包进行排序,把有序数据传送给应用层。
- 序列号:TCP传输时将每个字节的数据都进行了编号,这就是序列号。
- 确认应答:TCP传输的过程中,每次接收方收到数据后,都会对传输方进行确认应答。也就是发送ACK报文。这个ACK报文当中带有对应的确认序列号,告诉发送方,接收到了哪些数据,下一次的数据从哪里发。
超时重传
当TCP发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。
- 原因 发送方没有介绍到响应的ACK报文原因:
- 数据在传输过程中由于网络原因等直接全体丢包,接收方根本没有接收到。
- 接收方接收到了响应的数据,但是发送的ACK报文响应却由于网络原因丢包了。
简单理解就是发送方在发送完数据后等待一个时间,时间到达没有接收到ACK报文,那么对刚才发送的数据进行重新发送。如果是刚才第一个原因,接收方收到二次重发的数据后,便进行ACK应答。如果是第二个原因,接收方发现接收的数据已存在(判断存在的根据就是序列号,所以上面说序列号还有去除重复数据的作用),那么直接丢弃,仍旧发送ACK应答。
由于TCP传输时保证能够在任何环境下都有一个高性能的通信,因此这个最大超时时间(发送方发送完毕后等待的时间)是动态计算的。一般是500ms。
连接管理
连接管理就是三次握手与四次挥手的过程。所有可靠性的理论前提。
流量控制
TCP连接的每一方都有固定大小的缓冲空间,TCP的接收端只允许发送端发送接收端缓冲区能接纳的数据。当接收方来不及处理发送方的数据,能提示发送方降低发送的速率,防止包丢失。TCP使用的流量控制协议是可变大小的滑动窗口协议。
接收方有即时窗口(滑动窗口),随ACK报文发送。
接收端在接收到数据后,对其进行处理。如果发送端的发送速度太快,导致接收端的结束缓冲区很快的填充满了。此时如果发送端仍旧发送数据,那么接下来发送的数据都会丢包,继而导致丢包的一系列连锁反应,超时重传呀什么的。而TCP根据接收端对数据的处理能力,决定发送端的发送速度,这个机制就是流量控制。
在TCP协议的报头信息当中,有一个16位字段的窗口大小。在介绍这个窗口大小时我们知道,窗口大小的内容实际上是接收端接收数据缓冲区的剩余大小。这个数字越大,证明接收端接收缓冲区的剩余空间越大,网络的吞吐量越大。接收端会在确认应答发送ACK报文时,将自己的即时窗口大小填入,并跟随ACK报文一起发送过去。而发送方根据ACK报文里的窗口大小的值的改变进而改变自己的发送速度。如果接收到窗口大小的值为0,那么发送方将停止发送数据。并定期的向接收端发送窗口探测数据段,让接收端把窗口大小告诉发送端。
16位的窗口大小最大能表示65535个字节(64K),但是TCP的窗口大小最大并不是64K。在TCP首部中40个字节的选项中还包含了一个窗口扩大因子M,实际的窗口大小就是16为窗口字段的值左移M位。每移一位,扩大两倍。
拥塞控制
当网络拥塞时,减少数据的发送。
发送方有拥塞窗口,发送数据前比对接收方发过来的即使窗口,取小
慢启动、拥塞避免、拥塞发送、快速恢复
TCP传输的过程中,发送端开始发送数据的时候,如果刚开始就发送大量的数据,那么就可能造成一些问题。网络可能在开始的时候就很拥堵,如果给网络中在扔出大量数据,那么这个拥堵就会加剧。拥堵的加剧就会产生大量的丢包,就对大量的超时重传,严重影响传输。
所以TCP引入了慢启动的机制,在开始发送数据时,先发送少量的数据探路。探清当前的网络状态如何,再决定多大的速度进行传输。这时候就引入一个叫做拥塞窗口的概念。发送刚开始定义拥塞窗口为 1,每次收到ACK应答,拥塞窗口加1。在发送数据之前,首先将拥塞窗口与接收端反馈的窗口大小比对,取较小的值作为实际发送的窗口。
拥塞窗口的增长是指数级别的。慢启动的机制只是说明在开始的时候发送的少,发送的慢,但是增长的速度是非常快的。为了控制拥塞窗口的增长,不能使拥塞窗口单纯的加倍,设置一个拥塞窗口的阈值,当拥塞窗口大小超过阈值时,不能再按照指数来增长,而是线性的增长。在慢启动开始的时候,慢启动的阈值等于窗口的最大值,一旦造成网络拥塞,发生超时重传时,慢启动的阈值会为原来的一半(这里的原来指的是发生网络拥塞时拥塞窗口的大小),同时拥塞窗口重置为1。
UDP协议特点和如何实现可靠
UDP协议全称是用户数据报协议,在网络中它与TCP协议一样用于处理数据包,是一种无连接的协议。在OSI模型中,在第四层——传输层,处于IP协议的上一层。UDP有不提供数据包分组、组装和不能对数据包进行排序的缺点,也就是说,当报文发送之后,是无法得知其是否安全完整到达的。
特点
-
面向无连接
首先 UDP 是不需要和 TCP一样在发送数据前进行三次握手建立连接的,想发数据就可以开始发送了。并且也只是数据报文的搬运工,不会对数据报文进行任何拆分和拼接操作。
具体来说就是:- 在发送端,应用层将数据传递给传输层的 UDP 协议,UDP 只会给数据增加一个 UDP 头标识下是 UDP 协议,然后就传递给网络层了
- 在接收端,网络层将数据传递给传输层,UDP 只去除 IP 报文头就传递给应用层,不会任何拼接操作
-
有单播,多播,广播的功能
UDP 不止支持一对一的传输方式,同样支持一对多,多对多,多对一的方式,也就是说 UDP 提供了单播,多播,广播的功能。 -
UDP是面向报文的
发送方的UDP对应用程序交下来的报文,在添加首部后就向下交付IP层。UDP对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界。因此,应用程序必须选择合适大小的报文 -
不可靠性
首先不可靠性体现在无连接上,通信都不需要建立连接,想发就发,这样的情况肯定不可靠。
并且收到什么数据就传递什么数据,并且也不会备份数据,发送数据也不会关心对方是否已经正确接收到数据了。
再者网络环境时好时坏,但是 UDP 因为没有拥塞控制,一直会以恒定的速度发送数据。即使网络条件不好,也不会对发送速率进行调整。这样实现的弊端就是在网络条件不好的情况下可能会导致丢包,但是优点也很明显,在某些实时性要求高的场景(比如电话会议)就需要使用 UDP 而不是 TCP。 -
头部开销小,传输数据报文时是很高效的。
UDP 头部包含了以下几个数据:- 两个十六位的端口号,分别为源端口(可选字段)和目标端口
- 整个数据报文的长度
- 整个数据报文的检验和(IPv4 可选 字段),该字段用于发现头部信息和数据中的错误
因此 UDP 的头部开销小,只有八字节,相比 TCP 的至少二十字节要少得多,在传输数据报文时是很高效的
如何实现UDP的可靠性
原理与简单实现
传输层无法保证数据的可靠传输,只能通过应用层来实现了。实现的方式可以参照tcp可靠性传输的方式,只是实现不在传输层,实现转移到了应用层。
最简单的方式是在应用层模仿传输层TCP的可靠性传输。下面不考虑拥塞处理,可靠UDP的简单设计。
- 添加seq/ack机制,确保数据发送到对端
- 添加发送和接收缓冲区,主要是用户超时重传。
- 添加超时重传机制。
开源程序
-
RUDP(Reliable User Datagram Protocol)
RUDP 提供一组数据服务质量增强机制,如拥塞控制的改进、重发机制及淡化服务器算法等,从而在包丢失和网络拥塞的情况下, RTP 客户机(实时位置)面前呈现的就是一个高质量的 RTP 流。在不干扰协议的实时特性的同时,可靠 UDP 的拥塞控制机制允许 TCP 方式下的流控制行为。 -
RTP(Real Time Protocol)
RTP为数据提供了具有实时特征的端对端传送服务,如在组播或单播网络服务下的交互式视频音频或模拟数据。
应用程序通常在 UDP 上运行 RTP 以便使用其多路结点和校验服务;这两种协议都提供了传输层协议的功能。但是 RTP 可以与其它适合的底层网络或传输协议一起使用。如果底层网络提供组播方式,那么 RTP 可以使用该组播表传输数据到多个目的地。
RTP 本身并没有提供按时发送机制或其它服务质量(QoS)保证,它依赖于底层服务去实现这一过程。 RTP 并不保证传送或防止无序传送,也不确定底层网络的可靠性。 RTP 实行有序传送, RTP 中的序列号允许接收方重组发送方的包序列,同时序列号也能用于决定适当的包位置,例如:在视频解码中,就不需要顺序解码。 -
UDT(UDP-based Data Transfer Protocol)
基于UDP的数据传输协议(UDP-basedData Transfer Protocol,简称UDT)是一种互联网数据传输协议。UDT的主要目的是支持高速广域网上的海量数据传输,而互联网上的标准数据传输协议TCP在高带宽长距离网络上性能很差。
顾名思义,UDT建于UDP之上,并引入新的拥塞控制和数据可靠性控制机制。UDT是面向连接的双向的应用层协议。它同时支持可靠的数据流传输和部分可靠的数据报传输。由于UDT完全在UDP上实现,它也可以应用在除了高速数据传输之外的其它应用领域,例如点到点技术(P2P),防火墙穿透,多媒体数据传输等等。
TCP和UDP的简单对比
UDP | TCP | |
---|---|---|
是否连接 | 无连接 | 面向连接 |
是否可靠 | 不可靠传输,不使用流量控制和拥塞控制 | 可靠传输,使用流量控制和拥塞控制 |
连接对象个数 | 支持一对一,一对多,多对一和多对多交互通信 | 只能是一对一通信 |
传输方式 | 面向报文 | 面向字节流 |
首部开销 | 首部开销小,仅8字节 | 首部最小20字节,最大60字节 |
适用场景 | 适用于实时应用(IP电话、视频会议、直播等) | 适用于要求可靠传输的应用,例如文件传输 |
TCP和UDP监听同一个端口的实现
TCP监听办法:
SOCKET sock = socket(TCP)
sockaddr_in sin
sin.port = htons(xxx)
bind(sock, sin)
listen(sock)
这样就进入了监听状态,在xxx端口,接下来可以accept了
UDP监听办法
SOCKET sock = socket(UDP)
sockaddr_in sin
sin.port = htons(xxx)
bind(sock, sin)
这样就进入了监听状态,在xxx端口,接下来可以recvfrom和sentto了
一般的,如果有另一个请求(无论是否在同一个进程,无论是tcp还是udp)也监听xxx端口,会在bind处报错(一般情况,具体不展开了)
注意几点:
- 端口不是物理概念,仅仅是协议栈中的两个字节
- TCP和UDP的端口完全没有任何关系,完全有可能又有一种XXP基于IP,也有端口的概念,这是完全可能的。
- TCP和UDP传输协议监听同一个端口后,接收数据互不影响,不冲突。因为数据接收时时根据五元组{传输协议,源IP,目的IP,源端口,目的端口}判断接受者的。
TCP报文
TCP报文是TCP层传输的数据单元,也叫报文段。
- 端口号:用来标识同一台计算机的不同的应用进程。
1)源端口:源端口和IP地址的作用是标识报文的返回地址。
2)目的端口:端口指明接收方计算机上的应用程序接口。
TCP报头中的源端口号和目的端口号同IP数据报中的源IP与目的IP唯一确定一条TCP连接。
-
序号和确认号:是TCP可靠传输的关键部分。序号是本报文段发送的数据组的第一个字节的序号。在TCP传送的流中,每一个字节一个序号。e.g.一个报文段的序号为300,此报文段数据部分共有100字节,则下一个报文段的序号为400。所以序号确保了TCP传输的有序性。确认号,即ACK,指明下一个期待收到的字节序号,表明该序号之前的所有数据已经正确无误的收到。确认号只有当ACK标志为1时才有效。比如建立连接时,SYN报文的ACK标志位为0。
-
数据偏移/首部长度:4bits。由于首部可能含有可选项内容,因此TCP报头的长度是不确定的,报头不包含任何任选字段则长度为20字节,4位首部长度字段所能表示的最大值为1111,转化为10进制为15,15*32/8 = 60,故报头最大长度为60字节。首部长度也叫数据偏移,是因为首部长度实际上指示了数据区在报文段中的起始偏移值。
-
保留:为将来定义新的用途保留,现在一般置0。
-
控制位:URG ACK PSH RST SYN FIN,共6个,每一个标志位表示一个控制功能。
1)URG:紧急指针标志,为1时表示紧急指针有效,为0则忽略紧急指针。
2)ACK:确认序号标志,为1时表示确认号有效,为0表示报文中不含确认信息,忽略确认号字段。
3)PSH:push标志,为1表示是带有push标志的数据,指示接收方在接收到该报文段以后,应尽快将这个报文段交给应用程序,而不是在缓冲区排队。
4)RST:重置连接标志,用于重置由于主机崩溃或其他原因而出现错误的连接。或者用于拒绝非法的报文段和拒绝连接请求。
5)SYN:同步序号,用于建立连接过程,在连接请求中,SYN=1和ACK=0表示该数据段没有使用捎带的确认域,而连接应答捎带一个确认,即SYN=1和ACK=1。
6)FIN:finish标志,用于释放连接,为1时表示发送方已经没有数据发送了,即关闭本方数据流。 -
窗口:滑动窗口大小,用来告知发送端接受端的缓存大小,以此控制发送端发送数据的速率,从而达到流量控制。窗口大小时一个16bit字段,因而窗口大小最大为65535。
-
校验和:奇偶校验,此校验和是对整个的 TCP 报文段,包括 TCP 头部和 TCP 数据,以 16 位字进行计算所得。由发送端计算和存储,并由接收端进行验证。
-
紧急指针:只有当 URG 标志置 1 时紧急指针才有效。紧急指针是一个正的偏移量,和顺序号字段中的值相加表示紧急数据最后一个字节的序号。 TCP 的紧急方式是发送端向另一端发送紧急数据的一种方式。
-
选项和填充:最常见的可选字段是最长报文大小,又称为MSS(Maximum Segment Size),每个连接方通常都在通信的第一个报文段(为建立连接而设置SYN标志为1的那个段)中指明这个选项,它表示本端所能接受的最大报文段的长度。选项长度不一定是32位的整数倍,所以要加填充位,即在这个字段中加入额外的零,以保证TCP头是32的整数倍。
-
数据部分:TCP 报文段中的数据部分是可选的。在一个连接建立和一个连接终止时,双方交换的报文段仅有 TCP 首部。如果一方没有数据要发送,也使用没有任何数据的首部来确认收到的数据。在处理超时的许多情况中,也会发送不带任何数据的报文段。
UDP报文
UDP协议分为首部字段和数据字段,其中首部字段只占用8个字节,分别是个占用两个字节的源端口、目的端口、长度和检验和。
长度:UDP报文的整个大小,最小为8个字节(仅为首部)。
检验和:在进行检验和计算时,会添加一个伪首部一起进行运算。伪首部(占用12个字节)为:4个字节的源IP地址、4个字节的目的IP地址、1个字节的0、一个字节的数字17、以及占用2个字节UDP长度。这个伪首部不是报文的真正首部,只是引入为了计算校验和。相对于IP协议的只计算首部,UDP检验和会把首部和数据一起进行校验。接收端进行的校验和与UDP报文中的校验和相与,如果无差错应该全为1。如果有误,则将报文丢弃或者发给应用层、并附上差错警告。
TCP和UDP报文的大小,以及IP分节
传输层:
对于UDP协议来说,整个包的最大长度为65535,其中包头长度是65535-20=65515;
对于TCP协议来说,整个包的最大长度是由最大传输大小(MSS,Maxitum Segment Size)决定,MSS就是TCP数据包每次能够传输的最大数据分段。为了达到最佳的传输效能TCP协议在建立连接的时候通常要协商双方的MSS值,这个值TCP协议在实现的时候往往用MTU值代替(需要减去IP数据包包头的大小20Bytes和TCP数据段的包头20Bytes)所以往往MSS为1460。 通讯双方会根据双方提供的MSS值得最小值确定为这次连接的最大MSS值。
IP层:
对于IP协议来说, IP包的大小由MTU决定 。 MTU值越大,封包就越大,理论上可增加传送速率,但MTU值又不能设得太大,因为封包太大,传送时出现错误的机会大增。
一般默认的设置如下:
PPPoE连接的最高MTU值是1492
以太网(Ethernet)的最高MTU值则是1500
Internet上,默认的MTU大小是576字节。
HTTP详解之HTTP介绍
HTTP 简介
HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传送协议。。
HTTP是一个基于TCP/IP通信协议来传递数据(HTML 文件, 图片文件, 查询结果等)。
HTTP 工作原理
HTTP协议工作于客户端-服务端架构上。浏览器作为HTTP客户端通过URL向HTTP服务端即WEB服务器发送所有请求。
Web服务器有:Apache服务器,IIS服务器(Internet Information Services)等。
Web服务器根据接收到的请求后,向客户端发送响应信息。
HTTP默认端口号为80,但是你也可以改为8080或者其他端口。
HTTP三点注意事项:
- HTTP是无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
- HTTP是媒体独立的:这意味着,只要客户端和服务器知道如何处理的数据内容,任何类型的数据都可以通过HTTP发送。客户端以及服务器指定使用适合的MIME-type内容类型。
- MIME Type 是该资源的媒体类型,MIME Type 不是个人指定的,是经过互联网(IETF)组织协商,以 RFC(是一系列以编号排定的文件,几乎所有的互联网标准都有收录在其中) 的形式作为建议的标准发布在网上的,大多数的 Web 服务器和用户代理都会支持这个规范 (顺便说一句,Email 附件的类型也是通过 MIME Type 指定的)。
- 媒体类型通常通过 HTTP 协议,由 Web 服务器告知浏览器的,更准确地说,是通过 Content-Type 来表示的。例如:Content-Type:text/HTML。
- 通常只有一些卓哉互联网上获得广泛应用的格式才会获得一个 MIME Type,如果是某个客户端自己定义的格式,一般只能以 application/x- 开头。
- HTTP是无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。
HTTP流程图
HTTP详解之HTTP请求报文
HTTP请求报文的组成
HTTP请求报文由3部分组成(请求行+请求头+请求体)
请求行
请求行由:请求方法 + 空格 + 请求URL + 空格 + HTTP协议及版本
构成。请求行必须在http请求格式的第一行。
- 请求方法表
序号 | 方法 | 描述 |
---|---|---|
1 | GET | 请求指定的页面信息,并返回实体主体。 将请求参数追加在url后面,不安全。 url长度限制get请求方式数据的大小。 没有请求体 |
2 | HEAD | 类似于 GET 请求,不过服务端接收到HEAD请求时只返回响应头,不发送响应内容。所以,如果只需要查看某个页面的状态时,用HEAD更高效,因为省去了传输页面内容的时间。 |
3 | POST | 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST
请求可能会导致新的资源的建立和/或已有资源的修改。 请求参数在请求体处,较安全。 请求数据大小没有显示 只有表单设置为method=“post”才是post请求,其他都是get请求 |
4 | PUT | 从客户端向服务器传送的数据取代指定的文档的内容。 本质上来讲, PUT和POST极为相似,都是向服务器发送数据,但它们之间有一个重要区别,PUT通常指定了资源的存放位置,而POST则没有,POST的数据存放位置由服务器自己决定。 |
5 | DELETE | 请求服务器删除指定的页面。 |
6 | CONNECT | HTTP/1.1 协议中预留给能够将连接改为管道方式的代理服务器。 通常用于SSL加密服务器的链接与非加密的HTTP代理服务器的通信。 |
7 | OPTIONS | 允许客户端查看服务器的性能。 |
8 | TRACE | 回显服务器收到的请求,主要用于测试或诊断。 |
9 | PATCH | 是对 PUT 方法的补充,用来对已知资源进行局部更新 。 |
请求头
*为常用请求头
Header | 解释 | 示例 |
---|---|---|
*Accept | 指定客户端能够接收的内容类型。告诉服务端,该请求所能支持的响应数据类型,专业术语称为MIME 类型(文件类型的一种描述方式) | Accept: text/plain, text/html |
Accept-Charset | 浏览器可以接受的字符编码集。 | Accept-Charset: iso-8859-5 |
*Accept-Encoding | 指定浏览器可以支持的web服务器返回内容压缩编码类型。 | Accept-Encoding: compress, gzip |
*Accept-Language | 浏览器可接受的语言 | Accept-Language: en,zh |
Accept-Ranges | 可以请求网页实体的一个或者多个子范围字段 | Accept-Ranges: bytes |
Authorization | HTTP授权的授权证书 | Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== |
*Cache-Control | 指定请求和响应遵循的缓存机制 | Cache-Control: no-cache |
*Connection | 表示是否需要持久连接。(HTTP 1.1默认进行持久连接) | Connection: close |
Cookie | HTTP请求发送时,会把保存在该请求域名下的所有cookie值一起发送给web服务器。 | Cookie: $Version=1; Skin=new; |
*Content-Length | 请求的内容长度 | Content-Length: 348 |
*Content-Type | 请求的与实体对应的MIME信息 | Content-Type: application/x-www-form-urlencoded |
*Date | 请求发送的日期和时间 | Date: Tue, 15 Nov 2010 08:12:31 GMT |
Expect | 请求的特定的服务器行为 | Expect: 100-continue |
From | 发出请求的用户的Email | From: user@email.com |
*Host | 指定请求的服务器的域名和端口号 | Host: www.zcmhi.com |
If-Match | 只有请求内容与实体相匹配才有效 | If-Match: “737060cd8c284d8af7ad3082f209582d” |
*If-Modified-Since | 如果请求的部分在指定时间之后被修改则请求成功,未被修改则返回304代码。控制浏览器缓存 | If-Modified-Since: Sat, 29 Oct 2010 19:43:31 GMT |
If-None-Match | 如果内容未改变返回304代码,参数为服务器先前发送的Etag,与服务器回应的Etag比较判断是否改变 | If-None-Match: “737060cd8c284d8af7ad3082f209582d” |
If-Range | 如果实体未改变,服务器发送客户端丢失的部分,否则发送整个实体。参数也为Etag | If-Range: “737060cd8c284d8af7ad3082f209582d” |
If-Unmodified-Since | 只在实体在指定时间之后未被修改才请求成功 | If-Unmodified-Since: Sat, 29 Oct 2010 19:43:31 GMT |
Max-Forwards | 限制信息通过代理和网关传送的时间 | Max-Forwards: 10 |
Pragma | 用来包含实现特定的指令 | Pragma: no-cache |
Proxy-Authorization | 连接到代理的授权证书 | Proxy-Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== |
Range | 只请求实体的一部分,指定范围 | Range: bytes=500-999 |
*Referer | 表示这个请求是从哪个url跳过来的,通过百度来搜索淘宝网,那么在进入淘宝网的请求报文中,Referer的值就是:www.baidu.com。如果是直接访问就不会有这个头。常用于防盗链 | Referer: http://www.zcmhi.com/archives/71.html |
TE | 客户端愿意接受的传输编码,并通知服务器接受接受尾加头信息 | TE: trailers,deflate;q=0.5 |
Upgrade | 向服务器指定某种传输协议以便服务器进行转换(如果支持) | Upgrade: HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11 |
*User-Agent | 浏览器通知服务器,客户端浏览器与操作系统相关信息 | User-Agent: Mozilla/5.0 (Linux; X11) |
Via | 通知中间网关或代理服务器地址,通信协议 | Via: 1.0 fred, 1.1 nowhere.com (Apache/1.1) |
Warning | 关于消息实体的警告信息 | Warn: 199 Miscellaneous warning |
HTTP详解之HTTP响应报文
HTTP响应报文的组成
HTTP的响应报文也由三部分组成(响应行+响应头+响应体)
响应行
响应行由 报文协议及版本 + 空格 + HTTP响应状态码 + 空格 + 状态码名称(描述)
响应头
Header | 解释 | 示例 |
---|---|---|
Accept-Ranges | 表明服务器是否支持指定范围请求及哪种类型的分段请求 | Accept-Ranges: bytes |
Age | 从原始服务器到代理缓存形成的估算时间(以秒计,非负) | Age: 12 |
Allow | 对某网络资源的有效的请求行为,不允许则返回405 | Allow: GET, HEAD |
Cache-Control | 告诉所有的缓存机制是否可以缓存及哪种类型 | Cache-Control: no-cache |
Content-Encoding | web服务器支持的返回内容压缩编码类型。 | Content-Encoding: gzip |
Content-Language | 响应体的语言 | Content-Language: en,zh |
Content-Length | 响应体的长度 | Content-Length: 348 |
Content-Location | 请求资源可替代的备用的另一地址 | Content-Location: /index.htm |
Content-MD5 | 返回资源的MD5校验值 | Content-MD5: Q2hlY2sgSW50ZWdyaXR5IQ== |
Content-Range | 在整个返回体中本部分的字节位置 | Content-Range: bytes 21010-47021/47022 |
Content-Type | 返回内容的MIME类型 | Content-Type: text/html; charset=utf-8 |
Date | 原始服务器消息发出的时间 | Date: Tue, 15 Nov 2010 08:12:31 GMT |
ETag | 请求变量的实体标签的当前值 | ETag: “737060cd8c284d8af7ad3082f209582d” |
Expires | 响应过期的日期和时间 | Expires: Thu, 01 Dec 2010 16:00:00 GMT |
*Last-Modified | 请求资源的最后修改时间 服务器通知浏览器,文件的最后修改时间。与If-Modified-Since一起使用。 |
Last-Modified: Tue, 15 Nov 2010 12:45:26 GMT |
*Location | 用来重定向接收方到非请求URL的位置来完成请求或标识新的资源 指定响应的路径,需要与状态码302配合使用,完成跳转。 |
Location: http://www.zcmhi.com/archives/94.html |
Pragma | 包括实现特定的指令,它可应用到响应链上的任何接收方 | Pragma: no-cache |
Proxy-Authenticate | 它指出认证方案和可应用到代理的该URL上的参数 | Proxy-Authenticate: Basic |
refresh | 应用于重定向或一个新的资源被创造,在5秒之后重定向(由网景提出,被大部分浏览器支持) |
Refresh: 5; url=
|
Retry-After | 如果实体暂时不可取,通知客户端在指定时间之后再次尝试 | Retry-After: 120 |
Server | web服务器软件名称 | Server: Apache/1.3.27 (Unix) (Red-Hat/Linux) |
*Set-Cookie | 设置Http Cookie 与会话相关技术。服务器向浏览器写入cookie |
Set-Cookie: UserID=JohnDoe; Max-Age=3600; Version=1 |
Trailer | 指出头域在分块传输编码的尾部存在 | Trailer: Max-Forwards |
Transfer-Encoding | 文件传输编码 | Transfer-Encoding:chunked |
Vary | 告诉下游代理是使用缓存响应还是从原始服务器请求 | Vary: * |
Via | 告知代理客户端响应是通过哪里发送的 | Via: 1.0 fred, 1.1 nowhere.com (Apache/1.1) |
Warning | 警告实体可能存在的问题 | Warning: 199 Miscellaneous warning |
WWW-Authenticate | 表明客户端请求实体应该使用的授权方案 | WWW-Authenticate: Basic |
HTTP状态码
HTTP状态码分类
HTTP状态码由三个十进制数字组成,第一个十进制数字定义了状态码的类型,后两个数字没有分类的作用。HTTP状态码共分为5种类型:
分类 | 分类描述 |
---|---|
1** | 信息,服务器收到请求,需要请求者继续执行操作 |
2** | 成功,操作被成功接收并处理 |
3** | 重定向,需要进一步的操作以完成请求 |
4** | 客户端错误,请求包含语法错误或无法完成请求 |
5** | 服务器错误,服务器在处理请求的过程中发生了错误 |
HTTP状态码列表
状态码 | 状态码英文名称 | 中文描述 |
---|---|---|
100 | Continue | 继续。客户端应继续其请求 |
101 | Switching Protocols | 切换协议。服务器根据客户端的请求切换协议。只能切换到更高级的协议,例如,切换到HTTP的新版本协议 |
200 | OK | 请求成功。一般用于GET与POST请求 |
201 | Created | 已创建。成功请求并创建了新的资源 |
202 | Accepted | 已接受。已经接受请求,但未处理完成 |
203 | Non-Authoritative Information | 非授权信息。请求成功。但返回的meta信息不在原始的服务器,而是一个副本 |
204 | No Content | 无内容。服务器成功处理,但未返回内容。在未更新网页的情况下,可确保浏览器继续显示当前文档 |
205 | Reset Content | 重置内容。服务器处理成功,用户终端(例如:浏览器)应重置文档视图。可通过此返回码清除浏览器的表单域 |
206 | Partial Content | 部分内容。服务器成功处理了部分GET请求 |
300 | Multiple Choices | 多种选择。请求的资源可包括多个位置,相应可返回一个资源特征与地址的列表用于用户终端(例如:浏览器)选择 |
301 | Moved Permanently | 永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替 |
302 | Found | 临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI |
303 | See Other | 查看其它地址。与301类似。使用GET和POST请求查看 |
304 | Not Modified | 未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。客户端通常会缓存访问过的资源,通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源 |
305 | Use Proxy | 使用代理。所请求的资源必须通过代理访问 |
306 | Unused | 已经被废弃的HTTP状态码 |
307 | Temporary Redirect | 临时重定向。与302类似。使用GET请求重定向 |
400 | Bad Request | 客户端请求的语法错误,服务器无法理解 |
401 | Unauthorized | 请求要求用户的身份认证 |
402 | Payment Required | 保留,将来使用 |
403 | Forbidden | 服务器理解请求客户端的请求,但是拒绝执行此请求 |
404 | Not Found | 服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置"您所请求的资源无法找到"的个性页面 |
405 | Method Not Allowed | 客户端请求中的方法被禁止 |
406 | Not Acceptable | 服务器无法根据客户端请求的内容特性完成请求 |
407 | Proxy Authentication Required | 请求要求代理的身份认证,与401类似,但请求者应当使用代理进行授权 |
408 | Request Time-out | 服务器等待客户端发送的请求时间过长,超时 |
409 | Conflict | 服务器完成客户端的 PUT 请求时可能返回此代码,服务器处理请求时发生了冲突 |
410 | Gone | 客户端请求的资源已经不存在。410不同于404,如果资源以前有现在被永久删除了可使用410代码,网站设计人员可通过301代码指定资源的新位置 |
411 | Length Required | 服务器无法处理客户端发送的不带Content-Length的请求信息 |
412 | Precondition Failed | 客户端请求信息的先决条件错误 |
413 | Request Entity Too Large | 由于请求的实体过大,服务器无法处理,因此拒绝请求。为防止客户端的连续请求,服务器可能会关闭连接。如果只是服务器暂时无法处理,则会包含一个Retry-After的响应信息 |
414 | Request-URI Too Large | 请求的URI过长(URI通常为网址),服务器无法处理 |
415 | Unsupported Media Type | 服务器无法处理请求附带的媒体格式 |
416 | Requested range not satisfiable | 客户端请求的范围无效 |
417 | Expectation Failed | 服务器无法满足Expect的请求头信息 |
500 | Internal Server Error | 服务器内部错误,无法完成请求 |
501 | Not Implemented | 服务器不支持请求的功能,无法完成请求 |
502 | Bad Gateway | 作为网关或者代理工作的服务器尝试执行请求时,从远程服务器接收到了一个无效的响应 |
503 | Service Unavailable | 由于超载或系统维护,服务器暂时的无法处理客户端的请求。延时的长度可包含在服务器的Retry-After头信息中 |
504 | Gateway Time-out | 充当网关或代理的服务器,未及时从远端服务器获取请求 |
505 | HTTP Version not supported | 服务器不支持请求的HTTP协议的版本,无法完成处理 |
HTTP协议以及如何让HTTP协议“有状态”?
一、HTTP协议的状态
HTTP是一种无状态协议,即服务器不保留与客户交易时的任何状态。
也就是说,上一次的请求对这次的请求没有任何影响,服务端也不会对客户端上一次的请求进行任何记录处理。
二、HTTP协议的无状态性带来的问题:
用户登录后,切换到其他界面,进行操作,服务器端是无法判断是哪个用户登录的。 每次进行页面跳转的时候,得重新登录。
三、解决方案
既然HTTP协议是无状态的,不会记录用户信息,那么怎么样才能让HTTP协议记录用户信息呢?换句话说,服务器怎么判断发来HTTP请求的是哪个用户?
于是,两种用于保持HTTP状态的技术就应运而生了,一个是 Cookie,而另一个则是 Session。
-
Cookie
Cookie 是客户端的存储空间,由浏览器来维持。具体来说 cookie 机制采用的是在客户端保持状态的方案。
Cookie,有时也用其复数形式Cookies,指某些网站为了辨别用户身份、进行 session 跟踪而储存在用户本地终端上的数据(通常经过加密)。- Cookie 的实现过程:
Cookie 会根据从服务器端发送的响应报文内的一个叫做 Set-Cookie 的首部字段信息,通知客户端保存 Cookie,当下次客户端再往该服务器发送请求时,客户端会自动在请求报文中加入 Cookie 值后发送出去。
也就是 Cookie 是服务器生成的,但是发送给客户端,并且由客户端来保存。每次请求加上 Cookie就行了。服务器端发现客户端发送过来的 Cookie 后,会去检查究竟是从哪一个客户端发来的连接请求,然后对比服务器上的记录,最后得到之前的状态信息。
- Cookie 的实现过程:
-
Session
Session,中文经常翻译为会话,其本来的含义是指有始有终的一系列动作/消息,比如打电话是从拿起电话拨号到挂断电话这中间的一系列过程可以称之为一个 Session。然而当 Session 一词与网络协议相关联时,它又往往隐含了“面向连接”或“保持状态”这样两个含义。
Session 是另一种记录客户状态的机制,不同的是 Cookie 保存在客户端浏览器中,而 Session 保存在服务器上。
客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上,这就是 Session。客户端浏览器再次访问时,只需要从该 Session 中查找该客户的状态就可以了。
虽然 Session 保存在服务器,对客户端是透明的,它的正常运行仍然需要客户端浏览器的支持。这是因为 Session 需要使用Cookie 作为识别标志。HTTP协议是无状态的,Session 不能依据HTTP连接来判断是否为同一客户,因此服务器向客户端浏览器发送一个名为 JSESSIONID 的 Cookie,它的值为该 Session 的 id(即放在HTTP响应报文头部信息里的Set-Cookie)。Session依据该 Cookie 来识别是否为同一用户。 -
Cookie 和 Session 的区别
-
Cookie 数据存放在客户的浏览器上,Session 数据放在服务器上;
-
Cookie 不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗,考虑到安全应当使用 Session ;
-
Session 会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能。考虑到减轻服务器性能方面,应当使用COOKIE;
-
单个Cookie 在客户端的限制是3K,就是说一个站点在客户端存放的COOKIE不能超过3K;
-
-
总结
如果说 Cookie 机制是通过检查客户身上的“通行证”来确定客户身份的话,那么Session机制就是通过检查服务器上的“客户明细表”来确认客户身份。Session 相当于程序在服务器上建立的一份客户档案,客户来访的时候只需要查询客户档案表就可以了。
Cookie 和 Session 的方案虽然分别属于客户端和服务端,但是服务端的 Session 的实现对客户端的 Cookie 有依赖关系的,上面我讲到服务端执行 Session 机制时候会生成 Session 的 id 值,这个 id 值会发送给客户端,客户端每次请求都会把这个 id 值放到 http 请求的头部发送给服务端,而这个 id 值在客户端会保存下来,保存的容器就是 Cookie,因此当我们完全禁掉浏览器的Cookie的时候,服务端的Session也会不能正常使用。
问:客户端浏览器将 Cookie 功能禁用,或者不支持 Cookie 怎么办?
一般这种情况下,会使用一种叫做 URL 重写的技术来进行会话跟踪,即每次HTTP交互,URL后面都会被附加上一个诸如 sid=xxxxx 这样的参数,服务端据此来识别用户。
Cookie 和 Session 应用场景:
-
登录网站,今输入用户名密码登录了,第二天再打开很多情况下就直接打开了。这个时候用到的一个机制就是cookie。
-
session一个场景是购物车,添加了商品之后客户端处可以知道添加了哪些商品,而服务器端如何判别呢,所以也需要存储一些信息就用到了session。
HTTP传输的文件过大怎么办
如果传输的文件过大怎么办?
服务器上返回的资源文件比较大,比如有些 js 文件大小可能就有几兆。文件过大就会影响传 输的效率,同时也会带来带宽的消耗。怎么办呢?
-
常见的手段是,对文件进行压缩,减少文件大小。那压缩和解压缩的流程怎么实现呢? 首先服务端需要能支持文件的压缩功能,其次浏览器能够针对被压缩的文件进行解压缩。浏 览器可以指定 Accept-Encoding 来高速服务器我当前支持的编码类型 Accept-Encoding:gzip,deflate 那服务端会根据支持的编码类型,选择合适的类型进行压缩。常见的编码方式有:gzip/deflate
-
分割传输 在传输大容量数据时,通过把数据分割成多块,能够让浏览器逐步显示页面。这种把实体主 体分块的功能称为分块传输编码(Chunked Transfer Coding)。
HTTPS加密过程详解
HTTPS的安全通信机制
基本分为三个阶段:
-
认证服务器
浏览器内置一个受信任的CA机构列表,并保存了这些CA机构的证书。第一阶段服务器会提供经CA机构认证颁发的服务器证书,如果认证该服务器证书的CA机构,存在于浏览器的受信任CA机构列表中,并且服务器证书中的信息与当前正在访问的网站(域名等)一致,那么浏览器就认为服务端是可信的,并从服务器证书中取得服务器公钥,用于后续流程。否则,浏览器将提示用户,根据用户的选择,决定是否继续。当然,我们可以管理这个受信任CA机构列表,添加我们想要信任的CA机构,或者移除我们不信任的CA机构。 -
协商会话密钥
客户端在认证完服务器,获得服务器的公钥之后,利用该公钥与服务器进行加密通信,协商出两个会话密钥,分别是用于加密客户端往服务端发送数据的客户端会话密钥,用于加密服务端往客户端发送数据的服务端会话密钥。在已有服务器公钥,可以加密通讯的前提下,还要协商两个对称密钥的原因,是因为非对称加密相对复杂度更高,在数据传输过程中,使用对称加密,可以节省计算资源。另外,会话密钥是随机生成,每次协商都会有不一样的结果,所以安全性也比较高。 -
加密通讯
此时客户端服务器双方都有了本次通讯的会话密钥,之后传输的所有Http数据,都通过会话密钥加密。这样网路上的其它用户,将很难窃取和篡改客户端和服务端之间传输的数据,从而保证了数据的私密性和完整性。
HTTPS的通信步骤
客户端:client-hello
- 步骤1:
客户端通过发送
Client-Hello
报文开始SSL通信。报文中包含客户端支持的最高TSL协议版本version、加密组件列表cipher_suites、压缩算法compresssion_method、随机数random_C和扩展字段extensions。
服务器:server-hello + certificate + server-hello-done
-
步骤2: 服务器可进行SSL通信时,会以
Server-Hello
报文作为应答。该报文包括服务器选择使用的TSL版本version、加密组件cipher_suites、压缩算法compression_method、随机数random_S。 -
步骤3: 之后服务器发送
Certificate
报文。报文中包含公开密钥证书。用于身份验证和密钥交换。 -
步骤4: 最后服务器发送
Server-Hello-Done
报文通知客户端,最初阶段的SSL握手协商部分结束。
客户端:证书验证
- 步骤5: 客户端验证证书的合法性,如果验证通过才会进行后续通信,否则根据错误情况不同做出提示和操作,合法性验证包括如下:证书链的可信性 trusted certificate path;证书是否吊销 revocation,有两类方式离线 CRL 与在线 OCSP,不同的客户端行为会不同; 有效期 expiry date,证书是否在有效时间范围;域名 domain,核查证书域名是否与当前的访问域名匹配,匹配规则后续分析;
客户端:client-key-exchange + change-cipher-spec + encrypted-handshake-message
-
步骤6: SSL第一次握手结束之后,客户端以
Client-Key-Exchange
报文作为回应。报文中包含客户端计算产生随机数字 Pre-master,并用步骤3中证书公钥加密,发送给服务器。 -
步骤7: 接着客户端继续发送
Change-Cipher-Spec
报文。在上一步完成后客户端已经获取全部的计算协商密钥需要的信息:两个明文随机数 random_C 和 random_S 与自己计算产生的 Pre-master,计算得到协商密钥 enc_key=Fuc(random_C, random_S, Pre-Master)。此时客户端通知服务器后续的通信都采用协商的通信密钥和加密算法进行加密通信; -
步骤8: 客户端发送
Finished(encrypted_handshake_message)
报文。结合之前所有通信参数的 hash 值与其它相关信息生成一段数据,采用协商密钥 session secret 与算法进行加密,然后发送给服务器用于数据与握手验证。这次握手协商是否能够成功,要以服务器是否能够正确解密该报文作为判定标准。
服务器:change-cipher-spec + encrypted-handshake-message
-
步骤9: 服务器用私钥解密加密的 Pre-master 数据,基于之前交换的两个明文随机数 random_C 和 random_S,计算得到协商密钥:enc_key=Fuc(random_C, random_S, Pre-Master);并且,计算之前所有接收信息的 hash 值,然后解密客户端发送的
encrypted_handshake_message
,验证数据和密钥正确性; -
步骤10: 服务器同样发送
Change-Cipher-Spec
报文。告知客户端后续的通信都采用协商的密钥与算法进行加密通信; -
步骤11: 服务器同样发送
Finshed(encrypted-handshake-message)
报文。服务器也结合所有当前的通信参数信息生成一段数据并采用协商密钥 session secret 与算法加密并发送到客户端;
握手结束
- 步骤11: 服务器和客户端的Finished报文交换完毕之后,客户端计算所有接收信息的 hash 值,并采用协商密钥解密 encrypted_handshake_message,验证服务器发送的数据和密钥,验证通过则握手完成。当然,通信会受到SSL的保护。从此处开始进行应用层协议的通信,即发送HTTP请求。
加密通信和断开
-
步骤12: 应用层协议通信,即发送HTTP响应。
-
步骤13: 最后由客户端断开连接。断开连接时,发送close_notify报文。上图做客一些省略,这步之后再发送TCP FIN报文来关闭与TCP的通信。
在以上流程中,应用层发送数据时会附加一种叫做MAC(Message Authentication Cods)的报文摘要。MAC能够查知报文是否遭到篡改从,从而保护报文的完整性。
SSL和TLS的区别
TLS和SSL的关系:并列关系
最新版本的TLS(Transport Layer Security,传输层安全协议)是IETF(Internet Engineering Task Force,Internet工程任务组)制定的一种新的协议,它建立在SSL 3.0协议规范之上,是SSL 3.0的后续版本。在TLS与SSL 3.0之间存在着显著的差别,主要是它们所支持的加密算法不同,所以TLS与SSL 3.0不能互操作。
TLS与SSL的差异
- 1)版本号:TLS记录格式与SSL记录格式相同,但版本号的值同,TLS的版本1.0使用的版本号为SSLv3.1。
- 2)报文鉴别码:SSLv3.0和TLS的MAC算法及MAC计算的范围同。TLS使用RFC-2104定义的HMAC算法。SSLv3.0使用了相似的法,两者差别在于SSLv3.0中,填充字节与密钥之间采用的是连接算,而HMAC算法采用的异或运算。但是两者的安全程度是相同的。
- 3)伪随机函数:TLS使用了称为PRF的伪随机函数来将密钥扩展数据块,是更安全的方式。
- 4)报警代码:TLS支持几乎所有的SSLv3.0报警代码,而且TLS补充定义了很多报警代码,如解密失败(decryption_failed)记录溢出(record_overflow)、未知CA(unknown_ca)、拒访问(access_denied)等。
- 5)密文族和客户证书:SSLv3.0和TLS存在少量差别,即TLS不持Fortezza密钥交换、加密算法和客户证书。
- 6)certificate_verify和finished消息:SSLv3.0和TLS用certificate_verify和finished消息计算MD5和SHA-1散列时,计算的输入有少许差别,但安全性相当。
- 7)加密计算:TLS和SSLv3.0在计算主密值(master secret时采用的方式不同。
- 8)填充:用户数据加密之前需要增加的填充字节。在SSL中,填充后的数据长度哟啊达到密文快长度的最小整数倍。而在TLS中,填充后的数据长度可以是密文块长度的任意整数倍(但填充的最大长度为255字节),这种方式可以防止基于对报文长度进行分析的攻击。
-
TLS的主要增强内容
TLS的主要目标是使SSL更安全,并使协议的规范更精确和完善。TLS在SSL v3.0的基础上,提供了以下增加内容:- 1)更安全的MAC算法
- 2)更严密的警报
- 3)“灰色区域”规范的更明确的定义
-
TLS对于安全性的改进
- 1)对于消息认证使用密钥散列法:TLS使用“消息认证代码的密钥散列法”(HMAC),当记录在开放的网络(如因特网)上传送时,该代码确保记录不会被变更。SSLv3.0还提供键控消息认证,但HMAC比SSLv3.0使用(消息认证代码)MAC功能更安全。
- 2)增强的伪随机功能(PRF):PRF生成密钥数据。在TLS中,HMAC定义PRF。PRF使用两种散列算法保证其安全性。如果任一算法暴露了,只要第二种算法未暴露,则数据仍然是安全的。
- 3)改进的已完成消息验证:TLS和SSLv3.0都对两个端点提供已完成的消息,该消息认证交换的消息没有被变更。然而,TLS将此已完成消息基于PRF和HMAC值之上,这也比SSLv3.0更安全。
- 4)一致证书处理:与SSLv3.0不同,TLS试图指定必须在TLS之间实现交换的证书类型。
- 5)特定警报消息:TLS提供更多的特定和附加警报,以指示任一会话端点检测到的问题。TLS还对何时应该发送某些警报进行记录。
HTTP1.0,HTTP1.1和HTTP2.0的比较
HTTP1.0和HTTP1.1的区别
-
长连接(Persistent Connection)
HTTP1.1支持长连接和请求的流水线处理,在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟,在HTTP1.1中默认开启长连接keep-alive,一定程度上弥补了HTTP1.0每次请求都要创建连接的缺点。HTTP1.0需要使用keep-alive参数来告知服务器端要建立一个长连接。 -
节约带宽
HTTP1.0中存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能。HTTP1.1支持只发送header信息(不带任何body信息),如果服务器认为客户端有权限请求服务器,则返回100,客户端接收到100才开始把请求body发送到服务器;如果返回401,客户端就可以不用发送请求body了节约了带宽。 -
HOST域
在HTTP1.0中认为每台服务器都绑定一个唯一的IP地址,因此,请求消息中的URL并没有传递主机名(hostname),HTTP1.0没有host域。随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个IP地址。HTTP1.1的请求消息和响应消息都支持host域,且请求消息中如果没有host域会报告一个错误(400 Bad Request)。 -
缓存处理
在HTTP1.0中主要使用header里的If-Modified-Since,Expires来做为缓存判断的标准,HTTP1.1则引入了更多的缓存控制策略例如Entity tag,If-Unmodified-Since, If-Match, If-None-Match等更多可供选择的缓存头来控制缓存策略。 -
错误通知的管理
在HTTP1.1中新增了24个错误状态响应码,如409(Conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除。
HTTP1.1和HTTP2.0的区别
-
多路复用
HTTP2.0使用了多路复用的技术,做到同一个连接并发处理多个请求,而且并发请求的数量比HTTP1.1大了好几个数量级。HTTP1.1也可以多建立几个TCP连接,来支持处理更多并发的请求,但是创建TCP连接本身也是有开销的。
-
头部数据压缩
在HTTP1.1中,HTTP请求和响应都是由状态行、请求/响应头部、消息主体三部分组成。一般而言,消息主体都会经过gzip压缩,或者本身传输的就是压缩过后的二进制文件,但状态行和头部却没有经过任何压缩,直接以纯文本传输。随着Web功能越来越复杂,每个页面产生的请求数也越来越多,导致消耗在头部的流量越来越多,尤其是每次都要传输UserAgent、Cookie这类不会频繁变动的内容,完全是一种浪费。HTTP1.1不支持header数据的压缩,HTTP2.0使用HPACK算法对header的数据进行压缩,这样数据体积小了,在网络上传输就会更快。
-
服务器推送
服务端推送是一种在客户端请求之前发送数据的机制。网页使用了许多资源:HTML、样式表、脚本、图片等等。在HTTP1.1中这些资源每一个都必须明确地请求。这是一个很慢的过程。浏览器从获取HTML开始,然后在它解析和评估页面的时候,增量地获取更多的资源。因为服务器必须等待浏览器做每一个请求,网络经常是空闲的和未充分使用的。为了改善延迟,HTTP2.0引入了server push,它允许服务端推送资源给浏览器,在浏览器明确地请求之前,免得客户端再次创建连接发送请求到服务器端获取。这样客户端可以直接从本地加载这些资源,不用再通过网络。
长链接和短链接
长链接介绍
HTTP1.1规定了默认保持长连接(HTTP persistent connection ,也有翻译为持久连接),数据传输完成了保持TCP连接不断开(不发RST包、不四次握手),等待在同域名下继续用这个通道传输数据;相反的就是短连接。
HTTP首部的Connection: Keep-alive是HTTP1.0浏览器和服务器的实验性扩展,当前的HTTP1.1 RFC2616文档没有对它做说明,因为它所需要的功能已经默认开启,无须带着它,但是实践中可以发现,浏览器的报文请求都会带上它。如果HTTP1.1版本的HTTP请求报文不希望使用长连接,则要在HTTP请求报文首部加上Connection: close。《HTTP权威指南》提到,有部分古老的HTTP1.0 代理不理解Keep-alive,而导致长连接失效:客户端-->代理-->服务端,客户端带有Keep-alive,而代理不认识,于是将报文原封不动转给了服务端,服务端响应了Keep-alive,也被代理转发给了客户端,于是保持了“客户端-->代理”连接和“代理-->服务端”连接不关闭,但是,当客户端第发送第二次请求时,代理会认为当前连接不会有请求了,于是忽略了它,长连接失效。书上也介绍了解决方案:当发现HTTP版本为1.0时,就忽略Keep-alive,客户端就知道当前不该使用长连接。其实,在实际使用中不需要考虑这么多,很多时候代理是我们自己控制的,如Nginx代理,代理服务器有长连接处理逻辑,服务端无需做patch处理,常见的是客户端跟Nginx代理服务器使用HTTP1.1协议&长连接,而Nginx代理服务器跟后端服务器使用HTTP1.0协议&短连接。
在实际使用中,HTTP头部有了Keep-Alive这个值并不代表一定会使用长连接,客户端和服务器端都可以无视这个值,也就是不按标准来,譬如我自己写的HTTP客户端多线程去下载文件,就可以不遵循这个标准,并发的或者连续的多次GET请求,都分开在多个TCP通道中,每一条TCP通道,只有一次GET,GET完之后,立即有TCP关闭的四次握手,这样写代码更简单,这时候虽然HTTP头有Connection: Keep-alive,但不能说是长连接。正常情况下客户端浏览器、web服务端都有实现这个标准,因为它们的文件又小又多,保持长连接减少重新开TCP连接的开销很有价值。
长链接的过期时间
户端的长连接不可能无限期的拿着,会有一个超时时间,服务器有时候会告诉客户端超时时间,譬如:
HTTP/1.1 200 OK
Server: ngx_openresty
Date: Tue, 08 Apr 2014 05:23:22 GMT
Content-Type: text/html; charset = utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Keep-Alive: timeout = 20
Vary: Accept-Encoding
Last-Modified: Tue, 08 Apr 2014 05:20:01 GMT
Keep-Alive: timeout=20,表示这个TCP通道可以保持20秒。另外还可能有max=XXX,表示这个长连接最多接收XXX次请求就断开。对于客户端来说,如果服务器没有告诉客户端超时时间也没关系,服务端可能主动发起四次握手断开TCP连接,客户端能够知道该TCP连接已经无效;另外TCP还有心跳包来检测当前连接是否还活着,方法很多,避免浪费资源。
长连接的数据传输完成识别
使用长连接之后,客户端、服务端怎么知道本次传输结束呢?两部分:1是判断传输数据是否达到了Content-Length指示的大小;2动态生成的文件没有Content-Length,它是分块传输(chunked),这时候就要根据chunked编码来判断,chunked编码的数据在最后有一个空chunked块,表明本次传输数据结束。
HTTP keep-alive和TCP keep-alive
TCP Keepalive的起源
TCP协议中有长连接和短连接之分。短连接环境下,数据交互完毕后,主动释放连接;
长连接的环境下,进行一次数据交互后,很长一段时间内无数据交互时,客户端可能意外断电、死机、崩溃、重启,还是中间路由网络无故断开,这些TCP连接并未来得及正常释放,那么,连接的另一方并不知道对端的情况,它会一直维护这个连接,长时间的积累会导致非常多的半打开连接,造成端系统资源的消耗和浪费,且有可能导致在一个无效的数据链路层面发送业务数据,结果就是发送失败。所以服务器端要做到快速感知失败,减少无效链接操作,这就有了TCP的Keepalive(保活探测)机制。
TCP Keepalive工作原理
当一个 TCP 连接建立之后,启用 TCP Keepalive 的一端便会启动一个计时器,当这个计时器数值到达 0 之后(也就是经过tcp_keep-alive_time时间后,这个参数之后会讲到),一个 TCP 探测包便会被发出。这个 TCP 探测包是一个纯 ACK 包(规范建议,不应该包含任何数据,但也可以包含1个无意义的字节,比如0x0。),其 Seq号 与上一个包是重复的,所以其实探测保活报文不在窗口控制范围内。
如果一个给定的连接在两小时内(默认时长)没有任何的动作,则服务器就向客户发一个探测报文段,客户主机必须处于以下4个状态之一:
-
客户主机依然正常运行,并从服务器可达。客户的TCP响应正常,而服务器也知道对方是正常的,服务器在两小时后将保活定时器复位。
-
客户主机已经崩溃,并且关闭或者正在重新启动。在任何一种情况下,客户的TCP都没有响应。服务端将不能收到对探测的响应,并在75秒后超时。服务器总共发送10个这样的探测 ,每个间隔75秒。如果服务器没有收到一个响应,它就认为客户主机已经关闭并终止连接。
-
客户主机崩溃并已经重新启动。服务器将收到一个对其保活探测的响应,这个响应是一个复位,使得服务器终止这个连接。
-
客户机正常运行,但是服务器不可达,这种情况与2类似,TCP能发现的就是没有收到探测的响应。
对于linux内核来说,应用程序若想使用TCP Keepalive,需要设置SO_KEEPALIVE套接字选项才能生效。
有三个重要的参数:
-
tcp_keepalive_time,在TCP保活打开的情况下,最后一次数据交换到TCP发送第一个保活探测包的间隔,即允许的持续空闲时长,或者说每次正常发送心跳的周期,默认值为7200s(2h)。
-
tcp_keepalive_probes 在tcp_keepalive_time之后,没有接收到对方确认,继续发送保活探测包次数,默认值为9(次)
-
tcp_keepalive_intvl,在tcp_keepalive_time之后,没有接收到对方确认,继续发送保活探测包的发送频率,默认值为75s。
其他编程语言有相应的设置方法,这里只谈linux内核参数的配置。例如C语言中的setsockopt()函数,java的Netty服务器框架中也提供了相关接口。
TCP Keepalive作用
-
探测连接的对端是否存活
在应用交互的过程中,可能存在以下几种情况:- 客户端或服务器意外断电,死机,崩溃,重启。
- 中间网络已经中断,而客户端与服务器并不知道。
利用保活探测功能,可以探知这种对端的意外情况,从而保证在意外发生时,可以释放半打开的TCP连接。
-
防止中间设备因超时删除连接相关的连接表
中间设备如防火墙等,会为经过它的数据报文建立相关的连接信息表,并为其设置一个超时时间的定时器,如果超出预定时间,某连接无任何报文交互的话,
中间设备会将该连接信息从表中删除,在删除后,再有应用报文过来时,中间设备将丢弃该报文,从而导致应用出现异常。
TCP Keepalive可能导致的问题
Keepalive 技术只是 TCP 技术中的一个可选项。因为不当的配置可能会引起一些问题,所以默认是关闭的。
可能导致下列问题:
- 在短暂的故障期间,Keepalive设置不合理时可能会因为短暂的网络波动而断开健康的TCP连接
- 需要消耗额外的宽带和流量
- 在以流量计费的互联网环境中增加了费用开销
ICMP协议的两个应用:ping和traceroute
- Ping
Ping 是 ICMP 的一个重要应用,主要用来测试两台主机之间的连通性。
Ping 的原理是通过向目的主机发送 ICMP Echo 请求报文,目的主机收到之后会发送 Echo 回答报文。Ping 会根据时 间和成功响应的次数估算出数据包往返时间以及丢包率。 - Traceroute
Traceroute 是 ICMP 的另一个应用,用来跟踪一个分组从源点到终点的路径。
Traceroute 发送的 IP 数据报封装的是无法交付的 UDP 用户数据报,并由目的主机发送终点不可达差错报告报文。- 源主机向目的主机发送一连串的 IP 数据报。第一个数据报 P1 的生存时间 TTL 设置为 1,当 P1 到达路径上的第 一个路由器 R1 时,R1 收下它并把 TTL 减 1,此时 TTL 等于 0,R1 就把 P1 丢弃,并向源主机发送一个 ICMP 时间超过差错报告报文;
- 源主机接着发送第二个数据报 P2,并把 TTL 设置为 2。P2 先到达 R1,R1 收下后把 TTL 减 1 再转发给 R2,R2 收下后也把 TTL 减 1,由于此时 TTL 等于 0,R2 就丢弃 P2,并向源主机发送一个 ICMP 时间超过差错报文。
- 不断执行这样的步骤,直到最后一个数据报刚刚到达目的主机,主机不转发数据报,也不把 TTL 值减 1。但是 因为数据报封装的是无法交付的 UDP,因此目的主机要向源主机发送 ICMP 终点不可达差错报告报文。
- 之后源主机知道了到达目的主机所经过的路由器 IP 地址以及到达每个路由器的往返时间。
ping IP之后发生了什么?
机器A ping 机器B
同一网段
- ping通知系统建立一个固定格式的ICMP请求数据包
- ICMP协议打包这个数据包和机器B的IP地址转交给IP协议层(一组后台运行的进程,与ICMP类似)
- IP层协议将以机器B的IP地址为目的地址,本机IP地址为源地址,加上一些其他的控制信息,构建一个IP数据包
- 获取机器B的MAC地址
- IP层协议通过机器B的IP地址和自己的子网掩码,发现它跟自己属同一网络,就直接在本网络查找这台机器的MAC
- 若两台机器之前有过通信,在机器A的ARP缓存表应该有B机IP与其MAC的映射关系
- 若没有,则发送ARP请求广播,得到机器B的MAC地址,一并交给数据链路层
- 数据链路层构建一个数据帧,目的地址是IP层传过来的MAC地址,源地址是本机的MAC地址,再附加一些控制信息,依据以太网的介质访问规则,将他们传送出去
- 机器B收到这个数据帧后,先检查目的地址,和本机MAC地址对比
- 符合,接收。接收后检查该数据帧,将IP数据包从帧中提取出来,交给本机的IP协议层协议。IP层检查后,将有用的信息提取交给ICMP协议,后者处理后,马上构建一个ICMP应答包,发送给主机A,其过程和主机A发送ICMP请求包到主机B类似(这时候主机B已经知道了主机A的MAC地址,不需再发ARP请求)
- 不符合,丢弃
- IP层协议通过机器B的IP地址和自己的子网掩码,发现它跟自己属同一网络,就直接在本网络查找这台机器的MAC
不同网段
- ping通知系统建立一个固定格式的ICMP请求数据包
- ICMP协议打包这个数据包和机器B的IP地址转交给IP协议层(一组后台运行的进程,与ICMP类似)
- IP层协议将以机器B的IP地址为目的地址,本机IP地址为源地址,加上一些其他的控制信息,构建一个IP数据包
- 获取主机B的MAC地址
- IP协议通过计算发现主机B与自己不在同一网段内,就直接交给路由处理,就是将路由的MAC取过来,至于怎么得到路由的MAC地址,和之前一样,先在ARP缓存表中寻找,找不到可以利用广播。路由得到这个数据帧之后,再跟主机B联系,若找不到,就向主机A返回一个超时信息。
对ping后返回信息的分析
- Request timed out
- 对方已关机,或者网络上没有这个地址
- 对方与自己不在同一网段内,通过路由也无法到达
- 对方存在,不过设置了ICMP数据包过滤(比如防火墙设置)
- 错误设置IP地址
正常情况下,一台主机一张网卡,一个IP地址或多个网卡,多个IP地址。但如果一台电脑的“拨号网络适配器”(相当于一块软网卡)的TCO/IP设置中,设置了一个与网卡IP地址处于同一子网的IP地址,这样,在IP层协议看来,这台主机就有两个不同的接口处于同一网段内。当从这台主机ping其他机器时,会存在这样的问题:
- 主机不知道将数据包发到哪个网络接口,因为有两个网络接口都连接在同一网段。
- 主机不知道用哪个地址作为数据包的源地址。因此,从这台主机去ping其他机器,IP层协议会无法处理,超时后,ping就会给出一个“超时无应答”的错误信息提示。但从其他主机ping这台主机时,请求包从特定网卡来,ICMP只须简单的将目的、源地址互换,并更改一些标志即可,ICMP应答包能顺利发出,其他主机就能成功ping通这台主机了。
- Destination host Unreachable
- 自己未设定默认路由,对方跟自己不在同已网段
- 网线有问题
备注:
Request timed out 与 Destination host Unreachable 的区别
所经过的路由器的路由表具有到达目标的路由,而目标因为原因不可到达,这时出现前者。如果路由表中连到达目标的路由都没有,就会出现后者。
- Bad ip address
- 没有连接到DNS服务器,无法解析IP,也可能是IP不存在
- Source quench received
- 对方或中途服务器繁忙而无法应答
- Unkonw host
- 远程主机的名字不能被域名服务器转换成IP地址,故障原因可能是DNS服务器有故障,或者名字不正确,或者网络管理员的系统与远程主机之间的通信线路故障。
- No answer
- 无响应。说明本地系统有一条通向中心主机的路由,但却接收不到它发给该中心主机的人呢和信息。故障原因可能是:中心主机没有工作;本地或中心主机网络配置不正确;本地或中心的路由器没有工作;通信线路有故障;中心主机存在路由选择问题。
- Ping 127.0.0.1
- 如果ping不通,则表明本地址TCP/IP协议不能正常工作
- no rout to host
- 网卡工作不正常
- transmit failed。error code
- 10043网卡驱动不正常
- unknown host name
- DNS配置不正确
综上所述,当一个站点不能ping通,可能具有如下因素:
- dns系统配置错误
- iptables过滤了
- 对方站点不允许
- 网络节点异常了
两台电脑组网,ping不通对方的问题总结
问题:在一局域网里有两台PC,用IP地址互相ping不通,可能原因有哪些,尽可能多的列出。
参考答案:
- 物理层面:网线是否接好,交换机/路由器等设备是否正常。
- 故障方面:ping命令/服务是否被禁止,双方的IP地址是否正确。
- 配置方面:网络配置是否正确,IP/网关/子网掩码等。防火墙设置是否允许。
在同一网段下的两台电脑,正确连接之后无需配置即可ping通对方。如果ping不同,通常为防火墙软件或者电脑管家等安防软件等禁止了。
如果是不同网段下的两台电脑,B可以ping通A但是A无法ping通B的原因。这是由于B的IP经过路由器网络地址转换之后,对外显示的是路由器IP,这就导致A实际上无法寻得B的IP,即不知道B的存在。解决此问题的办法就是在A的网络配置中添加静态路由。
命令为: route -p add 目的地址 mask 子网掩码
Socket之TCP粘包问题
粘包问题分析与对策
TCP粘包是指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。
出现粘包现象的原因是多方面的,它既可能由发送方造成,也可能由接收方造成。
什么时候需要考虑粘包问题
-
如果利用tcp每次发送数据,就与对方建立连接,然后双方发送完一段数据后,就关闭连接,这样就不会出现粘包问题(因为只有一种包结构,类似于http协议)。
关闭连接主要是要双方都发送close连接(参考tcp关闭协议)。如:A需要发送一段字符串给B,那么A与B建立连接,然后发送双方都默认好的协议字符如"hello give me sth abour yourself",然后B收到报文后,就将缓冲区数据接收,然后关闭连接,这样粘包问题不用考虑到,因为大家都知道是发送一段字符。 -
如果发送数据无结构,如文件传输,这样发送方只管发送,接收方只管接收存储就ok,也不用考虑粘包3如果双方建立连接,需要在连接后一段时间内发送不同结构数据,如连接后,有好几种结构:
1)"hellogive me sth abour yourself"
2)"Don'tgive me sth abour yourself"
那这样的话,如果发送方连续发送这个两个包出去,接收方一次接收可能会是"hellogive me sth abour yourselfDon't give me sth abour yourself"这样接收方就傻了,到底是要干嘛?不知道,因为协议没有规定这么诡异的字符串,所以要处理把它分包,怎么分也需要双方组织一个比较好的包结构,所以一般可能会在头加一个数据长度之类的包,以确保接收。
粘包出现原因
简单得说,在流传输中出现,UDP不会出现粘包,因为它有消息边界(参考Windows网络编程)
- 发送端需要等缓冲区满才发送出去,造成粘包
- 接收方不及时接收缓冲区的包,造成多个包接收
具体点:
- 发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一包数据。若连续几次发送的数据都很少,通常TCP会根据优化算法把这些数据合成一包后一次发送出去,这样接收方就收到了粘包数据。
- 接收方引起的粘包是由于接收方用户进程不及时接收数据,从而导致粘包现象。这是因为接收方先把收到的数据放在系统接收缓冲区,用户进程从该缓冲区取数据,若下一包数据到达时前一包数据尚未被用户进程取走,则下一包数据放到系统接收缓冲区时就接到前一包数据之后,而用户进程根据预先设定的缓冲区大小从系统接收缓冲区取数据,这样就一次取到了多包数据。
粘包情况有两种,一种是粘在一起的包都是完整的数据包,另一种情况是粘在一起的包有不完整的包。
不是所有的粘包现象都需要处理,若传输的数据为不带结构的连续流数据(如文件传输),则不必把粘连的包分开(简称分包)。但在实际工程应用中,传输的数据一般为带结构的数据,这时就需要做分包处理。
在处理定长结构数据的粘包问题时,分包算法比较简单;在处理不定长结构数据的粘包问题时,分包算法就比较复杂。特别是粘在一起的包有不完整的包的粘包情况,由于一包数据内容被分在了两个连续的接收包中,处理起来难度较大。实际工程应用中应尽量避免出现粘包现象。
为了避免粘包现象,可采取以下几种措施:
-
对于发送方引起的粘包现象,用户可通过编程设置来避免,TCP提供了强制数据立即传送的操作指令push,TCP软件收到该操作指令后,就立即将本段数据发送出去,而不必等待发送缓冲区满;
-
对于接收方引起的粘包,则可通过优化程序设计、精简接收进程工作量、提高接收进程优先级等措施,使其及时接收数据,从而尽量避免出现粘包现象;
-
由接收方控制,将一包数据按结构字段,人为控制分多次接收,然后合并,通过这种手段来避免粘包。
以上提到的三种措施,都有其不足之处。
- 第一种编程设置方法虽然可以避免发送方引起的粘包,但它关闭了优化算法,降低了网络发送效率,影响应用程序的性能,一般不建议使用。
- 第二种方法只能减少出现粘包的可能性,但并不能完全避免粘包,当发送频率较高时,或由于网络突发可能使某个时间段数据包到达接收方较快,接收方还是有可能来不及接收,从而导致粘包。
- 第三种方法虽然避免了粘包,但应用程序的效率较低,对实时应用的场合不适合。
一种比较周全的对策是:接收方创建一预处理线程,对接收到的数据包进行预处理,将粘连的包分开。对这种方法我们进行了实验,证明是高效可行的。
TCP无保护消息边界的解决
- 发送固定长度的消息
- 把消息的尺寸与消息一块发送
- 使用特殊标记来区分消息间隔
Socket之TCP封包和拆包
为什么基于TCP的通讯程序需要进行封包和拆包
TCP是个"流"协议,所谓流,就是没有界限的一串数据。
大家可以想想河里的流水,是连成一片的,其间是没有分界线的。但一般通讯程序开发是需要定义一个个相互独立的数据包的,比如用于登陆的数据包,用于注销的数据包。
由于TCP"流"的特性以及网络状况,在进行数据传输时会出现以下几种情况:
假设我们连续调用两次 send 分别发送两段数据 data1 和 data2,在接收端有以下几种接收情况(当然不止这几种情况,这里只列出了有代表性的情况):
A.先接收到 data1,然后接收到 data2。
B.先接收到 data1 的部分数据,然后接收到 data1 余下的部分以及 data2 的全部。
C.先接收到了data1 的全部数据和 data2 的部分数据,然后接收到了 data2 的余下的数据。
D.一次性接收到了data1和data2的全部数据。
对于A这种情况正是我们需要的,在此不再做讨论。
对于B,C,D的情况就是大家经常说的"粘包",就需要我们把接收到的数据进行拆包,拆成一个个独立的数据包。为了拆包就必须在发送端进行封包。
另外要注意的是:对于UDP来说就不存在拆包的问题。因为UDP是个"数据包"协议,也就是两段数据间是有界限的,在接收端要么接收不到数据要么就是接收一个完整的一段数据,不会少接收也不会多接收。
为什么会出现 B,C,D 的情况?
"粘包"可发生在发送端也可发生在接收端。
1、由Nagle算法造成的发送端的粘包:
Nagle算法是一种改善网络传输效率的算法。简单的说,当我们提交一段数据给TCP发送时,TCP并不立刻发送此段数据,而是等待一小段时间,看看在等待期间是否还有要发送的数据,若有则会一次把这两段数据发送出去。这是对Nagle算法一个简单的解释,详细的请看相关书籍。像
C 和 D 的情况就有可能是 Nagle 算法造成的。
2、接收端接收不及时造成的接收端粘包:
TCP会把接收到的数据存在自己的缓冲区中,然后通知应用层取数据。当应用层由于某些原因不能及时的把TCP的数据取出来,就会造成TCP缓冲区中存放了几段数据。
怎样封包和拆包?
封包:
封包就是给一段数据加上包头,这样一来数据包就分为包头和包体两部分内容了(以后讲过滤非法包时封包会加入"包尾"内容)。
包头其实上是个大小固定的结构体,其中有个结构体成员变量表示包体的长度,这是个很重要的变量,其他的结构体成员可根据需要自己定义。根据包头长度固定以及包头中含有包体长度的变量就能正确的拆分出一个完整的数据包。
拆包:
对于拆包目前我最常用的是以下两种方式:
- 动态缓冲区暂存方式。之所以说缓冲区是动态的是因为当需要缓冲的数据长度超出缓冲区的长度时会增大缓冲区长度。
大概过程描述如下:- 为每一个连接动态分配一个缓冲区,同时把此缓冲区和 SOCKET 关联,常用的是通过结构体关联。
- 当接收到数据时首先把此段数据存放在缓冲区中。
- 判断缓存区中的数据长度是否够一个包头的长度,如不够,则不进行拆包操作。
- 根据包头数据解析出里面代表包体长度的变量。
- 判断缓存区中除包头外的数据长度是否够一个包体的长度,如不够,则不进行拆包操作。
- 取出整个数据包,这里的"取"的意思是不光从缓冲区中拷贝出数据包,而且要把此数据包从缓存区中删除掉。删除的办法就是把此包后面的数据移动到缓冲区的起始地址。
这种方法有两个缺点:
- 为每个连接动态分配一个缓冲区增大了内存的使用;
- 有三个地方需要拷贝数据,一个地方是把数据存放在缓冲区,一个地方是把完整的数据包从缓冲区取出来,一个地方是把数据包从缓冲区中删除。
- 利用底层的缓冲区来进行拆包
由于TCP也维护了一个缓冲区,所以我们完全可以利用TCP的缓冲区来缓存我们的数据,这样一来就不需要为每一个连接分配一个缓冲区了。另一方面我们知道 recv 或者 wsarecv 都有一个参数,用来表示我们要接收多长长度的数据。利用这两个条件我们就可以对第一种方法进行优化了。
对于阻塞 SOCKET 来说,我们可以利用一个循环来接收包头长度的数据,然后解析出代表包体长度的那个变量,再用一个循环来接收包体长度的数据。
Socket之TCP半连接和全连接以及半连接攻击
TCP的全连接和半连接队列
当服务端调用listen函数监听端口的时候,内核会为每个监听的socket创建两个队列:
- 半连接队列(syn queue):客户端发送SYN包,服务端收到后回复SYN+ACK后,服务端进入SYN_RCVD状态,这个时候的socket会放到半连接队列。
- 全连接队列(accept queue):当服务端收到客户端的ACK后,socket会从半连接队列移出到全连接队列。当调用accpet函数的时候,会从全连接队列的头部返回可用socket给用户进程。
半连接队列
半连接队列的大小由/proc/sys/net/ipv4/tcp_max_syn_backlog
控制,Linux的默认是1024。
当服务端发送SYN_ACK后将会开启一个定时器,如果超时没有收到客户端的ACK,将会重发SYN_ACK包。重传的次数由/proc/sys/net/ipv4/tcp_synack_retries
控制,默认是5次。
全连接队列
全连接队列的大小通过/proc/sys/net/core/somaxconn
指定,在使用listen
函数时,内核会根据传入的backlog
参数与系统参数somaxconn
,取二者的较小值。
listen函数:
int listen(int sockfd, int backlog)
Nginx和Redis默认的backlog值等于511,Linux默认的backlog 为 128,Java默认的backlog等于50
默认情况下,全连接队列满以后,服务端会忽略客户端的 ACK,随后会重传SYN+ACK,也可以修改这种行为,这个值由/proc/sys/net/ipv4/tcp_abort_on_overflow决定。
- tcp_abort_on_overflow为0表示三次握手最后一步全连接队列满以后服务端会丢掉客户端发过来的ACK,服务端随后会进行重传SYN+ACK。
- tcp_abort_on_overflow为1表示全连接队列满以后服务端发送RST给客户端,直接释放资源。
syn_flood攻击
命令查看
netstat -s
netstat -s | egrep "listen|LISTEN"
// 全连接队列溢出次数
667399 times the listen queue of a socket overflowed
// 半连接队列溢出次数
667399 SYNs to LISTEN sockets dropped
ss -lnt
[root@mcs opt]# ss -lnt
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 100 *:8080 *:*
在listen状态下,Send-Q表示全连接队列大小的最大值,Recv-Q表示全连接队列的使用大小,超过最大值则会溢出。
Socket之I/O模型
一个输入操作通常包括两个阶段:
- 等待数据准备好
- 从内核向进程复制数据
对于一个套接字上的输入操作,第一步通常涉及等待数据从网络中到达。当所等待数据到达时,它被复制到内核中的 某个缓冲区。第二步就是把数据从内核缓冲区复制到应用进程缓冲区。
Unix 有五种 I/O 模型:
- 阻塞式 I/O
应用进程被阻塞,直到数据从内核缓冲区复制到应用进程缓冲区中才返回。
应该注意到,在阻塞的过程中,其它应用进程还可以执行,因此阻塞不意味着整个操作系统都被阻塞。因为其它应用 进程还可以执行,所以不消耗 CPU 时间,这种模型的 CPU 利用率会比较高。
- 非阻塞式 I/O
应用进程执行系统调用之后,内核返回一个错误码。应用进程可以继续执行,但是需要不断的执行系统调用来获知
I/O 是否完成,这种方式称为轮询(polling)。
由于 CPU 要处理更多的系统调用,因此这种模型的 CPU 利用率比较低。
- I/O复用(select 和 poll)
使用 select 或者 poll 等待数据,并且可以等待多个套接字中的任何一个变为可读。这一过程会被阻塞,当某一个套 接字可读时返回,之后再使用 recvfrom 把数据从内核复制到进程中。
它可以让单个进程具有处理多个 I/O 事件的能力。又被称为 Event Driven I/O,即事件驱动 I/O。
如果一个 Web 服务器没有 I/O 复用,那么每一个 Socket 连接都需要创建一个线程去处理。如果同时有几万个连接, 那么就需要创建相同数量的线程。相比于多进程和多线程技术,I/O 复用不需要进程线程创建和切换的开销,系统开 销更小。
- 信号驱动式 I/O(SIGIO)
应用进程使用 sigaction 系统调用,内核立即返回,应用进程可以继续执行,也就是说等待数据阶段应用进程是非阻 塞的。内核在数据到达时向应用进程发送 SIGIO 信号,应用进程收到之后在信号处理程序中调用 recvfrom 将数据从 内核复制到应用进程中。
相比于非阻塞式 I/O 的轮询方式,信号驱动 I/O 的 CPU 利用率更高。
- 异步 I/O(AIO)
应用进程执行 aio_read 系统调用会立即返回,应用进程可以继续执行,不会被阻塞,内核会在所有操作完成之后向应用进程发送信号。
异步 I/O 与信号驱动 I/O 的区别在于,异步 I/O 的信号是通知应用进程 I/O 完成,而信号驱动 I/O 的信号是通知应用进程可以开始 I/O。
- 同步 I/O: 将数据从内核缓冲区复制到应用进程缓冲区的阶段(第二阶段),应用进程会阻塞。
- 异步 I/O: 第二阶段应用进程不会阻塞。
同步 I/O 包括阻塞式 I/O、非阻塞式 I/O、I/O 复用和信号驱动 I/O ,它们的主要区别在第一个阶段。
非阻塞式 I/O 、信号驱动 I/O 和异步 I/O 在第一阶段不会阻塞。