计算机网络

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

TCP

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

6个标志位:

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

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

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

TCP特点:

  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状态;

注意:上述是针对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开始依次连续编号(即1、2、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需要做哪些操作

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

HTTPS

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

HTTPS主要作用

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

HTTPS和HTTP的区别

  • 1、HTTPS是加密传输协议, HTTP是明文传输协议;
  • 2、HTTPS需要用到SSL证书, 而HTTP不用;
  • 3、HTTPS比HTTP更加安全, 对搜索引擎更友好, 利于SEO,
  • 4、HTTPS标准端口443, HTTP标准端口80;
  • 5、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的作用是反向代理服务器。同时也带来了一个好处,其中一台服务器万一挂了,只要还有其他服务器正常运行,就不会影响用户使用。
    1、什么是反向代理?
    客户端本来可以直接通过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这样的诊断程序。

类型8、代码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

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

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

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

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

  1. 防止接受网络上粘滞的TCP包,如果都从0开始的话,极其容易接受之前断开连接发送的粘滞包。虽然可以采用每次TCP会话都使用一个UUID作为标记,但是考虑到每次都要携带UUID,比较浪费流量,所以就采用随机序列号的方法。
  2. 防止Hack猜测序列号,然后伪装TCP报文,当然这种防御其实很弱。

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

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

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

1、三次握手,如果前两次有某一次失败,会重新从第一次开始,重来三次。
2、三次握手,如果最后一次失败,服务器并不会重传ack报文,而是直接发送RTS报文段,进入CLOSED状态。这样做的目的是为了防止SYN洪泛攻击。
3、发起连接时如果发生TCP SYN丢包,那么系统默认的重试间隔是3s,这期间不会返回错误码。
4、如何模拟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连接已经建立,否则认为连接失败。

传送门

posted @ 2019-08-29 07:27  张飘扬  阅读(653)  评论(0编辑  收藏  举报