计算机网络

关于 TCP 并发链接的几个思考题与试验css

TCP

源端口号与目的端口号: 源端口号与目的端口号, 加上IP首部的源IP地址和目的IP地址惟一肯定一个TCP链接
序号: 一次TCP通讯(从TCP链接创建到断开)过程当中某一个传输方向上的字节流编号
确认号: 仅当ACK标志位1时有效. 表示指望下一个字节的序号
头部长度: 标识TCP头部有多少个32bit(4字节). 由于4位最大能表示15, 因此TCP头部长度是60字节
保留位: 6位, 必须全为0html

6个标志位:node

  1. URG: 紧急指针是否有效
  2. ACK: 确认序号是否有效
  3. PSH: 接收方应尽快将这个报文段从TCP接收缓冲区中读走
  4. RST: 表示要求对方中心创建链接. 称携带RST标志的TCP报文为复位报文段
  5. SYN: 请求创建链接. 称携带SYN标志的TCP报文为同步报文段
  6. FIN: 表示通知对方本端要关闭链接. 称携带FIN标志的TCP报文为结束报文段

16位窗口大小: 经过窗口大小来达到流量控制git

检验和: 由发送端填充, 接收端对TCP报文执行CRC算法以检验TCP报文在传输过程当中是否损坏.github

TCP特色:web

  1. 基于字节流 --> 边界问题, 粘包问题
  2. 面向链接
  3. 可靠传输
  4. 缓冲传输
  5. 全双工
  6. 流量控制 --> 窗口机制

选项与填充(选项为4字节整数倍, 不然用0填充)
最多见的可选字段是最长报文大小MSS(Maximum Segment Size), 每一个链接方一般都在通讯的第一个报文段中指明这个选项. 它指明本端所能接受的最大长度的报文. 若是该选项不设置, 默认为536(20+20+536=576字节的IP数据报)面试

状态流转

若是两边同时断链接, 那就会就进入到 CLOSING状态, 而后到达TIME WAIT状态算法

CLOSED: 表示初始状态
LISTEN: 表示服务器端的某个socket处于监昕状态, 能够接受链接
SYN_SENT: 在服务端监昕后, 客户端socket执行CONNECT链接时, 客户端发送SYN报文, 此时客户端就进入SYN_SENT状态, 等待服务端的确认
SYN_RCVD: 表示服务端接收到了SYN报文, 在正常状况下, 这个状态是服务器端的socket在创建TCP链接时的3次握手会话过程当中的一个中间状态, 很短暂
ESTABLISHED: 表示链接已经创建了
FIN_WAIT_1: 这个是已经创建链接以后, 其中一方请求终止链接, 等待对方的FIN报文. FIN_WAIT_l状态是当socket在ESTABLISHED状态时, 它想主动关闭链接, 向对方发送了FIN报文, 此时该socket即进入到FIN_WAIT_l_状态. 而当对方回应ACK报文后, 则进入到FIN_WAIT_2状态, 在实际的正常状况下, 不管对方处于何种状况, 都应该立刻回应ACK报文因此FIN_WAIT_l状态通常是比较难见到
FIN_WAIT_2: 实际上FIN_WAIT_2状态下的socket, 表示半链接, 即有一方要求关闭链接, 但另外还告诉对方: 我暂时有数据须要传送给你, 请稍后再关闭链接
TIME_WAIT: 表示收到了对方的FIN报文, 并发送出了ACK报文, 就等2MSL. 后便可回到CLOSED可用状态了. 若是在FIN_WAIT_l状态下收到了对方同时带FIN标志和ACK标志的报文时, 能够直接进入到TIME_WAIT状态, 而无需通过FIN_WAIT_2状态
CLOSING: 这种状态比较特殊实际状况中应该是不多见属于一种比较罕见的例外状态. 正常状况下, 当发送FIN报文后, 按理来讲是应该先收到(或同时收到)对方的ACK报文, 再收到对方的FIN报文. 可是 CLOSING 状态表示你发送FIN报文后, 并无收到对方的ACK报文, 反而收到了对方的FIN报文. 为何会出现此种状况呢?其实细想一下, 也不可贵出结论:那就是若是双方几乎在同时关闭一个 socket 的话, 那么就出现了双方同时发送 FIN 报文的状况, 就会出现 CLOSING 状态, 表示双方都正在关闭 socket 链接. 
CLOSE_WAIT: 这种状态的含义实际上是表示在等待关闭. 怎么理解呢?当对方关闭一个socket后发送FIN报文给本身时, 系统将毫无疑问地会回应一个ACK报文给对方, 此时则进入到CLOSE_WAIT状态.  接下来呢, 实际上你真正须要考虑的事情是察看你是否还有数据发送给对方, 若是没有, 那么你也就能够关闭这个 socket 了, 发送 FIN 报文给对方, 即关闭链接.  在 CLOSE WAIT 状态下, 须要完成的事情是等待你去关闭链接. 
LAST_ACK: 这个状态仍是比较好理解的, 它是被动关闭一方在发送FIN报文后, 最后等待对方的ACK报文 . 
CLOSED: 当收到ACK报文后, 也便可以进入到 CLOSED 可用状态了

FIN_WAIT_2的设定时间

当TCP主动关闭一端调用了close()来执行链接的彻底关闭时会执行如下流程, 本端发送FIN给对端, 对端回复ACK, 本端进入FIN_WAIT_2状态, 此时只有对端发送了FIN, 本端才会进入TIME_WAIT状态, 为了防止对端不发送关闭链接的FIN包给本端, 将会在进入FIN_WAIT_2状态时, 设置一个FIN_WAIT_2定时器, 若是该链接超过必定时限, 则进入CLOSE状态;chrome

注意:上述是针对close调用彻底关闭链接的状况, shutdown执行半关闭不会启动FIN_WAIT_2定时器;数据库

TIME_WAIT

从上图可知, 客户端链接在收到服务器的结束报文以后, 并无直接进入CLOSED状态, 而是转义到TIME_WAIT状态. 在这个状态, 客户端链接要等待一段长为2MSL(Maximum Segment Life, 报文段最大生存时期)的时间, 才能彻底关闭. MSL是TCP报文段在网络中的最大生存时期, 标准文档建议是2min

TIME_WAIT状态的存在缘由有两点:

  1. 可靠地终止TCP链接
  2. 保证让迟来的TCP报文段有足够的时间识别并丢弃

第一个缘由很好理解. 假设图中用于确认服务器接收报文段6的TCP报文段7丢失, 那么服务器将从新发结束报文段(报文段6). 所以客户端须要停留在某个状态以处理重复收到的结束报文(即向服务器发送确认报文段). 不然, 客户端将以复位报文段回应服务器, 服务器认为这是一个错误, 由于它指望的是一个想TCP报文段7那样的确认报文段.

在Linux系统上, 一个TCP端口不能同时被打开屡次(两次及以上). 当一个TCP链接处于TIME_WAIT状态时, 将没法当即使用该链接占用着的端口来创建一个新链接. 反过来思考, 若是TIME_WAIT状态不存在, 则应用程序可以当即创建一个和刚关闭的链接类似的链接(这里说类似, 是指他们具备相同的IP和端口号). 这个新的和原来类似的链接被称为原来的链接的化身. 新的化身可能接受属于原来的链接, 携带应用程序数据的TCP报文段(迟到的报文段), 这显然是不该该发生的. 这就是TIME_WAIT状态存在的第二个缘由

另外, 由于TCP报文段的最大生存时间是MSL, 因此坚持2MSL时间的TIME_WAIT状态可以确保网络上两个传输方向上还没有被接收到的, 迟到的TCP报文段都已消失(被中转路由丢弃). 所以一个链接的新的化身能够在2MSL时间以后安全地创建, 而绝对不会接受到属于原来链接的应用程序数据, 这个是TIME_WAIT状态要持续2MSL时间的缘由

有时但愿避免TIME_WAIT状态, 由于当程序退出后, 但愿可以当即重启它. 但因为出在TIME_WAIT状态的链接还占用着端口, 程序没法启动(直到2MSL超时时间结束). 对客户端程序来讲一般不须要担忧重启问题. 由于客户端通常使用系统自动分配的临时端口号来创建链接, 而因为随机性, 临时端口号通常和抽象上一次使用的端口号(还处于TIME_WAIT状态的那个链接使用的端口号)不一样, 因此客户端程序通常能够当即重启

可是若是是服务器主动关闭链接后异常终止, 则由于它老是使用同一个知名服务端口号, 因此链接的TIME_WAIT状态将致使它不能当即重启. 不过, 能够经过socket选项SO_REUSERADDR来强制进程当即使用处于TIME_WAIT状态的链接占用的端口

半关闭状态

TCP链接时全双工的, 因此它容许两个方向的数据传输被独立关闭. 换而言之, 通讯的一端能够发送结束报文段给对方, 告诉他本端已经完成了数据的发送, 但容许继续接收来自对方的数据, 直到对方也发送结束报文段以关闭链接. TCP链接的这种状态称为半关闭状态

图中服务器和客户端应用程序判断对方是否已经关闭链接的方法是: read系统调用返回0(收到结束报文段). Linux还提供其余检验链接是否被对方关闭的方法

socket网络编程接口经过shutdown函数提供了对半关闭的支持. 这里强调一下, 虽然介绍了半关闭状态, 可是使用半关闭的应用程序不多见

报文复位段

在某些特殊条件下, TCP链接的一端会向另外一端发送携带RST标志的报文段, 即复位报文段, 以通知对方关闭链接或从新创建链接
由于复位报文的接收通告窗口大小为0, 因此能够碰见: 收到复位报文段的一端应该关闭该链接或者从新链接, 而不能回应这个报文段

  1. 当客户端程序访问一个不存在的端口时, 目标主机将给它发送一个复位报文段.
  2. 当客户端程序向服务器端的否个端口发起链接, 而该端口仍被处于TIME_WAIT状态的链接所占用时, 客户端程序也将收到复位报文段
  3. 异常终止链接. 异常终止一个链接, 即给对方一个复位报文段. 一旦发送了复位报文段, 发送端全部排队等待发送的数据都将被丢弃, 应用程序能够使用socket选项SO_LINGER来发送复位报文段, 以异常终止一个链接
  4. 处理半打开链接
      考虑下面的状况: 服务器(或客户端)关闭或者异常终止了链接, 而对方没有接收到结束报文段(好比发生了网络故障), 此时客户端(或服务器)还维持着原来的链接, 而服务器(或客户端)即便重启, 也已经没有该链接的任何消息了. 将这种状态称为半打开状态, 处于这种状态的链接称为半打开链接. 若是客户端(或服务器)往处于半打开状态的链接写入数据, 对方将回应一个复位报文段

滑动窗口

TCP的滑动窗口主要有两个做用: 一是提供TCP的可靠性; 二是提供TCP的流控特性. 同时滑动窗口机制还体现了 TCP 面向字节流的设计思路.

对于TCP会话的发送方, 任什么时候候在其发送缓存内的数据均可以分为4类:

  1. 已经发送并获得对端 ACK
  2. 已经发送但还未收到对端 ACK
  3. 未发送但对端容许发送
  4. 未发送且对端不容许发送
    其中“已经发送但还未收到对端ACK"和"未发送但对端容许发送的"这两部分数据称之为发送窗口

对于TCP的接收方, 在某一时刻在它的接收缓存内存在3种状态:

  1. 已接收;
  2. 未接收准备接收
  3. 未接收并未准备接收.(因为ACK直接由TCP协议找回复, 默认无应用延迟, 不存在“已接收未回复ACK")
    其中“未接收准备接收”称之为接收窗口

TCP 是双工的协议, 会话的双方均可以同时接收、发送数据. TCP会话的双方都各自维护一个“发送窗口”和一个“接收窗口”. 其中各自的“接收窗口”大小取决于应用、系统、硬件的限制(TCP 传输速率不能大于应用的数据处理速率). 各自的“发送窗口”则要求取决于对端通告的“接收窗口”, 要求相同

滑动窗口实现面向流的可靠性来源于“确认重传”机制. TCP 的滑动窗口的可靠性也是创建在“确认重传”基础上的. 发送窗口只有收到对端对于本段发送窗口内字节的ACK确认, 才会移动发送窗口的左边界. 接收窗口只有在前面全部的段都确认的状况下才会移动左边; 在前面还有字节未接收但收到后面字节的状况下, 窗口不会移动, 并不对后续字节确认. 以此确保对端会对这些数据重传

滑动窗口协议: 用于流量控制, 便可用于应用层也能够用于传输层, 前者以帧为单位进行确认, 后者以字节为单位进行确认. 发送端发送的数据不能超过对方接收窗口的缓冲区大小, 若是超过接收窗口大小就会致使数据的丢失

为什须要三次握手?

《计算机网络》第四版中讲“三次握手”的目的是“为了防止已失效的链接请求报文段忽然又传送到了服务端, 于是产生错误”, 书中的例子是这样的, “已失效的链接请求报文段”的产生在这样一种状况下:client发出的第一个链接请求报文段并无丢失, 而是在某个网络结点长时间的滞留了, 以至延误到链接释放之后的某个时间才到达server. 原本这是一个早已失效的报文段. 但server收到此失效的链接请求报文段后, 就误认为是client再次发出的一个新的链接请求. 因而就向client发出确认报文段, 赞成创建链接. 假设不采用“三次握手”, 那么只要server发出确认, 新的链接就创建了. 因为如今client并无发出创建链接的请求, 所以不会理睬server的确认, 也不会向server发送数据. 但server却觉得新的运输链接已经创建, 并一直等待client发来数据. 这样, server的不少资源就白白浪费掉了. 采用“三次握手”的办法能够防止上述现象发生. 例如刚才那种状况, client不会向server的确认发出确认. server因为收不到确认, 就知道client并无要求创建链接. ”. 主要目的防止server端一直等待, 浪费资源.

链接创建3次握手

  1. 第一次握手: 创建链接时, 客户端发送SYN包(SYN=J)到服务器端, 并进入SYN_SEND状态, 等待服务器确认
  2. 第二次握手: 服务器收到SYN包, 必须确认客户的SYN(ACK=J+1), 同时本身也发送一个SYN包(SYN=K), 即SYN+ACK包, 此时服务器进入SYN_RECV状态
  3. 第三次握手: 客户端收到服务器的SYN+ACK包, 向服务器发送确认包ACK(ACK=K+1), 此包发送完毕, 客户端和服务器端进入ESTABLISHED状态, 完成3次握手

链接断开4次挥手

为何建链接要3次握手, 断链接须要4次挥手?

  1. 对于建链接的3次握手, 主要是要初始化Sequence Number的初始值. 通讯的双方要互相通知对方本身的初始化的Sequence Number, 也就上图中的J和K. 这个号要做为之后的数据通讯的序号, 以保证应用层接收到的数据不会由于网络上的传输问题而乱序(TCP 会用这个序号来拼接数据)
  2. 对于4次挥手, 其实仔细看则是两次, 由于TCP是全双工的, 因此, 发送方和接收方都须要FIN和ACK. 只不过, 有一方是被动的, 因此看上去就成了所谓的4次挥手.

解释RTO,RTT和超时重传?

- 超时重传:发送端发送报文后若长时间未收到确认的报文则须要重发该报文。可能有如下几种状况:
  发送的数据没能到达接收端,因此对方没有响应。
  接收端接收到数据,可是ACK报文在返回过程当中丢失。
  接收端拒绝或丢弃数据。

  • RTO:从上一次发送数据,由于长期没有收到ACK响应,到下一次重发之间的时间。就是重传间隔。
      一般每次重传RTO是前一次重传间隔的两倍,计量单位一般是RTT。例:1RTT,2RTT,4RTT,8RTT......
      重传次数到达上限以后中止重传。

  • RTT:数据从发送到接收到对方响应之间的时间间隔,即数据报在网络中一个往返用时。大小不稳定。

如何区分流量控制和拥塞控制?

流量控制属于通讯双方协商;拥塞控制涉及通讯链路全局。
流量控制须要通讯双方各维护一个发送窗、一个接收窗,对任意一方,接收窗大小由自身决定,发送窗大小由接收方响应的TCP报文段中窗口值肯定;拥塞控制的拥塞窗口大小变化由试探性发送必定数据量数据探查网络情况后而自适应调整。
实际最终发送窗口 = min{流控发送窗口,拥塞窗口}

流量控制原理?

目的是接收方经过TCP头窗口字段告知发送方本方可接收的最大数据量,用以解决发送速率过快致使接收方不能接收的问题。因此流量控制是点对点控制。

TCP是双工协议,双方能够同时通讯,因此发送方接收方各自维护一个发送窗和接收窗。
  发送窗:用来限制发送方能够发送的数据大小,其中发送窗口的大小由接收端返回的TCP报文段中窗口字段来控制,接收方经过此字段告知发送方本身的缓冲(受系统、硬件等限制)大小。
  接收窗:用来标记能够接收的数据大小。

TCP是流数据,发送出去的数据流能够被分为如下四部分:已发送且被确认部分 | 已发送未被确认部分 | 未发送但可发送部分 | 不可发送部分,其中发送窗 = 已发送未确认部分 + 未发但可发送部分。接收到的数据流可分为:已接收 | 未接收但准备接收 | 未接收不许备接收。接收窗 = 未接收但准备接收部分。

发送窗内数据只有当接收到接收端某段发送数据的ACK响应时才移动发送窗,左边缘紧贴刚被确认的数据。接收窗也只有接收到数据且最左侧连续时才移动接收窗口。

流量控制与拥塞控制

拥塞控制
  拥塞控制一般表示的是一个全局性的过程,它会涉及到网络中全部的主机、
  全部的路由器和下降网络传输性能的全部因素
流量控制
  流量控制发生在发送端和接收端之间,只是点到点之间的控制

TCP之 流量控制(滑动窗口)和 拥塞控制(拥塞控制的工做过程)

1.什么是流量控制

防止发送方发的太快,耗尽接收方的资源,从而使接收方来不及处理

2.流量控制的一些知识点

(1)接收端抑制发送端的依据:接收端缓冲区的大小
(2)流量控制的目标是接收端,是怕接收端来不及处理
(3)流量控制的机制是丢包

3.怎么样实现流量控制?

使用滑动窗口
滑动窗口
1.滑动窗口是什么?
滑动窗口是相似于一个窗口同样的东西,是用来告诉发送端能够发送数据的大小或者说是窗口标记了接收端缓冲区的大小,这样就能够实现
ps:窗口指的是一次批量的发送多少数据
2.为何会出现滑动窗口?
在确认应答策略中,对每个发送的数据段,都要给一个ACK确认应答,收到ACK后再发送下一个数据段,这样作有一个比较大的缺点,就是性能比较差,尤为是数据往返的时间长的时候使用滑动窗口,就能够一次发送多条数据,从而就提升了性能
3.滑动窗口的一些知识点
(1)接收端将本身能够接收的缓冲区大小放入TCP首部中的“窗口大小”字段,经过ACK来通知发送端
(2)窗口大小字段越大,说明网络的吞吐率越高
(3)窗口大小指的是无需等待确认应答而能够继续发送数据的最大值,即就是说不须要接收端的应答,能够一次连续的发送数据
(4)操做系统内核为了维护滑动窗口,须要开辟发送缓冲区,来记录当前还有那些数据没有应答,只有确认应答过的数据,才能从缓冲区删掉
ps:发送缓冲区若是太大,就会有空间开销
(5)接收端一旦发现本身的缓冲区快满了,就会将窗口大小设置成一个更小的值通知给发送端,发送端收到这个值后,就会减慢本身的发送速度
(6)若是接收端发现本身的缓冲区满了,就会将窗口的大小设置为0,此时发送端将再也不发送数据,可是须要按期发送一个窗口探测数据段,使接收端把窗口大小告诉发送端
4.滑动窗口的优势
能够高效可靠的发送大量的数据

拥塞控制

拥塞控制与流量控制的区别
拥塞控制是防止过多的数据注入到网络中, 能够使网络中的路由器或链路不致过载, 是一个全局性的过程. 目的: 避免网络拥塞
流量控制是点对点通讯量的控制, 是一个端到端的问题, 主要就是抑制发送端发送数据的速率, 以便接收端来得及接收. 目的: 防止接收方缓存溢出致使分组丢失

拥塞控制四个部分: 慢启动, 拥塞避免, 快速重传, 快速恢复

  • 慢启动
    1.慢开始不是指cwnd的增加速度慢(指数增加), 而是指TCP开始发送设置cwnd=1.
    2.思路:不要一开始就发送大量的数据, 先探测一下网络的拥塞程度, 也就是说由小到大逐渐增长拥塞窗口的大小. 这里用报文段的个数的拥塞窗口大小举例说明慢开始算法, 实时拥塞窗口大小是以字节为单位的
    3.为了防止cwnd增加过大引发网络拥塞, 设置一个慢开始门限(ssthresh状态变量)
    (1)每收到一个ACK的回复报文, 窗口就加1MSS 假设每一个报文被单独确认. 发送方cwnd从1MSS开始, 发送1MSS后, 收到一个ACK. 此时cwnd=1+1=2;
    (2)而后发送方发送2MSS报文段, 会收到2个ACK, 此时cwnd=2+2=4.....依次类推拥塞窗口指数增长
    当cnwd<ssthresh, 使用慢开始算法
    当cnwd=ssthresh, 既可以使用慢开始算法, 也能够使用拥塞避免算法
    当cnwd>ssthresh, 使用拥塞避免算法

  • 拥塞避免
    1.拥塞避免并不是彻底可以避免拥塞, 是说在拥塞避免阶段将拥塞窗口控制为按线性规律增加, 使网络比较不容易出现拥塞.
    2.思路:让拥塞窗口cwnd缓慢地增大, 即每通过一个往返时间RTT就把发送方的拥塞控制窗口加1.

拥塞发生: 当发生丢包进行数据包重传时, 表示网络已经拥塞. 分两种状况进行处理:
等到RTO超时, 重传数据包: sshthresh=cwnd/2; cwnd 重置为1; 进入慢启动过程
在收到3个duplicate ACK时就开启重传, 进入快速恢复算法

  • 快重传
  1. 快重传要求接收方在收到一个失序的报文段后就当即发出重复确认(为的是使发送方及早知道有报文段没有到达对方)而不要等到本身发送数据时捎带确认. 快重传算法规定, 发送方只要一连收到三个重复确认就应当当即重传对方还没有收到的报文段, 而没必要继续等待设置的重传计时器时间到期.
  2. 因为不须要等待设置的重传计时器到期, 能尽早重传未被确认的报文段, 能提升整个网络的吞吐量
  • 快恢复
    快速重传和快速恢复算法通常同时使用。快速恢复算法是认为,你还有3个Duplicated Acks说明网络也不那么糟糕,因此没有必要像RTO超时那么强烈,并不须要从新回到慢启动进行,这样可能下降效率。因此协议栈会作以下工做
  1. cwnd = cwnd/2
  2. sshthresh = cwnd
    而后启动快速恢复算法
    1. 设置cwnd = ssthresh+ACK个数*MSS(通常状况下会是3个dup ACK)
  3. 重传丢失的数据包(对于重传丢失的那个数据包
  4. 若是只收到Dup ACK,那么cwnd = cwnd + 1,而且在容许的条件下发送一个报文段, 若此后每一次接收到Dup ACK cwnd都会+1, 直至接收到新的ACK
  5. 若是收到新的ACK, 设置cwnd = ssthresh,进入拥塞避免阶段
    快恢复增加过程

发送窗口增加到必定范围时, 可能出现网络空闲, 此时双方不会接收到对等方的确认信息, 拥塞窗口要不断的减少

AIMD算法:
乘法减少(Multiplicative Decrease):不论在慢开始阶段或拥塞避免阶段, 只要出现超时, 就把慢开始门限值减半(当前拥塞窗口的一半)
加法增大(Additive Increase):执行拥塞避免算法后, 使拥塞窗口缓慢增大, 以防止网络过早出现拥塞
拥塞避免不能彻底避免拥塞, 只是在拥塞避免阶段将拥塞窗口控制为线性增加, 使网络不容易出现拥塞

腾讯面试题

TCP的拥塞控制机制是什么?请简单说说.
答:咱们知道TCP经过一个定时器(timer)采样了RTT并计算RTO, 可是, 若是网络上的延时忽然增长, 那么, TCP对这个事作出的应对只有重传数据, 然而重传会致使网络的负担更重, 因而会致使更大的延迟以及更多的丢包, 这就致使了恶性循环, 最终造成“网络风暴” —— TCP的拥塞控制机制就是用于应对这种状况.
首先须要了解一个概念, 为了在发送端调节所要发送的数据量, 定义了一个“拥塞窗口”(Congestion Window), 在发送数据时, 将拥塞窗口的大小与接收端ack的窗口大小作比较, 取较小者做为发送数据量的上限.
拥塞控制主要是四个算法:

1.慢启动:意思是刚刚加入网络的链接, 一点一点地提速, 不要一上来就把路占满.
链接建好的开始先初始化cwnd = 1, 代表能够传一个MSS大小的数据.
每当收到一个ACK, cwnd++; 呈线性上升
每当过了一个RTT, cwnd = cwnd*2; 呈指数让升
阈值ssthresh(slow start threshold), 是一个上限, 当cwnd >= ssthresh时, 就会进入“拥塞避免算法”

2.拥塞避免:当拥塞窗口 cwnd 达到一个阈值时, 窗口大小再也不呈指数上升, 而是以线性上升, 避免增加过快致使网络拥塞.
每当收到一个ACK, cwnd = cwnd + 1/cwnd
每当过了一个RTT, cwnd = cwnd + 1

拥塞发生:当发生丢包进行数据包重传时, 表示网络已经拥塞. 分两种状况进行处理:

(1) 等到RTO超时, 重传数据包: sshthresh=cwnd/2; cwnd 重置为 1; 进入慢启动过程

(2) 在收到3个duplicate ACK时就开启重传, 而不用等到RTO超时:sshthresh = cwnd; cwnd = cwnd /2;进入快速恢复算法

3.进入慢启动过程
在收到3个duplicate ACK时就开启重传, 而不用等到RTO超时
sshthresh = cwnd = cwnd /2

进入快速恢复算法——Fast Recovery
4.快速恢复:至少收到了3个Duplicated Acks, 说明网络也不那么糟糕, 能够快速恢复.
而后启动快速恢复算法:
  cwnd = sshthresh + 3 * MSS (3的意思是确认有3个数据包被收到了)
  重传Duplicated ACKs指定的数据包
  若是再收到duplicated Acks, 那么cwnd = cwnd +1, 而且在容许的条件下发送一个报文段, 若此后每接收到一个duplicated Ack, cwnd++
  若是收到了新的Ack, 那么, cwnd = sshthresh , 而后就进入了拥塞避免的算法了

死锁

死锁问题出现
  主机B的接收缓存满了, rwnd=0. 主机A知道了就会暂停数据发送, 等待主机B的接收缓存有空闲. 若是此时主机B没有数据发送给A那么A将不可能知道主机B会有缓存空闲, 这会致使A被阻塞(主机B仅当他有数据发送或者有确认时才会发送报文段给A)

解决死锁问题
  当发送方A收到接收方B的窗口为0的通知, 便启动一个一个持续计数器, 每隔一段时间向B发送只有一个字节数据的零窗口探测报文段. 这些报文段将被接收方确认. 最终缓存将开始清空, 而且确认报文里包含一个非0的rwnd值.

中止并等待ARQ与连续ARQ

中止等待协议
中止等待协议是tcp保证传输可靠的重要途径,”中止等待”就是指发送完一个分组就中止发送,等待对方的确认,只有对方确认过,才发送下一个分组.

中止等待协议的优势是简单,可是缺点是信道的利用率过低,一次发送一条消息,使得信道的大部分时间内都是空闲的,为了提升效率,咱们采用流水线传输,这就与下面两个协议有关系了

二:连续ARQ协议和滑动窗口协议
这两个协议主要解决的问题信道效率低和增大了吞吐量,以及控制流量的做用.

  • 连续ARQ协议:它是指发送方维护着一个窗口,这个窗口中不止一个分组,有好几个分组,窗口的大小是由接收方返回的win值决定的,因此窗口的大小是动态变化的,只要在窗口中的分组均可以被发送,这就使得TCP一次不是只发送一个分组了,从而大大提升了信道的利用率.而且它采用累积确认的方式,对于按序到达的最后一个分组发送确认.

  • 滑动窗口协议:之因此叫滑动窗口协议,是由于窗口是不断向前走的,该协议容许发送方在中止并等待确认前发送多个数据分组. 因为发送方没必要每发一个分组就停下来等待确认, 所以该协议能够加速数据的传输,还能够控制流量的问题.

  • 累积确认:若是发送方发送了5个分组,接收方只收到了1,2,4,5,没有收到3分组,那么个人确认信息只会说我指望下一个收到的分组是第三个,此时发送方会将3,4,5,所有重发一次,当通讯质量不是很好的时候,连续ARQ仍是会带来负面影响.

TCP怎么重传

RTT(Round Trip Time): 一个链接的往返时间, 即数据发送时刻到接收到确认的时刻的差值
RTO(Retransmission Time Out): 重传超时时间, 即从数据发送时刻算起, 超过这个时间便执行重传

TCP模块为每一个TCP报文段都维护一个重传定时器, 该定时器在TCP报文段第一次发送时启动. 若是超时时间内未收到接收方的应答, TCP模块将重传TCP报文段并重置定时器. 在达到必定次数尚未成功时放弃并发送一个复位信号

这里比较重要的是重传超时时间, 怎样设置这个定时器的时间(RTO), 从而保证对网络资源最小的浪费. 由于若RTO过小, 可能有些报文只是遇到拥堵或网络很差延迟较大而已, 这样就会形成没必要要的重传. 太大的话, 使发送端须要等待过长的时间才能发现数据丢失, 影响网络传输效率. 因为不一样的网络状况不同, 不可能设置同样的RTO, 实际中RTO是根据网络中的RTT(传输往返时间)来自适应调整的.

RTT和RTO的关系是: 因为网络波动的不肯定性, 每一个RTT都是动态变化的, 因此RTO也应随着RTT动态变化
重传定时器: 当TCP发送报文段时, 就建立这个特定报文段的重传定时器, 若在定时器超时以前收到对报文段的确认, 则撤销定时器; 若在收到对特定报文段的确认以前计时器超时, 则重传该报文, 而且进行RTO=2*RTO进行退避

超时重传有如下三种状况:

  1. 分组丢失: 发送方发送分组, 接收方没有收到分组, 那么接收方不会发出确认, 只要发送方过一段时间没有收到确认, 就认为刚才的分组丢了, 那么发送方就会再次发送.
  2. 确认丢失: 发送方发送成功, 接收方接收成功, 确认分组也被发送, 可是分组丢失, 那么到了等待时间, 发送方没有收到确认, 又会发送分组过去, 此时接收方前面已经收到了分组, 那么此时接收方要作的事就是:丢弃分组,从新发送确认.
  3. 传送延迟: 发送方发送成功, 接收方接收成功, 确认分组也被发送, 没有丢失, 可是因为传输太慢, 等到了发送方设置的时间,发送方又会从新发送分组, 此时接收方要作的事情:丢弃分组,从新发送确认. 发送方若是收到两个或者多个确认,就中止发送,丢弃其余确认.

差错恢复机制

假设主机A向主机B发送5个连续的报文段, 主机B对每一个报文段进行确认, 其中第二个报文段丢失, 其他报文段以及重传的第二个报文段均被主机B正确接收, 主机A正确接收全部ACK报文段;报文段从1开始依次连续编号(即一、二、3……), 主机A的超时时间足够长. 请回答下列问题:
1).若是分别采用GBN、SR和TCP协议, 则对应这三个协议, 主机A分别总共发了多少个报文段?主机B分别总共发送了多少个ACK?它们的序号是什么?(针对3个协议分别给出解答)
2).若是对上述三个协议, 超时时间比5RTT长得多, 那么哪一个协议将在最短的时间间隔内成功交付5个报文段?

(1)当采用GBN协议时, 由GBN协议可得:
主机A共发送了9个报文段, 首先发送报文段1,2,3,4,5, 当报文2丢失后, 重发报文段2,3,4,5共9个;
主机B共发送8个ACK, 首先发送ACK1, 报文段2丢失, 所以对于3,4,5都发送ACK1共4个ACK1, 后对于重传的2,3,4,5, 则发送ACK2, ACK3, ACK4, ACK5, 一共8个ACK.

当采用SR协议时, 由SR协议可得:
主机A共发送了6个报文段, 首先发送报文段1,2,3,4,5, 当报文2丢失后, 重发报文段2共6个报文段;
主机B共发送5个ACK, 首先发送ACK1, ACK3, ACK4, ACK5, 对于重发的报文段2, 则发送ACK2共5个ACK.

当采用TCP协议时, 由TCP协议可得:
主机A共发送了6个报文段, 首先发送报文段1,2,3,4,5, 当报文2丢失后, 重发报文段2共6个报文段;
主机B共发送5个ACK, 首先发送4个ACK2, 重传后发送一个ACK6一共5个ACK.
(2)采用TCP协议可在最短的时间间隔内成功交付5个报文段, 由于TCP有快速重传机制, 即在未超时状况下就开始重传丢失的2号报文段.

GBN, SR, TCP

GBN: 失序后不缓存, 直接丢弃, 重传失序后全部数据
SR: 缓存失序后数据, 只重传失序的部分
TCP收到乱序数据后会将其放到乱序序列中, 而后发送重复ACK给对端. 对端若是收到多个重复的ACK, 认为发生丢包, TCP会重传最后确认的包开始的后续包. 这样原先已经正确传输的包, 可能会重复发送, 下降了TCP性能.
为改善这种状况, 发展出SACK技术, 使用SACK选项能够告知发包方收到了哪些数据, 发包方收到这些信息后就会知道哪些数据丢失, 而后当即重传丢失的部分.
SACK --> 累计确认, 延迟确认(正常TCP断开是4次挥手, 可是抓包抓到大部分都是3次挥手. 就是延迟确认的结果吧. )

TCP 为何是可靠链接

tcp不可靠表如今:

  1. 差错 --> 端到端校验和
  2. 丢包 --> 超时重传 + 确认机制
  3. 失序 --> tcp每一个段的第一个字节都有一个序号
  4. 重复 --> tcp对等方收到数据后会对数据进行重排, 经过序号机制
  5. 流量控制
  6. 拥塞控制

保证可靠性方法:

  1. 应用数据被分隔成TCP认为最适合发送的数据块, 称为段传递给IP层
  2. 当TCP发出一个段后, 它启动一个定时器, 等待目的段确认收到这个报文段. 若是不能及时收到一个确认, 将从新发送这个报文段
  3. 当TCP收到发自TCP链接另外一端的数据, 它将发送一个确认. 这个确认不是当即发送, 一般推迟几分之一秒
  4. TCP将保持它首部和数据的校验和. 这是一个端到端的校验和, 目的是检测数据在传输过程当中的任何变化. 若是收到的校验和有差错, TCP将丢弃这个报文而且不确认(致使对方超时重传)
  5. TCP承载与IP数据报来传输, 而IP数据报的到达可能会失序, 所以报文段的到达也可能失序. TCP将对收到的数据进行从新排序
  6. IP数据报会发生重复, TCP的接收端必须丢弃重复的数据
  7. TCP还能提供流量控制. TCP链接的每一方都有必定大小的缓冲空间

TCP与UDP中一个包的大小最大能多大

链路层MTU限制, 传输数据包超过MTU限制, 意味着在IP层会对数据包进行分片, MSS一般会取一个值保证不会超过MTU大小, 默认值536字节, 路由器MTU常常等于576, 576-20(IP头部)-20(tcp头部)=536, 传输的数据不超过536就不会致使IP层的分片, 因此上层的缓冲区不要超过536(避免IP层数据包的分组分片), 应用层缓冲区取512字节保证不会超过536字节(前提不去更改MSS的默认大小). 局域网MTU为46~1500, 广域网576


传送门

数据校验的意义

粘包处理

心跳意义

TCP新手误区

UDP

UDP 是一个简单的传输层协议. 和TCP相比, UDP有下面几个显著特性:

  1. UDP 缺少可靠性. UDP 自己不提供确认, 序列号, 超时重传等机制. UDP 数据报可能在网络中被复制, 被从新排序. 即 UDP 不保证数据报会到达其最终目的地, 也不保证各个数据报的前后顺序, 也不保证每一个数据报只到达一次
  2. UDP 数据报是有长度的. 每一个UDP数据报都有长度, 若是一个数据报正确地到达目的地, 那么该数据报的长度将随数据一块儿传递给接收方. 而 TCP 是一个字节流协议, 没有任何(协议上的)记录边界.
  3. UDP 是无链接的. UDP 客户和服务器以前没必要存在长期的关系. UDP 发送数据报以前也不须要通过握手建立链接的过程.
  4. UDP 支持多播和广播.

UDP 的主要应用场景

  • 须要资源少, 网络状况稳定的内网, 或者对于丢包不敏感的应用, 好比DHCP就是基于UDP协议的.
  • 不须要一对一沟通, 创建链接, 而是能够广播的应用. 由于它不面向链接, 因此能够作到一对多, 承担广播或者多播的协议.
  • 须要处理速度快, 能够容忍丢包, 可是即便网络拥塞, 也绝不退缩, 勇往直前的时候

基于UDP的几个例子:

  • 直播. 直播对实时性的要求比较高, 宁肯丢包, 也不要卡顿的, 因此不少直播应用都基于 UDP 实现了本身的视频传输协议
  • 实时游戏. 游戏的特色也是实时性比较高, 在这种状况下, 采用自定义的可靠的 UDP 协议, 自定义重传策略, 可以把产生的延迟降到最低, 减小网络问题对游戏形成的影响
  • 物联网. 一方面, 物联网领域中断资源少, 极可能知识个很小的嵌入式系统, 而维护 TCP 协议的代价太大了;另外一方面, 物联网对实时性的要求也特别高. 好比 Google 旗下的 Nest 简历 Thread Group, 推出了物联网通讯协议 Thread, 就是基于 UDP 协议的

TCP 和 UDP 的区别

  1. TCP 是面向链接的, UDP 是面向无链接的
  2. TCP 是面向字节流的, UDP 是基于数据报的
  3. TCP 保证数据正确性, UDP 可能丢包
  4. TCP 保证数据顺序, UDP 不保证
    UDP程序结构较简单

QQ通信

无论UDP仍是TCP,最终登录成功以后,QQ都会有一个TCP链接来保持在线状态。这个TCP链接的远程端口通常是80,采用UDP方式登录的时候,端口是8000。

DDOS

DDoS是分布式拒绝服务(distributeddenial-of-service)的简称. DDoS攻击是指攻击者经过控制傀儡计算机, 大量持续地向攻击目标请求资源, 从而阻塞了正经常使用户的合法请求. DDoS攻击的对象主要是网站和域名(DNS)服务器, 大量消耗服务器的资源, 包括内存、CPU以及网络带宽等, 使其不能提供正常服务. 此外, DDoS也能够对网络基础设施进行攻击, 如路由器、交换机等, 经过巨大的攻击流量, 能够致使目标所在的网络性能大幅降低甚至瘫痪, 使目标主机不能对外提供服务.

syn flood

传送门

使用syncookie的基本原理是:在第二次TCP握手的时候, Server端不为链接分配资源, Server端返回了带syncookies的syn,ack包, 在第三次握手的时候client端也要带上这个syncookie, 此时Server端才正式分配系统资源.

第一种是缩短SYN Timeout时间: 因为SYN Flood攻击的效果取决于服务器上保持的SYN半链接数, 这个值=SYN攻击的频度 x SYN Timeout, 因此经过缩短从接收到SYN报文到肯定这个报文无效并丢弃该链接的时间, 例如设置为20秒如下(太低的SYN Timeout设置可能会影响客户的正常访问), 能够成倍的下降服务器的负荷.
第二种方法是设置SYN Cookie: 就是给每个请求链接的IP地址分配一个Cookie, 若是短期内连续受到某个IP的重复SYN报文, 就认定是受到了攻击, 之后从这个IP地址来的包会被丢弃.

UDP flood

限流, 指纹学习
传送门

DNS flood

令使用udp访问dns服务器的客户端使用tcp从新访问
传送门

HTTP flood/CC flood

利用http报文重定向机制
传送门

你所知道的套接字选项

int getsockopt(int sock, int level, int optname, void *optval, socklen_t *optlen);  
int setsockopt(int sock, int level, int optname, const void *optval, socklen_t optlen); 
// level指定控制套接字的层次.能够取三种值:
// 1)SOL_SOCKET:通用套接字选项.
// 2)IPPROTO_IP:IP选项.
// 3)IPPROTO_TCP:TCP选项. 

SOL_SOCKET

得到套接字错误

若是fd上出现了错误, 那么第一次调用getsockopt会经过status返回错误缘由. 若是此时并无调用close(fd), 按理说这个错误在fd上依然存在, 可是若是再次调用上面的getsockopt, 则会告知用户此fd上没有任何错误. 这种状况常常会发生在函数之间传递fd时, 一个函数A里面作了getsockopt判断, 以后将fd传至别的函数B, 函数B不知道fd的状态, 再次调用getsockopt, 会误认为fd上没有错误了.
因此若是在fd上没有任何读写操做的话, fd上的getsockopt要只调用一次, 以后, 要将该次getsockopt的状态和fd一块儿传递给别的函数. 省得出现上面的问题.

int optval;
socklen_t optlen = sizeof optval;
if (::getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &optval, &optlen) < 0) {
    return errno;
}
else {
    return optval;
}

SO_REUSEADDR

容许重用本地地址和端口

int optval = on ? 1 : 0;
::setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof optval);

SO_KEEPALIVE

进行长链接通讯时(如移动应用开发中手机端与服务端要维持一个稳定的链接, 进行实时消息传递), 须要在应用层定义心跳机制. 有人问, TCP自己就是可靠传输协议, 为何还须要应用层来实现心跳机制呢?
【然而】在使用TCP创建链接后
1) 不少防火墙等对于空闲socket自动关闭
2) 对于非正常断开, 服务器并不能检测到(如拔网线, 直接关机等).

【解决办法--keepalive属性】
套接字自己是有一套心跳保活机制的, 不过默认的设置并不像咱们一厢情愿的那样有效. 在双方TCP套接字创建链接后(即都进入ESTABLISHED状态)而且在两个小时左右上层没有任何数据传输的状况下, 这套机制才会被激活.

不少人认为两个小时的时间设置得很不合理. 为何不设置成为10分钟, 或者更短的时间?(能够经过SO_KEEPALIVE选项设置. )可是这样作其实并不被推荐. 实际上这套机制只是操做系统底层使用的一个被动机制, 原理上不该该被上层应用层使用. 当系统关闭一个由KEEPALIVE机制检查出来的死链接时, 是不会主动通知上层应用的, 只有在调用相应的IO操做在返回值中检查出来
所以, 忘记SO_KEEPALIVE, 在应用层本身写一套保活机制比较靠谱.

int optval = on ? 1 : 0;
setsockopt(sockfd_, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof optval);

SO_RCVTIMEO和SO_SNDTIMEO

int nNetTimeout=1000;//1秒
//发送时限
setsockopt(socket, SOL_S0CKET,SO_SNDTIMEO, (char *)&nNetTimeout,sizeof(int));
//接收时限
setsockopt(socket, SOL_S0CKET,SO_RCVTIMEO, (char *)&nNetTimeout,sizeof(int));

在TCP链接中, recv等函数默认为阻塞模式(block), 即直到有数据到来以前函数不会返回, 而咱们有时则须要一种超时机制使其在必定时间后返回而无论是否有数据到来, 这里咱们就会用到setsockopt()函数:

int  setsockopt(int  s, int level, int optname, void* optval, socklen_t* optlen);
这里咱们要涉及到一个结构:
struct timeval
{
        time_t tv_sec;
        time_t tv_usec;
};
这里第一个域的单位为秒, 第二个域的单位为微秒. 
struct timeval tv_out;
tv_out.tv_sec = 1;
tv_out.tv_usec = 0;
填充这个结构后, 咱们就能够以以下的方式调用这个函数:
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv_out, sizeof(tv_out));(具体参数能够man一下, 或查看MSDN)
这样咱们就设定了recv()函数的超时机制, 当超过tv_out设定的时间而没有数据到来时recv()就会返回0值.

SO_RCVBUF和SO_SNDBUF

SO_RCVBUF和SO_SNDBUF每一个套接口都有一个发送缓冲区和一个接收缓冲区, 使用这两个套接口选项能够改变缺省缓冲区大小. 默认8K

// 接收缓冲区
int nRecvBuf=32*1024;         //设置为32K
setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));
//发送缓冲区
int nSendBuf=32*1024;//设置为32K
setsockopt(s,SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));

注意: 当设置TCP套接口接收缓冲区的大小时, 函数调用顺序是很重要的, 由于TCP的窗口规模选项是在创建链接时用SYN与对方互换获得的. 对于客户, O_RCVBUF选项必须在connect以前设置;对于服务器, SO_RCVBUF选项必须在listen前设置.

TCP_NODELAY

Nagle算法能够必定程度上避免网络拥塞, Nagle算法: 若频发送小数据包, 会作些延迟, 等待后续数据包合并一块儿发生
TCP_NODELAY选项能够禁用Nagle算法. 禁用Nagle算法, 能够避免连续发包出现延迟(通常是200ms), 这对于编写低延迟的网络服务很重要

int optval = on ? 1 : 0;        // 1: 禁用, 0: 不由用
::setsockopt(sockfd_, IPPROTO_TCP, TCP_NODELAY, &optval, sizeof optval);

SIGPIPE

默认状况下, 往一个读端关闭的管道或socket链接中写数据将引起SIGPIPE信号. 须要在代码中捕获处理该信号, 或者至少忽略它, 由于程序接收到SIGPIPE信号的默认行为是结束进程, 而绝对不但愿由于错误的写操做而致使程序的退出. 引发SIGPIPE信号的写操做将设置errno

向一个已经收到FIN的套接字中写是容许的, 接收到FIN仅仅表明对方再也不发送数据, 它并不能确认对方的进程是否已经消失. 若对方进程消失, 这时须要客户端调用write, 此次调用并不会产生断开的管道, 会发现对等方的进程不存在了, 对等方的TCP协议栈会发送RST链接重置的TCP协议段过来, 这意味着对等方读管道不存在

在收到RST报文段以后, 若是再调用write就会产生SIGPIPE信号, 对于这个信号的处理方式默认退出进程, 一般改变默认处理方式, 使用signal(SIGPIPE, SIG_IGN);

void echo_clie(int sock) {
    char sendbuf[1024] = { 0 };
    char recvbuf[1024] = { 0 };
    while (fget(sendbuf, sizeof(sendbuf), stdin) != NULL) { // 若此时服务器端异常终止, 客户端没有及时read此时的FIN报文段
        write(sock, sendbuf, 1);                            // 向已经终止的进程发送数据会返回RST
        write(sock, sendbuf + 1, strlen(sendbuf) - 1);      // 向返回RST的套接字继续发送数据会产生SIGPIPE信号
        
        int ret = readline(sock, recvbuf, sizeof(recvbuf)); // 返回FIN, RST, SIGPIPE
        //...
    }
}

套接字编程

int socket(int domain, int type, int protocol);
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
int listen(int sockfd, int backlog);
int accept(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len);
int accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags);
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
int close(int fd);

int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

// 得到本地/对等端地址及端口
int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

// 地址转换
int inet_pton(int af, const char *src, void *dst);
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
char *inet_ntoa(struct in_addr in);
int inet_aton(const char *cp, struct in_addr *inp);

// 两个宏用于定义地址转换后字符串的长度
#define INET_ADDRSTRLEN 16  /* for IPv4 dotted-decimal */
#define INET6_ADDRSTRLEN 46 /* for IPv6 hex string */

// 字节序转换
uint16_t htobe16(uint16_t host_16bits);
uint32_t htobe32(uint32_t host_32bits);
uint64_t htobe64(uint64_t host_64bits);
uint16_t be16toh(uint16_t big_endian_16bits);
uint32_t be32toh(uint32_t big_endian_32bits);
uint64_t be64toh(uint64_t big_endian_64bits);
// 下面转换函数没有上面强
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);

// Linux 2.6.27以上的内核支持SOCK_NONBLOCK与SOCK_CLOEXEC
int sockfd = ::socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_TCP);

int flags = ::fcntl(sockfd, F_GETFL, 0);
flags |= O_NONBLOCK;
int ret = ::fcntl(sockfd, F_SETFL, flags);

// close-on-exec
flags = ::fcntl(sockfd, F_GETFD, 0);
flags |= FD_CLOEXEC;
ret = ::fcntl(sockfd, F_SETFD, flags);

HTTP

参考
参考

你是怎么理解http

  1. 目前最成功的互联网协议之一.
  2. 基于TCP协议的一个报文协议, 其报文头是不定长且任意扩展的, 这也使得这个协议充满了生命力.
  3. 设计很是简单、轻巧, 却又功能强大.
  4. 没有人能完整描述HTTP协议, 由于这个协议的细节能够随时扩充. 例如控制咖啡壶什么的……

HTTP1.0 HTTP1.1

HTTP1.0: 无状态, 无链接. 无状态性能够借助cookie/session机制来作身份认证和状态记录
问题: 没法复用链接, 队头阻塞
队头阻塞(head of line blocking). 因为HTTP1.0规定下一个请求必须在前一个请求响应到达以前才能发送. 假设前一个请求响应一直不到达, 那么下一个请求就不发送, 一样的后面的请求也给阻塞了.

HTTP1.1

  1. 缓存处理, 在HTTP1.0中主要使用header里的If-Modified-Since, Expires来作为缓存判断的标准, HTTP1.1则引入了更多的缓存控制策略例如Entity tag, If-Unmodified-Since, If-Match, If-None-Match等更多可供选择的缓存头来控制缓存策略

  2. 带宽优化及网络链接的使用, HTTP1.0中, 存在一些浪费带宽的现象, 例如客户端只是须要某个对象的一部分, 而服务器却将整个对象送过来了, 而且不支持断点续传功能, HTTP1.1则在请求头引入了Range头域, 它容许只请求资源的某个部分, 即返回码是206(Partial Content), 这样就方便了开发者自由的选择以便于充分利用带宽和链接.

  3. 错误通知的管理, 在HTTP1.1中新增了24个错误状态响应码, 如409(Conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除.

  4. Host头处理, 在HTTP1.0中认为每台服务器都绑定一个惟一的IP地址, 所以, 请求消息中的URL并无传递主机名(hostname). 但随着虚拟主机技术的发展, 在一台物理服务器上能够存在多个虚拟主机(Multi-homed Web Servers), 而且它们共享一个IP地址. HTTP1.1的请求消息和响应消息都应支持Host头域, 且请求消息中若是没有Host头域会报告一个错误(400 Bad Request).

  5. 长链接, HTTP 1.1支持长链接(PersistentConnection)在HTTP1.1中默认开启Connection: keep-alive, 必定程度上弥补了HTTP1.0每次请求都要建立链接的缺点.

  6. 请求的流水线处理, 基于HTTP1.1的长链接, 使得请求管线化成为可能. 管线化使得请求可以“并行”传输. 举个例子来讲, 假如响应的主体是一个html页面, 页面中包含了不少img, 这个时候keep-alive就起了很大的做用, 可以进行“并行”发送多个请求. (注意这里的“并行”并非真正意义上的并行传输, 具体解释以下.)须要注意的是, 服务器必须按照客户端请求的前后顺序依次回送相应的结果, 以保证客户端可以区分出每次请求的响应内容, 一旦有某请求超时等, 后续请求只能被阻塞. 也就是说, HTTP管道化可让咱们把先进先出队列从客户端(请求队列)迁移到服务端(响应队列).

    如: 客户端同时发了两个请求分别来获取html和css, 假如说服务器的css资源先准备就绪, 服务器也会先发送html再发送css. 换句话来讲, 只有等到html响应的资源彻底传输完毕后, css响应的资源才能开始传输. 也就是说, 不容许同时存在两个并行的响应.

缺点:虽然容许复用TCP链接, 可是同一个TCP链接里面, 全部的数据通讯是按次序进行的. 服务器只有处理完一个请求, 才会接着处理下一个请求. 若是前面的处理特别慢, 后面就会有许多请求排队等着. 这将致使“队头堵塞”
避免方式:一是减小请求数, 二是同时多开持久链接

HTTP1.0 定义了三种请求方法: GET, POST 和 HEAD方法.
HTTP1.1 新增了六种请求方法:OPTIONS、PUT、PATCH、DELETE、TRACE 和 CONNECT 方法.

HTTP1.0和1.1现存的一些问题

HTTP/1.1 缺点

  1. HTTP/1.0一次只容许在一个TCP链接上发起一个请求,HTTP/1.1使用的流水线技术也只能部分处理请求并发,仍然会存在队列头阻塞问题,所以客户端在须要发起屡次请求时,一般会采用创建多链接来减小延迟。
  2. 单向请求,只能由客户端发起。
  3. 请求报文与响应报文首部信息冗余量大。
  4. 数据未压缩,致使数据的传输量大。

SPDY

SPDY是Speedy的昵音,意为“更快”。它是Google开发的基于TCP协议的应用层协议。目标是优化HTTP协议的性能,经过压缩、多路复用和优先级等技术,缩短网页的加载时间并提升安全性。SPDY协议的核心思想是尽可能减小TCP链接数。SPDY并非一种用于替代HTTP的协议,而是对HTTP协议的加强。

SPDY能够说是综合了HTTPS和HTTP二者有点于一体的传输协议, 主要解决:

  1. 多路复用. 多路复用经过多个请求stream共享一个tcp链接的方式, 解决了HOL blocking的问题, 下降了延迟同时提升了带宽的利用率.
  2. 请求优先级. 多路复用带来一个新的问题是, 在链接共享的基础之上有可能会致使关键请求被阻塞. SPDY容许给每一个request设置优先级, 这样重要的请求就会优先获得响应. 好比浏览器加载首页, 首页的html内容应该优先展现, 以后才是各类静态资源文件, 脚本文件等加载, 这样能够保证用户能第一时间看到网页内容.
  3. header压缩. 前面提到HTTP1.x的header不少时候都是重复多余的. 选择合适的压缩算法能够减少包的大小和数量.
  4. 基于HTTPS的加密协议传输, 大大提升了传输数据的可靠性.
  5. 服务端推送, 采用了SPDY的网页, 例如个人网页有一个sytle.css的请求, 在客户端收到sytle.css数据的同时, 服务端会将sytle.js的文件推送给客户端, 当客户端再次尝试获取sytle.js时就能够直接从缓存中获取到, 不用再发请求了. SPDY构成图:

HTTP2.0

HTTP/2是HTTP协议自1999年HTTP1.1发布后的首个更新,主要基于SPDY协议。HTTP2.0的特色是:在不改动HTTP语义、方法、状态码、URI及首部字段的状况下,大幅度提升了web性能。

HTTP2.0能够说是SPDY的升级版(其实本来也是基于SPDY设计的), 可是, HTTP2.0 跟 SPDY 仍有不一样的地方, 主要是如下两点:

  • HTTP2.0 支持明文HTTP传输, 而SPDY强制使用HTTPS
  • HTTP2.0 消息头的压缩算法采用HPACK, 而非SPDY采用的 DEFLATE
  1. 二进制分帧
  • HTTP/1.1 版的头信息确定是文本(ASCII编码), 数据体能够是文本, 也能够是二进制. HTTP/2 则是一个完全的二进制协议, 头信息和数据体都是二进制, 而且统称为”帧”:头信息帧和数据帧.
  • 二进制协议解析起来更高效、“线上”更紧凑, 更重要只有0和1的组合错误更少.

[][https://image-static.segmentfault.com/293/521/293521278-5a6e83af997f9_articlex]

  1. 彻底多路复用, 而非有序并阻塞的、只需一个链接便可实现并行;
  • 流(stream):已创建链接上的双向字节流.
  • 消息:与逻辑消息对应的完整的一系列数据帧.
  • 帧(frame):HTTP2.0通讯的最小单位, 每一个帧包含帧头部, 至少也会标识出当前帧所属的流(stream id).

    从图中可见, 全部的HTTP2.0通讯都在一个TCP链接上完成, 这个链接能够承载任意数量的双向数据流.
    每一个数据流以消息的形式发送, 而消息由一或多个帧组成. 这些帧能够乱序发送, 而后再根据每一个帧头部的流标识符(stream id)从新组装.
    举个例子, 每一个请求是一个数据流, 数据流以消息的方式发送, 而消息又分为多个帧, 帧头部记录着stream id用来标识所属的数据流, 不一样属的帧能够在链接中随机混杂在一块儿. 接收方能够根据stream id将帧再归属到各自不一样的请求当中去.
    另外, 多路复用(链接共享)可能会致使关键请求被阻塞. HTTP2.0里每一个数据流均可以设置优先级和依赖, 优先级高的数据流会被服务器优先处理和返回给客户端, 数据流还能够依赖其余的子数据流.
    可见, HTTP2.0实现了真正的并行传输, 它可以在一个TCP上进行任意数量HTTP请求. 而这个强大的功能则是基于“二进制分帧”的特性.
  1. 使用报头压缩
  • HTTP 协议是没有状态, 致使每次请求都必须附上全部信息. 因此, 请求的不少头字段都是重复的, 好比说cookie, 默认状况下, 浏览器会在每次请求的时候, 把cookie附在header上面发送给服务器. (因为cookie比较大且每次都重复发送, 通常不存储信息, 只是用来作状态记录和身份认证)
  • HTTP2.0使用头信息压缩机制来减小须要传输的header大小, 通信双方各自cache一份header fields表, 全部字段都会存入这个表, 产生一个索引号, 以后就不发送一样字段了, 只需发送索引号. 对于相同的头部, 没必要再经过请求发送, 只需发送一次;既避免了重复header的传输, 又减少了须要传输的大小. 高效的压缩算法能够很大的压缩header, 减小发送包的数量从而下降延迟.
  1. 服务器推送
  • HTTP/2 容许服务器未经请求, 主动向客户端发送资源;
  • 经过推送那些服务器任务客户端将会须要的内容到客户端的缓存中, 避免往返的延迟
  1. 更安全
    HTTP2.0使用了tls的拓展ALPN作为协议升级,除此以外,HTTP2.0对tls的安全性作了近一步增强,经过黑名单机制禁用了几百种再也不安全的加密算法。

HTTP2.0其实能够支持非HTTPS的, 可是如今主流的浏览器像chrome, firefox表示仍是只支持基于 TLS 部署的HTTP2.0协议, 因此要想升级成HTTP2.0仍是先升级HTTPS为好.

HTTP1.1的合并请求是否适用于HTTP2.0

就是http1.1的请求的流水线处理是否适用于http2.0
首先, 答案是“没有必要”. 之因此没有必要, 是由于这跟HTTP2.0的头部压缩有很大的关系.
在头部压缩技术中, 客户端和服务器均会维护两份相同的静态字典动态字典.

  • 静态字典中, 包含了常见的头部名称以及头部名称与值的组合. 静态字典在首次请求时就能够使用. 那么如今头部的字段就能够被简写成静态字典中相应字段对应的index.
  • 动态字典跟链接的上下文相关, 每一个HTTP/2链接维护的动态字典是不尽相同的. 动态字典能够在链接中不停的进行更新.

也就是说, 本来完整的HTTP报文头部的键值对或字段, 因为字典的存在, 如今能够转换成索引index, 在相应的端再进行查找还原, 也就起到了压缩的做用.
因此, 同一个链接上产生的请求和响应越多, 动态字典累积得越全, 头部压缩的效果也就越好, 因此针对HTTP/2网站, 最佳实践是不要合并资源.
另外, HTTP2.0多路复用使得请求能够并行传输, 而HTTP1.1合并请求的一个缘由也是为了防止过多的HTTP请求带来的阻塞问题. 而如今HTTP2.0已经可以并行传输了, 因此合并请求也就没有必要了.

QUIC

这里额外给你们介绍一个协议, 是由Google基于UDP实现的同为传输层的协议, 目标是但愿替代TCP协议.
该协议支持多路复用, 虽说HTTP2.0也支持多路复用, 可是下层仍然是TCP, 由于TCP的重传机制, 只要一个包丢失就得判断丢包而且重传, 致使发生队头阻塞的问题, 可是UDP没有这个限制. 除此以外, 它还有以下特色:

  1. 实现了本身的加密协议, 经过相似TCP的TFO机制实现0-RTT, 固然TLS1.3已经实现了0-RTT.
  2. 支持重传和纠错机制, 在只丢失一个包的状况下不须要重传, 使用纠错机制恢复丢失的包.
    纠错机制:经过异或的方式, 算出发出去的数据的异或值并单独发出一个包, 服务端在发现有一个包丢失的状况下, 经过其余数据包的异或值包算出丢失包.
    在丢失两个包及以上的状况就是用重传机制, 由于算不出来了.

由http升级为https须要作哪些操做

1、获取证书
升级到 HTTPS 协议的第一步,就是要得到一张证书。
证书是一个二进制文件,里面包含通过认证的网站公钥和一些元数据,要从经销商购买。
2、安装证书
证书能够放在/etc/ssl目录(Linux 系统),而后根据你使用的Web服务器进行配置。
3、修改连接
下一步,网页加载的 HTTP 资源,要所有改为 HTTPS 连接。由于加密网页内若是有非加密的资源,浏览器是不会加载那些资源的。
4、301重定向
下一步,修改 Web 服务器的配置文件,使用 301 重定向,将 HTTP 协议的访问导向 HTTPS 协议。

HTTPS

HTTP协议一般承载于TCP协议之上, 在HTTP和TCP之间添加一个安全协议层(SSL或TSL), 这个时候, 就成了咱们常说的HTTPS.

HTTPS主要做用

  • (1)对数据进行加密, 并创建一个信息安全通道, 来保证传输过程当中的数据安全;
  • (2)对网站服务器进行真实身份认证.

HTTPS和HTTP的区别

  • 一、HTTPS是加密传输协议, HTTP是明文传输协议;
  • 二、HTTPS须要用到SSL证书, 而HTTP不用;
  • 三、HTTPS比HTTP更加安全, 对搜索引擎更友好, 利于SEO,
  • 四、HTTPS标准端口443, HTTP标准端口80;
  • 五、HTTPS基于传输层, HTTP基于应用层;

HTTPS和HTTP的工做过程区别

  1. HTTP 包含动做:
      浏览器打开一个 TCP 链接
      浏览器发送 HTTP 请求到服务器端
      服务器发送 HTTP 回应信息到浏览器
      TCP 链接关闭
  2. SSL 包含动做:
      验证服务器端
      客户端和服务器端选择加密算法和密码, 确保双方都支持
      验证客户端(可选)
      使用公钥加密技术来生成共享加密数据
      建立一个加密的 SSL 链接
      基于该 SSL 链接传递 HTTP 请求

HTTPS加密方式

  1. 对称加密:加密和解密都是使用的同一个密钥;
  2. 非对称加密:
      加密使用的密钥和解密使用的密钥是不相同的, 分别称为:公钥、私钥;
      公钥和算法都是公开的, 私钥是保密的.
      非对称加密过程:
        服务端生成配对的公钥和私钥
        私钥保存在服务端, 公钥发送给客户端
        客户端使用公钥加密明文传输给服务端
        服务端使用私钥解密密文获得明文
  3. 数字签名:签名就是在信息的后面再加上一段内容, 能够证实信息没有被修改过.

HTTPS的优势
尽管HTTPS并不是绝对安全, 掌握根证书的机构、掌握加密算法的组织一样能够进行中间人形式的攻击, 但HTTPS还是现行架构下最安全的解决方案, 主要有如下几个好处:
(1)使用HTTPS协议可认证用户和服务器, 确保数据发送到正确的客户机和服务器;
(2)HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议, 要比http协议安全, 可防止数据在传输过程当中不被窃取、改变, 确保数据的完整性.
(3)HTTPS是现行架构下最安全的解决方案, 虽然不是绝对安全, 但它大幅增长了中间人攻击的成本.
(4)谷歌曾在2014年8月份调整搜索引擎算法, 并称“比起同等HTTP网站, 采用HTTPS加密的网站在搜索结果中的排名将会更高”.

HTTPS的缺点
(1)HTTPS协议握手阶段比较费时, 会使页面的加载时间延长近50%, 增长10%到20%的耗电;
(2)HTTPS链接缓存不如HTTP高效, 会增长数据开销和功耗, 甚至已有的安全措施也会所以而受到影响;
(3)SSL证书须要钱, 功能越强大的证书费用越高, 我的网站、小网站没有必要通常不会用.
(4)SSL证书一般须要绑定IP, 不能在同一IP上绑定多个域名, IPv4资源不可能支撑这个消耗.
(5)HTTPS协议的加密范围也比较有限, 在黑客攻击、拒绝服务攻击、服务器劫持等方面几乎起不到什么做用. 最关键的, SSL证书的信用链体系并不安全, 特别是在某些国家能够控制CA根证书的状况下, 中间人攻击同样可行.

加密
HTTPS

SSL创建的过程

  1. 客户端经过发送Client Hello报文开始SSL通讯。报文中包含客户端支持的SSL的指定版本、加密组件(Cipher Suite)列表(所使用的加密算法及密钥长度等)。
    注意:客户端还会附加一个随机数,这里记为A。
  2. 服务器可进行SSL通讯时,会以Server Hello报文做为应答。和客户端同样,在报文中包含SSL版本以及加密组件。服务器的加密组件内容是从接收到的客户端加密组件内筛选出来的。
    注意:这里服务器一样会附加一个随机数,发给客户端,这里记为B。
  3. 以后服务器发送Certificate报文。报文中包含公开密钥证书。(具体的数字签名请看证书一节)
  4. 最后服务器发送Server Hello Done报文通知客户端,最初阶段的SSL握手协商部分结束。
    5SSL第一次握手结束后,客户端会对服务器发过来的证书进行验证,若是验证成功,解密取出证书中的公钥。(具体查看证书一节)
    接着,客户端以Client Key Exchange报文做为回应。报文中包含通讯加密中使用的一种被称为Pre-master secret的随机密码串。该报文使用从证书中解密得到的公钥进行加密(其实就是服务器的公钥)。
  5. 客户端继续发送Change Cipher Spec报文。用于告知服务端,客户端已经切换到以前协商好的加密套件(Cipher Suite)的状态,准备使用以前协商好的加密套件加密数据并传输了。
  6. 客户端发送Finished报文。该报文包含链接至今所有报文的总体校验值(也就是HASH值),用来供服务器校验。
  7. 服务器接收到客户端的请求以后,使用私钥解密报文,把Pre-master secret取出来。接着,服务器一样发送Change Cipher Spec报文。
  8. 服务器一样发送Finished报文,用来供客户端校验。
  9. 服务器和客户端的Finished报文交换完毕以后,SSL链接就算创建完成。固然,通讯会受到SSL的保护。今后处开始进行应用层协议的通讯,即发送HTTP请求。
  10. 应用层协议通讯,即发送HTTP响应。
  11. 最后由客户端断开链接。断开链接时,发送close_notify报文。上图作了一些省略,这步以后再发送TCP FIN报文来关闭与TCP的通讯。

整个过程当中产生的三个随机数有什么用呢?还有,后面进行HTTP通讯的时候,是用哪个密钥进行加密,还有怎么保证报文的完整性。

对于客户端:
当其生成了Pre-master secret以后,会结合原来的A、B随机数,用DH算法计算出一个master secret,紧接着根据这个master secret推导出hash secret和session secret。

对于服务端:
当其解密得到了Pre-master secret以后,会结合原来的A、B随机数,用DH算法计算出一个master secret,紧接着根据这个master secret推导出hash secret和session secret。

在客户端和服务端的master secret是依据三个随机数推导出来的,它是不会在网络上传输的,只有双方知道,不会有第三者知道。同时,客户端推导出来的session secret和hash secret与服务端也是彻底同样的

那么如今双方若是开始使用对称算法加密来进行通信,使用哪一个做为共享的密钥呢?过程是这样子的:

  • 双方使用对称加密算法进行加密,用hash secret对HTTP报文作一次运算生成一个MAC,附在HTTP报文的后面, 并用私钥进行加密MAC,而后用session-secret加密全部数据(HTTP+MAC),而后发送。
  • 接收方则先用session-secret解密数据,而后获得HTTP+加密后的MAC, 用公钥解密MAC,再用相同的算法计算出本身的MAC,若是两个MAC相等,证实数据没有被篡改。

    MAC(Message Authentication Code)称为报文摘要,可以查知报文是否遭到篡改,从而保护报文的完整性。

数字签名&数字证书

信息安全中有三个须要解决的问题:

  1. 保密性(Confidentiality):信息在传输时不被泄露
  2. 完整性(Integrity):信息在传输时不被篡改
  3. 有效性(Availability):信息的使用者是合法的

这三要素统称为CIA Triad。公钥密码解决保密性问题; 数字签名解决完整性问题和有效性问题

数字签名: 信息进行哈希计算取得哈希值, 而后用服务器的私钥进行加密

生成签名, 通常来讲,不直接对消息进行签名,而是对消息的哈希值进行签名,步骤以下。

  1. 对消息进行哈希计算,获得哈希值
  2. 利用私钥对哈希值进行加密,生成签名
  3. 将签名附加在消息后面,一块儿发送过去

验证签名

  1. 收到消息后,提取消息中的签名
  2. 用公钥对签名进行解密,获得哈希值1。
  3. 对消息中的正文进行哈希计算,获得哈希值2。
  4. 比较哈希值1和哈希值2,若是相同,则验证成功。

数字证书: 对服务器的公钥进行哈希计算取得哈希值, 使用CA的私钥进行加密. 证书实际上就是对公钥进行数字签名,它是对公钥合法性提供证实的技术。

如何生成证书?

  1. 服务器将公钥A给CA(公钥是服务器的)
  2. CA用本身的私钥B给公钥A加密,生成数字签名A
  3. CA把公钥A,数字签名A,附加一些服务器信息整合在一块儿,生成证书,发回给服务器。

如何验证证书?

  1. 客户端获得证书
  2. 客户端获得证书的公钥B(经过CA或其它途径)
  3. 客户端用公钥B对证书中的数字签名解密,获得哈希值
  4. 客户端对公钥进行哈希值计算
  5. 两个哈希值对比,若是相同,则证书合法。

什么是数字签名和证书?

对称加密的不足主要有两点

  1. 发送方和接收方首先须要共享相同的密钥,即存在密钥k的分发问题,如何安全的把共享密钥在双方进行分享,这自己也是一个如何安全通讯的问题,一种方法是提早双方约定好,不经过具体的通讯进行协商,避免被监听和截获。另一种方式,将是下面咱们介绍的经过非对称加密信道进行对称密码的分发和共享,即混合加密系统。
  2. 密钥管理的复杂度问题。因为对称加密的密钥是一对一的使用方式,若一方要跟n方通讯,则须要维护n对密钥。

对称加密的好处是:加密和解密的速度要比非对称加密快不少,所以经常使用非对称加密创建的安全信道进行共享密钥的分享,完成后,具体的加解密则使用对称加密。即混合加密系统。

从输入 URL 到页面加载完成, 中间发生了什么

  1. URL输入: 主要组成部分有: protocol(协议), hostname(主机名), port(端口号)
  2. DNS解析: 查找顺序: 浏览器缓存--> 操做系统缓存--> 本地host文件 --> 路由器缓存--> ISP DNS缓存 --> 顶级DNS服务器/根DNS服务器, 有迭代查询, 递归查询两种方式
  3. TCP链接: 链接涉及三次握手
  4. 发送HTTP请求: 请求行, 请求头, 空行, 请求正文
    GET/sample.jspHTTP/1.1
    Accept:image/gif.image/jpeg,/
    Accept-Language:zh-cn
    Connection:Keep-Alive
    Host:localhost
    User-Agent:Mozila/4.0(compatible;MSIE5.01;Window NT5.0)
    Accept-Encoding:gzip,deflate

    username=jinqiao&password=1234
  5. 服务器处理请求: 涉及服务器重定向, 301, 302状态码;
    反向代理客户端不是直接经过HTTP协议访问某网站应用服务器,而是先请求到Nginx,Nginx再请求应用服务器,而后将结果返回给客户端,这里Nginx的做用是反向代理服务器。同时也带来了一个好处,其中一台服务器万一挂了,只要还有其余服务器正常运行,就不会影响用户使用。
    一、什么是反向代理?
    客户端原本能够直接经过HTTP协议访问某网站应用服务器,网站管理员能够在中间加上一个Nginx,客户端请求Nginx,Nginx请求应用服务器,而后将结果返回给客户端,此时Nginx就是反向代理服务器。
  6. 服务器响应请求: 状态行, 响应头, 空行, 响应正文
  7. 浏览器解析渲染页面: html解析
  8. 链接结束: 断开涉及四次挥手

传送门
传送门
传送门

301和302状态码

301和302状态码都表示重定向,就是说浏览器在拿到服务器返回的这个状态码后会自动跳转到一个新的URL地址,这个地址能够从响应的Location首部中获取(用户看到的效果就是他输入的地址A瞬间变成了另外一个地址B)——这是它们的共同点。他们的不一样在于。301表示旧地址A的资源已经被永久地移除了(这个资源不可访问了),搜索引擎在抓取新内容的同时也将旧的网址交换为重定向以后的网址;302表示旧地址A的资源还在(仍然能够访问),这个重定向只是临时地从旧地址A跳转到地址B,搜索引擎会抓取新的内容而保存旧的网址。

多进程多线程浏览器(好比 Chrome), 主控进程, 插件进程, GPU进程, 每一个 tab 一个进程, tab 进程内有网络请求线程等

简版
详细版

协议栈各层经常使用协议

应用层:
HTTP:超文本传输协议, 基于TCP, 使用80号端口, 是用于从www服务器传输超文本到本地浏览器的传输协议.
SMTP:简单邮件传输协议, 基于TCP, 使用25号端口, 是一组用于由源地址到目的地址传送邮件的规则, 用来控制信件的发送、中转.
FTP:文件传输协议, 基于TCP, 通常上传下载用FTP服务, 数据端口是20号, 控制端口是21号.
TELNET:远程登陆协议, 基于TCP, 使用23号端口, 是Internet远程登录服务的标准协议和主要方式. 为用户提供了在本地计算机上完成远程主机工做的能力. 在终端使用者的电脑上使用telnet程序链接到服务器. 使用明码传送, 保密性差、简单方便.
DNS:域名解析, 基于UDP, 使用53号端口, 提供域名到IP地址之间的转换. 同时使用tcp 53端口
SSH:安全外壳协议, 基于TCP, 使用22号端口, 为创建在应用层和传输层基础上的安全协议. SSH 是目前较可靠, 专为远程登陆会话和其余网络服务提供安全性的协议.

GET和POST区别

GET和POST是HTTP协议中的两种发送请求的方法。而HTTP是是基于TCP/IP的关于数据如何在万维网中如何通讯的协议。HTTP的底层是TCP/IP。因此GET和POST的底层也是TCP/IP,也就是说,GET/POST都是TCP连接。GET和POST能作的事情是同样同样的, 因此给GET加上request body,给POST带上url参数,技术上是彻底行的通的。

业界不成文的规定是,(大多数)浏览器一般都会限制url长度在2K个字节,而(大多数)服务器最多处理64K大小的url。超过的部分,恕不处理。若是你用GET服务,在request body偷偷藏了数据,不一样服务器的处理方式也是不一样的,有些服务器会帮你卸货,读出数据,有些服务器直接忽略,因此,虽然GET能够带request body,也不能保证必定能被接收到哦。

因此GET和POST本质上就是TCP连接,并没有差异。可是因为HTTP的规定和浏览器/服务器的限制,致使他们在应用过程当中体现出一些不一样。

GET和POST还有一个重大区别,简单的说:GET产生一个TCP数据包;POST产生两个TCP数据包。

  • 对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);
  • 而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。

由于POST须要两步,时间上消耗的要多一点,看起来GET比POST更有效。所以Yahoo团队有推荐用GET替换POST来优化网站性能。但这是一个坑!跳入需谨慎。为何?

  1. GET与POST都有本身的语义,不能随便混用。
  2. 据研究,在网络环境好的状况下,发一次包的时间和发两次包的时间差异基本能够无视。而在网络环境差的状况下,两次包的TCP在验证数据包完整性上,有很是大的优势。
  3. 并非全部浏览器都会在POST中发送两次包,Firefox就只发送一次。

传送门

Cookie和Session的做用及它们之间的区别

传送门
传送门

  • Cookie
    Cookie是存储在用户本地计算机上,用于保存一些用户操做的历史信息,当用户再次访问咱们的服务器的时候,浏览器经过HTTP协议,将他们本地的Cookie内容也发到我们服务器上,从而完成验证. Cookie又分为了会话Cookie与持久Cookie,要区分这两种类型,很是的简单,持久Cookie就是咱们设置了它的过时时间,而没设置过时时间的,都属于会话Cookie由于当咱们设置了Cookie的过时时间,那么这个Cookie就会存储在用户的硬盘中,而不是在内存中,由于不在内存中,无论用户是关闭浏览器,仍是关机重启,只要在有效时间内,这个Cookie都能用

  • Session
    存储在咱们的服务器上,就是在咱们的服务器上保存用户的操做信息用户访问咱们的网站时,咱们的服务器会成一个Session ID,而后把Session ID存储起来,再把这个Session ID发给咱们的用户,用户再次访问咱们的服务器的时候,拿着这个Session ID就能验证了,当这个ID能与咱们服务器上存储的ID对应起来时,咱们就能够认为是本身人

Cookie与Session的不一样

  1. 存放位置不一样. Cookie保存在客户端,Session保存在服务端。
  2. 存取方式的不一样. Cookie中只能保管ASCII字符串,假如需求存取Unicode字符或者二进制数据,需求先进行编码。Session中可以存取任何类型的数据
  3. 安全性(隐私策略)的不一样. Cookie存储在浏览器中,对客户端是可见的,客户端的一些程序可能会窥探、复制以致修正Cookie中的内容. 而Session存储在服务器上,对客户端是透明的,不存在敏感信息泄露的风险。
  4. 有效期上的不一样. 只须要设置Cookie的过时时间属性为一个很大很大的数字,Cookie就能够在浏览器保存很长时间。 因为Session依赖于名为JSESSIONID的Cookie,而Cookie JSESSIONID的过时时间默许为–1,只需关闭了浏览器(一次会话结束),该Session就会失效。
  5. 对服务器形成的压力不一样 Session是保管在服务器端的,每一个用户都会产生一个Session。假如并发访问的用户十分多,会产生十分多的Session,耗费大量的内存。而Cookie保管在客户端,不占用服务器资源。假如并发阅读的用户十分多,Cookie是很好的选择。

二者结合使用

  • 存储在服务端:经过cookie存储一个session_id, 而后具体的数据则是保存在session中. 若是用户已经登陆, 则服务器会在 cookie中保存一个 session_id, 下次再次请求的时候, 会把该 session_id携带上来, 服务器根据 session_id在 session库中获取用户的 session数据. 就能知道该用户究竟是谁, 以及以前保存的一些状态信息. 这种专业术语叫作 server side session.
  • 将session数据加密, 而后存储在cookie中. 这种专业术语叫作 client side session

会话机制

  • Cookie
      Cookie是客户端保持状态的方法。
      Cookie简单的理解就是存储由服务器发至客户端并由客户端保存的一段字符串。为了保持会话,服务器能够在响应客户端请求时将Cookie字符串放在Set-Cookie下,客户机收到Cookie以后保存这段字符串,以后再请求时候带上Cookie就能够被识别。
      除了上面提到的这些,Cookie在客户端的保存形式能够有两种,一种是会话Cookie一种是持久Cookie,会话Cookie就是将服务器返回的Cookie字符串保持在内存中,关闭浏览器以后自动销毁,持久Cookie则是存储在客户端磁盘上,其有效时间在服务器响应头中被指定,在有效期内,客户端再次请求服务器时均可以直接从本地取出。须要说明的是,存储在磁盘中的Cookie是能够被多个浏览器代理所共享的。

  • Session
      Session是服务器保持状态的方法。
      首先须要明确的是,Session保存在服务器上,能够保存在数据库、文件或内存中,每一个用户有独立的Session用户在客户端上记录用户的操做。咱们能够理解为每一个用户有一个独一无二的Session ID做为Session文件的Hash键,经过这个值能够锁定具体的Session结构的数据,这个Session结构中存储了用户操做行为。

当服务器须要识别客户端时就须要结合Cookie了。每次HTTP请求的时候,客户端都会发送相应的Cookie信息到服务端。实际上大多数的应用都是用Cookie来实现Session跟踪的,第一次建立Session的时候,服务端会在HTTP协议中告诉客户端,须要在Cookie里面记录一个Session ID,之后每次请求把这个会话ID发送到服务器,我就知道你是谁了。若是客户端的浏览器禁用了Cookie,会使用一种叫作URL重写的技术来进行会话跟踪,即每次HTTP交互,URL后面都会被附加上一个诸如sid=xxxxx这样的参数,服务端据此来识别用户,这样就能够帮用户完成诸如用户名等信息自动填入的操做了。

介绍经常使用的请求方法

GET     请求指定的页面信息,并返回实体主体。
HEAD    相似于get请求,只不过返回的响应中没有具体的内容,用于获取报头
POST    向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会致使新的资源的创建和/或已有资源的修改。
PUT     从客户端向服务器传送的数据取代指定的文档的内容。
DELETE  请求服务器删除指定的页面。
OPTIONS 容许客户端查看服务器的性能。
COPY    请求服务器将指定的页面拷贝至另外一个网络地址。

介绍常见的 HTTP 状态码(至少五个)

分类 分类描述
1** 信息,服务器收到请求,须要请求者继续执行操做
2** 成功,操做被成功接收并处理
3** 重定向,须要进一步的操做以完成请求
4** 客户端错误,请求包含语法错误或没法完成请求
5** 服务器错误,服务器在处理请求的过程当中发生了错误

传送门

介绍常见的 HTTP 头部(至少五个)

传送门

HTTP 中与缓存相关的头部有哪些, 它们有什么区别

Expires(响应)/Cache-Control(请求+响应): 前者指定过时绝对时间, 后者指定过时相对时间

Last-Modified(响应)/If-Modified-Since(请求): 修改时间

ETag(响应)/If-None-Match(请求): 把时间换成编码

传送门

各类刷新

假设对一个资源:
浏览器第一次访问,获取资源内容和cache-control: max-age:600,Last_Modify: Wed, 10 Aug 2013 15:32:18 GMT
因而浏览器把资源文件放到缓存中,而且决定下次使用的时候直接去缓存中取了。

浏览器url回车
浏览器发现缓存中有这个文件了,好了,就不发送任何请求了,直接去缓存中获取展示。(最快)

下面我按下了F5刷新
F5就是告诉浏览器,别偷懒,好歹去服务器看看这个文件是否有过时了。因而浏览器就胆胆襟襟的发送一个请求带上If-Modify-since:Wed, 10 Aug 2013 15:32:18 GMT
而后服务器发现:诶,这个文件我在这个时间后还没修改过,不须要给你任何信息了,返回304就好了。因而浏览器获取到304后就去缓存中欢欢喜喜获取资源了。

可是呢,下面咱们按下了Ctrl+F5
这个但是要命了,告诉浏览器,你先把你缓存中的这个文件给我删了,而后再去服务器请求个完整的资源文件下来。因而客户端就完成了强行更新的操做...

分别介绍强缓存和协商缓存

浏览器缓存主要分为强缓存(也称本地缓存)和协商缓存(也称弱缓存)。

强缓存
强缓存是利用 http 头中的 Expires 和 Cache-Control 两个字段来控制的,用来表示资源的缓存时间。
强缓存中,普通刷新会忽略它,但不会清除它,须要强制刷新。浏览器强制刷新,请求会带上 Cache-Control:no-cache 和 Pragma:no-cache。
一般,强缓存不会向服务器发送请求,直接从缓存中读取资源,在chrome控制台的network选项中能够看到该请求返回200的状态码。分为 from disk cache 和 from memory cache。

  • from disk cache :通常非脚本会存在内存当中,如css,html等
  • from memory cache :资源在内存当中,通常脚本、字体、图片会存在内存当

有缓存命中和缓存未命中状态:

协商缓存就是由服务器来肯定缓存资源是否可用,因此客户端与服务器端要经过某种标识来进行通讯,从而让服务器判断请求资源是否能够缓存访问。
普通刷新会启用弱缓存,忽略强缓存。只有在地址栏或收藏夹输入网址、经过连接引用资源等状况下,浏览器才会启用强缓存,这也是为何有时候咱们更新一张图片、一个js文件,页面内容依然是旧的,可是直接浏览器访问那个图片或文件,看到的内容倒是新的。
这个主要涉及到两组 header 字段: Etag 和 If-None-Match、 Last-Modified和 If-Modified-Since。
向服务器发送请求,服务器会根据这个请求的request header的一些参数来判断是否命中协商缓存。
有缓存命中和缓存未命中状态:

传送门

IP地址做用, 以及MAC地址做用

MAC地址是一个硬件地址, 用来定义网络设备位置, 主要由数据链路层负责. 而IP协议提供一种统一的地址格式, 为互联网上的每个网络和每一台主机分配一个逻辑地址, 以此来屏蔽物理地址的差别

TCP/IP数据链路层交互过程

网络层等到数据链路层用mac地址做为通讯目标, 数据包到达网络等准备往数据链路层发送的时候, 首先会去本身的arp缓存表(存放着ip-mac对应关系)去查找该目标ip的mac地址, 若是查到了, 就标明目标ip的mac地址封装到链路层数据包的包头. 若是缓存中没有找到, 会发起一个广播: who is ip XXX tell ip xxx, 全部受到广播的机器看这个ip是否是本身的, 若是是本身的, 则以单播形式将本身的mac地址回复给请求的机器

ICMP

ICMP(Internet Control Message Protocol)因特网控制报文协议。它是IPv4协议族中的一个子协议,用于IP主机、路由器之间传递控制消息。控制消息是在网络通不通、主机是否可达、路由是否可用等网络自己的消息。这些控制消息虽然不传输用户数据,可是对于用户数据的传递起着重要的做用。

ICMP协议与ARP协议不一样, ICMP靠IP协议来完成任务, 因此ICMP报文中要封装IP头部. 它与传输层协议(如TCP和UDP)的目的不一样,通常不用来在端系统之间传送数据,不被用户网络程序直接使用,除了想Ping和Tracert这样的诊断程序。

类型八、代码0:回射请求
类型0、代码0:回射应答

数据包通过路由会发生什么, 路由转发

IP数据包经由路由转发的时候源ip,目的ip,源MAC,目的mac是否发生改变,如何改变?

A—–(B1-B2)—–(C1-C2)——-E

如上拓扑图为例,B1和B2是路由器B上的两个接口,C1和C2是路由器C上的两个接口,A和E是PC,由主机A向主机E发送数据包,那么在主机A造成的数据包的目的IP就是E的IP,源IP就是主机A的IP地址,目标MAC地址就是B1的MAC地址,源MAC地址就是A的MAC地址

由A发给路由器B,B通过重封装后,源IP和目标IP是不变的,源MAC地址变成B2的MAC地址,目标MAC地址变成C1的MAC地址,封装完成发送给路由器C,路由器C接收到数据包后和B作的操做是同样的,源IP和目标IP的不变的,源MAC地址变成C2的MAC地址,目标MAC地址变成主机E的MAC地址,而后发送给主机E,这样E就收到了这个数据包,当恢复数据包的时候就是把收到的数据包的源IP地址(主机A的IP地址)和源MAC地址(接口C2的MAC地址)做为他的目标IP和目标MAC地址

数据包经由路由转发时源、目的IP地址及MAC地址变化状况

ping 的流程

ping同一网段(主机A ping主机B)

  1. 主机A封装二层报文, 检查本身的mac地址, 若无B的mac地址, 则向外发送一个arp广播(arp广播中有a的mac地址, 无b的mac地址)
  2. 交换机收到报文后, 检索本身有没有保存B的mac地址, 如有直接返回给A; 若没有, 则像全部端口发送arp广播
  3. 其余主机收到后, 若发现目的ip不是本身的ip, 则丢弃报文; 如果则当即响应, 并按一样的报文返回给主机A
  4. 主机A获得B的mac地址后, 把这个mac封装到ICMP二层报文中(有mac地址), 想主机B发送,
  5. 主机B收到报文后, 以一样格式返回一个值给A, 完成统一网段的ping

ping不一样网段(主机A ping 主机C)(假设拓扑结构为A-端口E1-端口E2-C,E1和E2为路由器的两个端口)

  • 主机A观察目的IP与本机IP是否为同一网段,结果为不一样;
  • 看本机是否设置了网关,若未,则目的不可达;如有,则执行下一步;
  • 发送一个ARP广播包(以获取路由器MAC地址),ARP广播包的源IP为主机A的ip,目的IP为主机A网关IP,即端口E1的IP,源MAC为主机A的MAC,目的MAC为广播MAC:ff-ff-ff-ff-ff-ff。
  • 路由器回应ARP包:源IP为主机A网关IP,目的IP为主机A的IP;源MAC为主机A网关MAC,即端口E1的MAC,目的MAC为主机A的MAC。
  • 主机A发送ICMP包,源MAC为主机A的MAC,目的MAC为主机A的网关MAC,源IP为主机A的IP,目的IP为主机C的IP。
  • 路由器收到ICMP包后,拆包,查IP端口对照表(路由表),发现主机C的IP所在网段的数据由E2口发出,转发包给端口E2。
  • 路由器(为了获取主机C的MAC)发送一个ARP包,源IP为端口E2的IP,目的IP为主机C的IP;源MAC为端口E2的MAC,目的MAC为广播MAC;
  • 主机C发送ARP回应,端口E2得到主机C的MAC;
  • 路由器发送ICMP,源IP为主机A的IP,目的IP为主机C的IP;源MAC为端口E2的MAC,目的MAC为主机C的MAC。
  • 主机C回应ICMP,源IP为主机C的IP,目的IP为主机A的 IP;源MAC为主机C的MAC,目的MAC为 端口E2的MAC。
  • 路由器转发ICMP,源IP为主机C的IP,目的IP为主机A的IP;源MAC为端口E1的MAC,目的MAC为主机A的MAC。
  • 主机A收到回应,则完成一次ping。(跨网段ping过程当中ICMP数据报中源IP和目的IP始终是两台主机IP地址,可是MAC地址在变化)

ping域名

  • 首先本机发送域名请求数据到PC设置的DNS ip
  • PC经过子网掩码判断DNS ip是本网段仍是跨网段(这里只考虑跨网段)
  • 因为是跨网段,PC发送DNS域名解析数据包到PC设置的网关ip上。(此时先要进行二层的mac转发,PC查看本机arp缓存表,若是表中有网关的mac地址,直接转发,若是没有,使用arp解析协议解析到网关的mac地址。以后封装成数据帧发送到三层网络层)此时PC发送三层数据到网关,源地址为PC内网地址,目的地址为DNS ip地址。而在二层源mac地址为PC mac地址,目的mac地址为网关mac地址。
  • 路由内网网关收到数据包,根据数据包的目的地址,查看路由表。根据路由表发送数据到下一跳上。(发送前,数据到达路由外网端口,会根据nat地址转换配置。造成一条内网ip+port与外网ip+port的一一对应关系。)
  • 发送到下一跳和内网通讯都是同样的,查看路由arp缓存表,若是有下一跳mac地址,就直接发送,没有的话须要arp协议解析一下。
  • 对端路由收到数据包,再接着根据路由表判断下一跳。这样一跳一跳地,最后到达DNS服务器。服务器将查询结果返回。
  • 返回的数据包在ISP的网络里最后寻址到你的路由器上,你的路由器收到数据包后,会查询路由nat链接表,寻找ip+port关系对应的内网ip。拆分数据包,封装成帧,最后PC收到域名对应的ip地址。
    到这里,域名解析过程完成,接下来ping对方ip,过程与上面几乎同样
  • 再发起一次PC到目的域名ip地址的一次ping请求信息
  • PC经过子网掩码判断对方ip是本网段仍是跨网段(这里只考虑跨网段)
  • 因为是跨网段,PC发送数据包到网关ip上。
  • 路由内网网关收到数据包,根据数据包的目的地址,查看路由表。根据路由表发送数据到下一跳上。(发送前,数据到达路由外网端口,会根据nat地址转换配置。造成一条内网IP+port与外网ip+port的一一对应关系。)
  • 发送到下一跳和内网通讯都是同样的,查看路由arp缓存表,若是有下一跳mac地址,就直接发送,没有的话就是要arp协议解析一下。
  • 服务器收到数据包后,会从新构建一个ICMP应答包,而后返回。
  • 返回的数据包在ISP的网络里最后寻址到你的路由器上,你的路由器收到数据包后,会查询路由nat链接表,寻找ip+port关系对应的内网ip。拆分数据包,封装成帧,最后PC收到ICMP应答数据包。

整个过程到此结束。在整个这个过程当中,源ip地址和目的ip地址是不变的(内网到路由器段不算在内)而mac地址是变的。

多路分解与多路复用

套接字寻址系统使得TCP和UDP可以执行传输层另外一个重要任务:多路复用多路分解。多路复用是指把多个来源的数据导向一个输出,而多路分解是把从一个来源接收的数据发送到多个输出。

多路传输/多路分解让TCP/IP协议栈较低层的协议没必要关心哪一个程序在传输数据。与应用程序相关的操做都由传输层完成了,数据经过一个与应用程序无关的管道在传输层与网际层之间传递。

多路复用和多路分解的关键就在于套接字地址。套接字地址包含了IP地址与端口号,为特定计算机上的特定应用程序提供了一个惟一的标识。例如,FTP服务器:全部客户端计算机使用熟知的TCP端口21链接到FTP服务器,但针对每台我的计算机的目的套接字是不一样的。相似地,运行于这台FTP服务器上所有网络应用程序都使用服务器的IP地址,但只有FTP服务程序使用由IP地址和TCP端口号21组成的套接字地址。

DNS

域名解析是把域名指向网站空间IP,让人们经过注册的域名能够方便地访问到网站的一种服务。IP地址是网络上标识站点的数字地址,为了方便记忆,采用域名来代替IP地址标识站点地址。域名解析就是域名到IP地址的转换过程。域名的解析工做由DNS服务器完成。

解析顺序:

  1. 浏览器缓存
    当用户经过浏览器访问某域名时,浏览器首先会在本身的缓存中查找是否有该域名对应的IP地址(若曾经访问过该域名且没有清空缓存便存在);
  2. 系统缓存
    当浏览器缓存中无域名对应IP则会自动检查用户计算机系统Hosts文件DNS缓存是否有该域名对应IP;
  3. hosts
  4. 路由器缓存
    当浏览器及系统缓存中均无域名对应IP则进入路由器缓存中检查,以上三步均为客服端的DNS缓存;
  5. ISP(互联网服务提供商)DNS缓存
    当在用户客服端查找不到域名对应IP地址,则将进入ISP DNS缓存中进行查询。好比你用的是电信的网络,则会进入电信的DNS缓存服务器中进行查找;
  6. 根域名服务器
    当以上均未完成,则进入根服务器进行查询。全球仅有13台根域名服务器,1个主根域名服务器,其他12为辅根域名服务器。根域名收到请求后会查看区域文件记录,若无则将其管辖范围内顶级域名(如.com)服务器IP告诉本地DNS服务器;
  7. 顶级域名服务器
    顶级域名服务器收到请求后查看区域文件记录,若无则将其管辖范围内主域名服务器的IP地址告诉本地DNS服务器;
  8. 主域名服务器
    主域名服务器接受到请求后查询本身的缓存,若是没有则进入下一级域名服务器进行查找,并重复该步骤直至找到正确纪录;
  9. 保存结果至缓存
    本地域名服务器把返回的结果保存到缓存,以备下一次使用,同时将该结果反馈给客户端,客户端经过这个IP地址与web服务器创建连接。

域名的层级: www.example.com真正的域名是www.example.com.root,简写为www.example.com.。由于,根域名.root对于全部域名都是同样的,因此平时是省略的。
根域名的下一级,叫作"顶级域名"(top-level domain,缩写为TLD),好比.com、.net;再下一级叫作"次级域名"(second-level domain,缩写为SLD),好比www.example.com里面的.example,这一级域名是用户能够注册的;再下一级是主机名(host),好比www.example.com里面的www,又称为"三级域名",这是用户在本身的域里面为服务器分配的名称,是用户能够任意分配的。

递归查询和迭代查询的区别

  • 递归查询
    递归查询是一种DNS 服务器的查询模式,在该模式下DNS 服务器接收到客户机请求,必须使用一个准确的查询结果回复客户机。若是DNS 服务器本地没有存储查询DNS 信息,那么该服务器会询问其余服务器,并将返回的查询结果提交给客户机。
  • 迭代查询
    DNS 服务器另一种查询方式为迭代查询,DNS 服务器会向客户机提供其余可以解析查询请求的DNS 服务器地址,当客户机发送查询请求时,DNS 服务器并不直接回复查询结果,而是告诉客户机另外一台DNS 服务器地址,客户机再向这台DNS 服务器提交请求,依次循环直到返回查询的结果
    为止。

NAT

网络地址转换(Network Address Translation,缩写:NAT)在计算机网络中是一种在IP数据包经过路由器或防火墙时重写来源IP地址或目的IP地址的技术。这种技术被广泛使用在有多台主机但只经过一个公有IP地址访问因特网的私有网络中。它是一个方便且获得了普遍应用的技术。固然,NAT也让主机之间的通讯变得复杂,致使了通讯效率的下降。

DHCP

动态主机设置协议(Dynamic Host Configuration Protocol,缩写:DHCP)是一个用于局域网的网络协议,位于OSI模型的应用层,使用UDP协议工做,主要有两个用途:

  1. 用于内部网或网络服务供应商自动分配IP地址给用户
  2. 用于内部网管理员做为对全部计算机做中央管理的手段

EGP&IGP

1、IGP:内部网关协议,即在一个自治系统内部使用的路由选择协议,如RIP和OSPF
一、RIP是一种分布式的基于距离向量的路由选择协议,要求网络中的每个路由器都要维护从它本身到其余每个目的
网络的距离向量。距离便是跳数,路由器与直接相连的网络条数为1,之后每通过一个路由器跳数加1。RIP容许一条路
径最多包含15个路由,所以当距离为16时认为不可达,这由于如此限制了网络的规模,说明RIP只能工做到规模较小的
网络中。RIP的三个要点:仅和相邻路由器交换信息;交换的信息是当前路由器知道的所有信息,即路由表;按固定的
时间间隔交换路由信息,如30秒,RIP协议使用运输层的用户数据报UDP进行传送, 所以RIP协议的位置位于应用层,
可是转发IP数据报的过程是在网络层完成的。RIP是好消息传播的快,坏消息传播的慢。

二、OSPF:最短路径优先,三个要点:采用洪泛法向本自治系统的路由器发送信息;发送的信息就是与本路由器相邻的
全部路由器的链路状态,可是只是路由器所知道的部分信息;只有当链路状态发生变化时,路由器才用洪泛发向全部路
由器发送此信息。OSPF直接使用IP数据包传送,所以OSPF位于网络层

2、EGP:外部网关协议,若源站和目的站处于不一样的自治系统中,当数据报传到一个自治系统的边界时,就须要使用
一种协议将路由信息传递到另外一个自治系统中,如EGP

TCP中为何第一个起始序列号是随机的

  1. 防止接受网络上粘滞的TCP包,若是都从0开始的话,极其容易接受以前断开链接发送的粘滞包。虽然能够采用每次TCP会话都使用一个UUID做为标记,可是考虑到每次都要携带UUID,比较浪费流量,因此就采用随机序列号的方法。
  2. 防止Hack猜想序列号,而后假装TCP报文,固然这种防护其实很弱。

反向代理&正向代理&透明代理

图解正向代理、反向代理、透明代理

TCP三次握手须要知道的细节点

一、三次握手,若是前两次有某一次失败,会从新从第一次开始,重来三次。
二、三次握手,若是最后一次失败,服务器并不会重传ack报文,而是直接发送RTS报文段,进入CLOSED状态。这样作的目的是为了防止SYN洪泛攻击。
三、发起链接时若是发生TCP SYN丢包,那么系统默认的重试间隔是3s,这期间不会返回错误码。
四、如何模拟tcp挥手失败?答案是iptables命令能够过滤数据包,丢弃全部的链接请求,导致客户端没法获得任何ack报文。

TCP与UDP的区别与适用场景

答:TCP协议栈自己是可靠,不会丢包,不会乱序,失败会重发。UDP须要应用层作协议来保证可靠性。视频能够用UDP。必须使用udp的场景:广播。

select函数能够检测网络异常吗?

答:不能够。当网络异常时,select函数能够检测到可读事件,这时候用read函数读取数据,会返回0.

send/recv(read/write)返回值大于0、等于0、小于0的区别

答:
recv:
阻塞与非阻塞recv返回值没有区分,都是 <0:出错,=0:链接关闭,>0接收到数据大小,
特别:非阻塞模式下返回 值 <0时而且(errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)的状况 下认为链接是正常的,继续接收。
只是阻塞模式下recv会阻塞着接收数据,非阻塞模式下若是没有数据会返回,不会阻塞着读,所以须要 循环读取。
write:
阻塞与非阻塞write返回值没有区分,都是 <0:出错,=0:链接关闭,>0发送数据大小,
特别:非阻塞模式下返回值 <0时而且 (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)的状况下认为链接是正常的, 继续发送。
只是阻塞模式下write会阻塞着发送数据,非阻塞模式下若是暂时没法发送数据会返回,不会阻塞着 write,所以须要循环发送。
read:
阻塞与非阻塞read返回值没有区分,都是 <0:出错,=0:链接关闭,>0接收到数据大小,
特别:非阻塞模式下返回 值 <0时而且(errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)的状况 下认为链接是正常的,继续接收。
只是阻塞模式下read会阻塞着接收数据,非阻塞模式下若是没有数据会返回,不会阻塞着读,所以须要 循环读取。
send:
阻塞与非阻塞send返回值没有区分,都是 <0:出错,=0:链接关闭,>0发送数据大小,
特别:非阻塞模式下返回值 <0时而且 (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)的状况下认为链接是正常的, 继续发送。
只是阻塞模式下send会阻塞着发送数据,非阻塞模式下若是暂时没法发送数据会返回,不会阻塞着 send,所以须要循环发送

socket选项TCP_NODELAY
答:通常来讲,应用层send函数不会马上把数据发出去,而是先给到网卡缓冲区。网卡缓冲区须要等待数据积累到必定量以后才会发送数据,这样会致使必定的延迟。
默认状况下,发送数据采用Nagle算法。这样虽然提升了网络吞吐量,可是实时性却下降了,在一些交互性很强的应用程序来讲是不容许的,使用TCP_NODELAY选项能够禁止Nagle算法,避免连续发包出现延迟,这对低延迟网络服务很重要。 此时,应用程序向内核递交的每一个数据包都会当即发送出去。须要注意的是,虽然禁止了Nagle 算法,但网络的传输仍然受到TCP确认延迟机制的影响。

如何检测对端已经关闭socket

答:根据ERRNO和recv结果进行判断
在UNIX/LINUX下,非阻塞模式SOCKET能够采用recv+MSG_PEEK的方式进行判断,其中MSG_PEEK保证了仅仅进行状态判断,而不影响数据接收
对于主动关闭的SOCKET, recv返回-1,并且errno被置为9(#define EBADF   9 /* Bad file number /)或104 (#define ECONNRESET 104 / Connection reset by peer /)
对于被动关闭的SOCKET,recv返回0,并且errno被置为11(#define EWOULDBLOCK EAGAIN /
Operation would block /)
 对正常的SOCKET, 若是有接收数据,则返回>0, 不然返回-1,并且errno被置为11(#define EWOULDBLOCK EAGAIN /
Operation would block */)
所以对于简单的状态判断(不过多考虑异常状况):
    recv返回>0,   正常

socket选项SO_SNDTIMEO和SO_RCVTIMEO

答:阻塞模式时须要设置超时时间,不然会卡死。

shutdown与优雅关闭

答:socket 多进程中的shutdown, close使用
当全部的数据操做结束之后,你能够调用close()函数来释放该socket,从而中止在该socket上的任何数据操做:close(sockfd);
你也能够调用shutdown()函数来关闭该socket。该函数容许你只中止在某个方向上的数据传输,而一个方向上的数据传输继续进行。如你能够关闭某socket的写操做而容许继续在该socket上接受数据,直至读入全部数据。
int shutdown(int sockfd,int how);
Sockfd是须要关闭的socket的描述符。参数 how容许为shutdown操做选择如下几种方式:
    SHUT_RD:关闭链接的读端。也就是该套接字再也不接受数据,任何当前在套接字接受缓冲区的数据将被丢弃。进程将不能对该套接字发出任何读操做。对TCP套接字该调用以后接受到的任何数据将被确认而后无声的丢弃掉。
    SHUT_WR:关闭链接的写端,进程不能在对此套接字发出写操做。
    SHUT_RDWR:至关于调用shutdown两次:首先是以SHUT_RD,而后以SHUT_WR。

服务器若是要主动关闭链接,能够这么执行:先关本地“写”端,等对方关闭后,再关本地“读”端。

服务器若是要被动关闭链接,能够这么执行:当read函数返回值是0时,先关本地“写”端,等对方关闭后,再关本地“读”端。

connect非阻塞

设置非阻塞, connect返回值判断是否链接创建, 失败加入select/poll/epoll, 关注可写事件,

A) 当链接创建成功时,套接口描述符变成 可写(链接创建时,写缓冲区空闲,因此可写)

B) 当链接创建出错时,套接口描述符变成 既可读又可写(因为有未决的错误,从而可读又可写)

进一步判断
方法一:
  A)若是链接创建是成功的,则经过getsockopt(sockfd,SOL_SOCKET,SO_ERROR,(char *)&error,&len) 获取的error 值将是0
  B)若是创建链接时遇到错误,则errno 的值是链接错误所对应的errno值,好比ECONNREFUSED,ETIMEDOUT 等
方法二:
  再次调用connect,相应返回失败,若是错误errno是EISCONN,表示socket链接已经创建,不然认为链接失败。

传送门