计网一些关键知识点

目录

常用协议

应用层常用协议

ICMP(Internet Control Message Protocol)互联网控制报文协议

ARP( Address Resolution Protocol) 地址解析协议

RARP

DNS (Domain Name System)域名系统

RIP(Routing Information Protocol)路由信息协议

MTU  (Maximum Transmission Unit) 最大传输单元

MSS (Maximum Segment Size ) 最大报文段长度

TTL(Time to Live)生存时间

RTT(Round trip time)客户端到服务端往返所花时间         

ARQ(Automatic Repeat-reQuest)自动重传请求

五层协议

关于三次握手连接

为什么 TCP 每次建立连接时,初始化序列号都要不一样

第一次握手丢失了会发生什么

第二次握手丢失了会发生什么

第三次握手丢失了会发生什么

关于四次挥手

第一次挥手丢失了会发生什么

第二次挥手丢失了,会发生什么?

第三次挥手丢失了会发生什么

第四次挥手丢失了,会发生什么?

TCP挥手的第二次和第三次挥手

为什么 TCP 挥手需要四次

close与shutdown

什么情况会出现三次挥手

TCP 延迟确认机制

TCP 面向字节流,UDP 面向报文的理解

TCP 协议如何保证可靠传输?

IP TCP UDP头部信息

为什么 UDP 头部没有「首部长度」字段,而 TCP 头部有「首部长度」字段呢?

TCP粘包问题

Linux发送网络包的过程

HTTPS 对称加密 非对称加密

基本概念

关于对称加密和非对称加密

非对称加密的安全问题及解决办法

两者的优缺点

HTTPS使用对称加密和非对称加密结合的方式

LISTEN ACCEPT 全连接队列 半连接队列

常见状态码

IP地址与MAC地址     

子网划分与子网掩码

路由器相关

TCP 重传、滑动窗口、流量控制、拥塞控制

重传机制

超时重传

 快速重传

SACK 方法

滑动窗口 

流量控制

拥塞控制

拥塞控制和流量控制的区别

具体实现

拥塞窗口

如何判断出现拥塞

拥塞控制算法(慢启动;拥塞避免;拥塞发生;快速恢复)

HTTP3.0 HTTP2.0 HTTP1.1与HTTP1.0

HTTP的keep-alive和TCP的keepalive

TCP的keepalive

过程描述      

保活时间的设置

HTTP的keep-alive

什么情况下传输RST报文

Time Wait的作用

MSL的定义      

为什么需要 TIME_WAIT 状态

原因一:防止旧连接的数据包

原因二:保证连接正确关闭

TIME_WAIT 过长有什么危害?

客户端受端口资源限制:

服务端受系统资源限制:

在TCP正常挥手过程中,处于TIME_WAIT状态的连接,收到相同四元组的SYN后会发生什么

TIME_WAIT 状态快速回收

关于客户端与服务器的连接问题

杀死客户端和拔掉客户端网线之后都会发生什么

TCP 和 UDP 可以同时绑定相同的端口吗?

多个服务器进程可以绑定同一个端口吗?

重启 TCP 服务进程时,为什么会有“Address in use”的报错信息?

客户端的一个端口可以重复使用吗(即一个客户端可以和多个服务端建立连接吗)?

多个客户端可以 bind 同一个端口吗?

客户端 TCP 连接 TIME_WAIT 状态过多,会导致端口资源耗尽而无法建立新的连接吗?

如何解决客户端 TCP 连接 TIME_WAIT 过多,导致无法与同一个服务器建立连接的问题?

GET 和 POST 的区别

HTTP请求和响应报文的主要字段

cookie与session

session

两者区别

SYN攻击

一台机器能够使用的端口号上限


常用协议

应用层常用协议

ICMPInternet Control Message Protocol互联网控制报文协议

ping 是基于 ICMP 协议工作的。ICMP的主要功能包括:确认 IP 包是否成功送达目标地址、报告发送过程中 IP 包被废弃的原因和改善网络设置等,工作在网络层

ARP( Address Resolution Protocol) 地址解析协议

负责把目的主机的IP 地址解析成目的MAC地址。工作在网络层

在发包时:

  • 先根据IP地址,查询 ARP 缓存,如果其中已经保存了对方的 MAC 地址,就不需要发送 ARP 查询,直接使用 ARP 缓存中的地址。

  • 而当 ARP 缓存中不存在对方 MAC 地址时,则发送 ARP 广播查询。

RARP

反向ARP协议,根据MAC地址求得IP地址,工作在网络层

DNS (Domain Name System)域名系统

因特网上作为域名和IP地址相互映射的一个分布式数据库,能够使用户更方便的访问互联网,而不用去记住能够被机器直接读取的IP数串。

通过主机名,最终得到该主机名对应的IP地址的过程叫做域名解析(或主机名解析)。

将主机域名转换为ip地址,属于应用层协议,一般使用UDP传输, 端口号为53。(因为udp较为快速,并且客户端向DNS服务器查询域名,一般返回的内容都不超过512字节

DNS查询过程:

  • 请求一旦发起,若是chrome浏览器,先在浏览器找之前有没有缓存过的域名所对应的ip地址,有的 ,直接跳过dns解析了,若是没有,就会找硬盘的hosts文件,看看有没有,有的话,直接找到hosts 文件里面的ip地址
  • 如果本地的hosts文件没有能得到对应的ip地址,浏览器会发出一个dns请求到本地dns服务器本地 dns服务器一般都是网络接入服务器商提供,比如中国电信,中国移动等。
  • 查询输入的网址的DNS请求到达本地DNS服务器之后,本地DNS服务器会首先查询它的缓存记录, 如果缓存中有此条记录,就可以直接返回结果,此过程是递归的方式进行查询。如果没有,本地DNS 服务器还要向DNS根服务器进行查询。
  • 本地DNS服务器继续向域服务器发出请求,在这个例子中,请求的对象是.com域服务器。com域服务器收到请求之后,也不会直接返回域名和IP地址的对应关系,而是告诉本地DNS服务器,域名的解析服务器的地址。
  • 最后,本地DNS服务器向域名的解析服务器发出请求,这时就能收到一个域名和IP地址对应关系,本地DNS服务器不仅要把IP地址返回给用户电脑,还要把这个对应关系保存在缓存中,以备下次别的用户查询时,可以直接返回结果,加快网络访问。

RIP(Routing Information Protocol)路由信息协议

使用跳数(Hop Count)作为度量来衡量到达目的网络的距离。

MTU  (Maximum Transmission Unit) 最大传输单元

一个网络包的最大长度,以太网中一般为 1500 字节。

MSS (Maximum Segment Size ) 最大报文段长度

除去 IP 和 TCP 头部之后,一个网络包所能容纳的 TCP 数据的最大长度。

TCP3次握手建立连接时,连接双方都要互相告知自己期望接收到的MSS大小

对于以太网, MTU是1500字节 除去IP头部20字节和TCP头部20字节,MSS是1460字节

  • 如果一个大的TCP报文是被MTU分片,那么只有「第一个分片」才具有TCP头部,后面的分片则没有TCP头部,接收方IP层只有重组了这些分片,才会认为是一个TCP报文,那么丢失了其中一个分片,接收方IP层就不会把TCP报文丢给TCP层,那么就会等待对方超时重传这一整个TCP报文。
  • 如果一个大的TCP报文被MSS分片,那么所有「分片都具有TCP头部」,因为每个MSS分片的是具有TCP头部的TCP报文,那么其中一个MSS分片丢失,就只需要重传这一个分片就可以

TTL(Time to Live)生存时间

TTL是IP数据包在计算机网络中可以转发的最大跳数,是IP报文段中一个8bit的数据,TTL的作用是限制IP数据包在计算机网络中的存在的时间。TTL的最大值是255,TTL的一个推荐值是64。
TTL字段由IP数据包的发送者设置,在IP数据包从源到目的的整个转发路径上,每经过一个路由器,路由器都会修改这个TTL字段值,具体的做法是把该TTL的值减1,然后再将IP包转发出去。如果在IP包到达目的IP之前,TTL减少为0,路由器将会丢弃收到的TTL=0的IP包,并向IP包的发送者发送 ICMP time exceeded消息。
TTL的主要作用是避免IP包在网络中的无限循环和收发,节省了网络资源,并能使IP包的发送者能收到告警消息。

MSL(Maximum Segment Lifetime报文最大生存时间

它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。

TIME_WAIT  为 2MSL的意义

TIME_WAIT 等待 2 倍的 MSL,比较合理的解释是:网络中可能存在来自发送方的数据包,当这些发送方的数据包被接收方处理后又会向对方发送响应,所以一来一回需要等待 2 倍的时间

比如,如果被动关闭方没有收到断开连接的最后的 ACK 报文,就会触发超时重发 Fin 报文,另一方接收到 FIN 后,会重发 ACK 给被动关闭方, 一来一去正好 2 个 MSL。

2MSL的时间是从客户端接收到 FIN 后发送 ACK 开始计时的。如果在 TIME-WAIT 时间内,因为客户端的 ACK 没有传输到服务端,客户端又接收到了服务端重发的 FIN 报文,那么 2MSL 时间将重新计时

在 Linux 系统里 2MSL 默认是 60 秒,那么一个 MSL 也就是 30 秒。Linux 系统停留在 TIME_WAIT 的时间为固定的 60 秒

RTT(Round trip time)客户端到服务端往返所花时间         

RTT受网络传输拥塞的变化而变化。TCP含有动态估算RTT的算法。

ARQ(Automatic Repeat-reQuest)自动重传请求

它通过使用确认重传这两个机制,在不可靠服务的基础上实现可靠的信息传输,通常与滑动窗口结合使用。

自动重传请求分成为三种,停止等待(stop-and-wait)ARQ,回退n帧(go-back-n)ARQ,以及选择性重传(selective repeat)ARQ。后两种协议是滑动窗口技术请求重发技术的结合,由于窗口尺寸开到足够大时,帧在线路上可以连续地流动,因此又称后两种为连续ARQ协议

三者的区别在于对于出错的数据报文的处理机制不同。

  • 当发送窗口和接收窗口的大小都等于 1时,就是停止等待协议。
  • 当发送窗口大于1,接收窗口等于1时,就是回退N步协议。
  • 当发送窗口和接收窗口的大小均大于1时,就是选择重发协议
     

五层协议

  • 物理层:底层数据传输,如网线;网卡标准,数据在此叫做比特
  • 数据链路层:定义数据的基本格式,如何传输,如何标识;如网卡MAC地址。在两个相邻结点之间传送数据(在同一局域网内)时,数据链路层将网络层交下来的IP数据报组装成帧,数据在此叫做数据帧
  • 网络层:定义IP编址,定义路由功能;如不同设备的数据转发。网络层主要包括两个任务:负责为分组交换网上的不同主机提供通信服务选中合适的路由,使源主机运输层所传下来的分组,能够通过网络中的路由器找到目的主机,数据在此叫做数据包
  • 传输层:端到端传输数据的基本功能;如 TCPUDP。负责向两个主机中进程之间的通信提供服务数据在此叫做数据段(报文段)
  • 应用层:定义应用进程间通信和交互的规则直接为用户的应用进程(例如电子邮件、文件传输和终端仿真)提供服务。

关于三次握手连接

在三次握手的过程中,第三次握手是可以携带数据的,前两次握手不可以携带数据

三次握手是为了完整的确认客户端和服务器的接收数据能力。

三次握手.png

  • 第一次和第二次握手如果成功,证明客户端具有发送数据的能力,服务器具有接收数据的能力; 
  • 第二次和第三次握手如果成功,证明客户端具有接收数据的能力,服务器具有发送数据的能力;

第三次握手 的一种情景如下(这也是区分为什么要三次握手连接而不是两次握手连接)

客户端连续发送多次 SYN 建立连接的报文,在网络拥堵等情况下:

  • 一个「旧 SYN 报文」比「最新的 SYN 」 报文早到达了服务端;

  • 那么此时服务端就会回一个 SYN + ACK 报文给客户端;

  • 客户端收到后可以根据自身的上下文,判断这是一个历史连接(序列号过期或超时),那么客户端就会在第三次握手时,发送RST报文,表示中止这一次连接。

如果是两次握手连接,就不能判断当前连接是否是历史连接,三次握手则可以在客户端(发送方)准备发送第三次报文时,客户端因有足够的上下文来判断当前连接是否是历史连接:

  • 如果是历史连接(序列号过期或超时),则第三次握手发送的报文是 RST 报文,以此中止历史连接;

  • 如果不是历史连接,则第三次发送的报文是 ACK 报文,通信双方就会成功建立连接;

所以, TCP 使用三次握手建立连接的最主要原因是防止历史连接初始化了连接。

为什么 TCP 每次建立连接时,初始化序列号都要不一样

主要原因是为了防止历史报文被下一个相同四元组的连接接收。

在正常情况下,因为有2MSL等待时间的存在,历史报文对于后续的连接并不会有影响。但是如果TCP连接没有正常关闭,历史报文仍存在于网络中的话,就会有影响。假设客户端和服务端的初始化序列号都是从 0 开始,如下:

上图中客户端和服务器一共建立了两次连接,

  • 客户端和服务端建立的第一个 TCP 连接中,有一个报文被网络阻塞了,然后超时重传了这个数据包,而此时服务端设备断电重启了,之前与客户端建立的连接就消失了,于是在收到客户端的数据包的时候就会发送 RST 报文。
  • 紧接着,客户端又与服务端建立了与上一个四元组相同的第二个连接,在新连接建立完成后,上一个连接中被网络阻塞的数据包正好抵达了服务端,刚好该数据包的序列号正好是在服务端的接收窗口内,所以该数据包会被服务端正常接收,就会造成数据错乱。

第一次握手丢失了会发生什么

客户端发送的SYN报文丢失之后,如果客户端迟迟收不到服务端的 SYN-ACK 报文(第二次握手),就会触发「超时重传」机制,重传 SYN 报文。

当客户端在 1 秒后没收到服务端的 SYN-ACK 报文后,客户端就会再次重发 SYN 报文,在 Linux 里,客户端的 SYN 报文最大重传次数由 tcp_syn_retries内核参数控制,这个参数是可以自定义的,默认值一般是 5。

通常,第一次超时重传是在 1 秒后,第二次超时重传是在 2 秒,第三次超时重传是在 4 秒后,第四次超时重传是在 8 秒后,第五次是在超时重传 16 秒后。每次超时的时间是上一次的 2 倍

当第五次超时重传后,会继续等待 32 秒,如果服务端仍然没有回应 ACK,客户端就不再发送 SYN 包,然后断开 TCP 连接。

第二次握手丢失了会发生什么

当服务端收到客户端的第一次握手后,就会回 SYN-ACK 报文给客户端,此时是第二次握手

第二次握手的 SYN-ACK 报文其实有两个目的 :

  • 第二次握手里的 ACK, 是对第一次握手的确认报文;

  • 第二次握手里的 SYN,是服务端发起建立 TCP 连接的报文;

因为第二次握手报文里是包含对客户端的第一次握手的 ACK 确认报文,所以,如果客户端迟迟没有收到第二次握手中的ACK报文,那么客户端就会认为自己的 SYN 报文(第一次握手)丢失了,于是客户端就会触发超时重传机制,重传 SYN 报文(第一次握手报文)

然后,因为第二次握手中包含服务端的 SYN 报文,当客户端收到并给服务端发送 ACK 确认报文(第三次握手)之后,服务端才会认为该 SYN 报文被客户端收到了。

如果第二次握手报文丢失导致SYN报文丢失了,服务端就收不到第三次握手,于是服务端这边会触发超时重传机制,重传 SYN-ACK 报文(第二次握手报文)

因此,当第二次握手丢失了,客户端和服务端都会重传

第三次握手丢失了会发生什么

客户端收到服务端的 SYN-ACK 报文后,就会给服务端回一个 ACK 报文,也就是第三次握手。

因为这个第三次握手的 ACK 是对第二次握手的 SYN 的确认报文,所以当第三次握手丢失了,服务端那一方迟迟收不到这个确认报文,就会触发超时重传机制,重传 SYN-ACK 报文,直到收到第三次握手,或者达到最大重传次数。

关于四次挥手

image.png

第一次挥手丢失了会发生什么

当客户端(主动关闭方)调用 close 函数后,就会向服务端发送 FIN 报文,试图与服务端断开连接,此时客户端的连接进入到FIN_WAIT_1状态。

正常情况下,如果能及时收到服务端(被动关闭方)的 ACK,则会很快变为FIN_WAIT_2状态。

如果第一次挥手丢失了,客户端迟迟收不到被动方的 ACK ,就会触发超时重传机制,重传 FIN 报文,重发次数由 tcp_orphan_retries 参数控制。

当客户端重传 FIN 报文的次数超过 tcp_orphan_retries 后,就不再发送 FIN 报文,则会在等待一段时间(时间为上一次超时时间的 2 倍),如果还是没能收到第二次挥手,那么直接进入到close状态。

第二次挥手丢失了,会发生什么?

当服务端收到客户端的第一次挥手后,就会先回一个 ACK 确认报文,此时服务端的连接进入到CLOSE_WAIT 状态。

注意:ACK 报文是不会重传的。所以如果服务端的第二次挥手丢失了,客户端就会触发超时重传机制,重传 FIN 报文,直到收到服务端的第二次挥手,或者达到最大的重传次数。

当客户端收到第二次挥手后,需要等服务端发送第三次挥手的 FIN 报文。

  • 对于调用 close 函数关闭的连接,由于无法再发送和接收数据,所以 FIN_WAIT2 状态不可以持续太久,而 tcp_fin_timeout 控制了这个状态下连接的持续时长,默认值是 60 秒。如果在 60 秒后还没有收到 FIN 报文,客户端(主动关闭方)的连接就会直接关闭
  • 如果主动关闭方使用 shutdown 函数关闭连接,只关闭发送方向,而接收方向并没有关闭,意味着主动关闭方还是可以接收数据的。此时,如果主动关闭方一直没收到第三次挥手,那么主动关闭方的连接将会一直处于 FIN_WAIT2 状态(tcp_fin_timeout 无法控制 shutdown 关闭的连接)。

第三次挥手丢失了会发生什么

当服务端收到客户端的第一条挥手 FIN 报文后,内核会自动回复 ACK,同时连接处于 CLOSE_WAIT 状态。此时,内核没有权利替代进程关闭连接,必须由进程主动调用 close 函数来触发服务端发送 FIN 报文,同时连接进入 LAST_ACK 状态,等待客户端返回 ACK 来确认连接关闭。

如果迟迟收不到这个 ACK,服务端就会重发 FIN 报文,重发次数仍然由 tcp_orphan_retries 参数控制,这与客户端重发 FIN 报文的重传次数控制方式是一样的。

当服务端重传第三次挥手报文的次数达到了 tcp_orphan_retries 设定值后,达到了重传最大次数,于是再等待一段时间(时间为上一次超时时间的 2 倍),如果还是没能收到客户端的第四次挥手(ACK报文),那么服务端就会断开连接

客户端如果是通过 close 函数关闭连接,处于 FIN_WAIT_2 的状态有时长限制,如果 tcp_fin_timeout 时间内还是没能收到服务端的第三次挥手(FIN 报文),那么客户端也就会断开连接。

第四次挥手丢失了,会发生什么?

当客户端收到服务端的第三次挥手的 FIN 报文后,就会回 ACK 报文,也就是第四次挥手,此时客户端连接进入 TIME_WAIT 状态并持续 2MSL 后会进入关闭状态。服务端在没有收到 ACK 报文前,还是处于 LAST_ACK 状态。

如果第四次挥手的 ACK 报文没有到达服务端,服务端就会重发 FIN 报文,重发次数仍然由 tcp_orphan_retries 参数控制。

当服务端重传第三次挥手报文达到 tcp_orphan_retries 时,达到了最大重传次数,于是再等待一段时间(时间为上一次超时时间的 2 倍),如果还是没能收到客户端的第四次挥手(ACK 报文),那么服务端就会断开连接。

客户端在收到第三次挥手后,就会进入 TIME_WAIT 状态,开启时长为 2MSL 的定时器,如果途中再次收到第三次挥手(FIN 报文)后,就会重置定时器,当等待 2MSL 时长后,客户端就会断开连接。

TCP挥手的第二次和第三次挥手

为什么 TCP 挥手需要四次

服务器收到客户端的第一次挥手 FIN 报文时,内核会马上回一个 ACK 应答报文,但是服务端应用程序可能还有数据要发送,所以并不能马上发送 FIN 报文,而是将发送 FIN 报文的控制权交给服务端应用程序

  • 如果服务端应用程序有数据要发送的话,就发完数据后,才调用关闭连接的函数;

  • 如果服务端应用程序没有数据要发送的话,可以直接调用关闭连接的函数,

从上面过程可知,是否要发送第三次挥手的控制权不在内核,而是在被动关闭方(服务端)的应用程序,因为应用程序可能还有数据要发送,由应用程序决定什么时候调用关闭连接的函数,当调用了关闭连接的函数,内核就会发送 FIN 报文了,所以服务端的 ACK 和 FIN 一般都会分开发送。也就产生了四次挥手。

close与shutdown

关闭的连接的函数有两种函数:

  • close 函数,同时关闭 socket 套接字上的发送方向和读取方向,socket 不再有发送和接收数据的能力

  • shutdown 函数,可以指定 socket 只关闭发送方向而不关闭读取方向,socket 不再有发送数据的能力,但是还是具有接收数据的能力;

如果客户端是用 close 函数来关闭连接,那么在四次挥手过程中,如果收到了服务端发送的数据,由于客户端已经不再具有发送和接收数据的能力,所以客户端的内核会直接回 RST 报文给服务端,然后内核会释放连接,这时就不会经历完整的 TCP 四次挥手,所以我们常说,调用 close 是粗暴的关闭

当服务端收到 RST 后,内核就会释放连接,当服务端应用程序再次发起读操作或者写操作时,就能感知到连接已经被释放了

  • 如果是读操作,则会返回 RST 的报错,也就是我们常见的Connection reset by peer。

  • 如果是写操作,那么程序会产生 SIGPIPE 信号,应用层代码可以捕获并处理信号,如果不处理,则默认情况下进程会终止,异常退出。

shutdown 函数因为可以指定只关闭发送方向而不关闭读取方向,所以即使在 TCP 四次挥手过程中,如果收到了服务端发送的数据,客户端也是可以正常读取到该数据的,然后就会经历完整的 TCP 四次挥手,所以我们常说,调用 shutdown 是优雅的关闭

什么情况会出现三次挥手

当被动关闭方(服务端)在 TCP 挥手过程中,没有数据要发送并且开启了 TCP 延迟确认机制,那么第二和第三次挥手就会合并传输,这样就出现了三次挥手

TCP 延迟确认机制

TCP 延迟确认机制是默认开启的,延迟等待的时间是在 Linux 内核中定义的

TCP 延迟确认的策略:

  • 有响应数据要发送时,ACK 会随着响应数据一起立刻发送给对方

  • 没有响应数据要发送时,ACK 将会延迟一段时间,以等待是否有响应数据可以一起发送,如果时间耗尽并且在此期间没有其他数据发送,那么就会发送ACK报文。

  • 如果在延迟等待发送 ACK 期间,对方的第二个重复数据报文又到达了,这时就会立刻发送 ACK

TCP 面向字节流,UDP 面向报文的理解

这两者的不同是因为操作系统对 TCP 和 UDP 协议的发送方的机制不同,也就是问题原因在发送方。

当用户消息通过 UDP 协议传输时,操作系统不会对消息进行拆分,在组装好 UDP 头部后就交给网络层来处理,所以发出去的 UDP 报文中的数据部分就是完整的用户消息,也就是每个 UDP 报文就是一个用户消息的边界,这样接收方在接收到 UDP 报文后,读一个 UDP 报文就能读取到完整的用户消息。

操作系统在收到 UDP 报文后,会将其插入到队列里,队列里的每一个元素就是一个 UDP 报文,这样当用户调用 recvfrom() 系统调用读数据的时候,就会从队列里取出一个数据,然后从内核里拷贝给用户缓冲区。


 当用户消息通过 TCP 协议传输时,一条完整的消息可能会被操作系统分组成多个的 TCP 报文进行传输。

在发送端,当我们调用 send 函数完成数据“发送”以后,数据并没有被真正从网络上发送出去,只是从应用程序拷贝到了操作系统内核协议栈中。至于什么时候真正被发送,取决于发送窗口、拥塞窗口以及当前发送缓冲区的大小等条件。也就是说,我们不能认为每次 send 调用发送的数据,都会作为一个整体完整地消息被发送出去。

当接收方收到报文消息时,如果不知道发送方发送的消息的长度,也就是不知道消息的边界时,是无法读出一个有效的用户消息的。

当两个消息的某个部分内容被分到同一个 TCP 报文时,就是我们常说的 TCP 粘包问题,这时接收方不知道消息的边界的话,是无法读出有效的消息。如下:

TCP 协议如何保证可靠传输?

  • 确认和重传:接收方收到报文就会确认,发送方发送一段时间后没有收到确认就会重传。
  • 数据校验TCP报文头有校验和,用于校验报文是否损坏。
  • 数据合理分片和排序tcp会按最大传输单元(MTU)合理分片,接收方会缓存未按序到达的数据,重新 排序后交给应用层。而采用UDP协议的话,当IP数据报大于1500字节时,发送方的IP层就需要分片,把数据报分成若干片。由接收方IP层进行数据报的重组。由于 UDP的特性,某一片数据丢失时,接收方便无法重组数据报,导致丢弃整个UDP数据报。
  • 流量控制:当接收方来不及处理发送方的数据,能通过滑动窗口,提示发送方降低发送的速率,防止包丢失。
  • 拥塞控制:当网络拥塞时,通过拥塞窗口,减少数据的发送,防止包丢失。

IP TCP UDP头部信息

IP头部

重点关注:

8位TTL段,字段设置了数据报可以经过的最多路由器数。它指定了数据报的生存时间。 TTL的初始值由源主机设置(通常为32或64),一旦经过一个处理它的路由器,它的值就减去1。当该字段的值为0时,数据报就被丢弃,并发送ICMP报文通知源主机。

16位总长度段:总长度字段是指整个IP数据报的长度,以字节为单位。由于该字段长16比特,所以IP数据报最长可达65535字节。

tcp头部

在这里插入图片描述

源端口和目的端口(16bit):用于寻找发端和收端的应用程序。这两个值加上IP首部的源端IP和目的端IP唯一确定一个TCP连接;

序号32bit):传输方向上字节流的字节编号。初始时序号会被设置一个随机的初始值(ISN)

确认号 32bit ):接收方对发送方 TCP 报文段的下一次响应序号
首部长 4bit ):标识首部有多少个 4 字节 * 首部长,最大为 15 ,即 60 字节。
标志位 6bit ):
  • URG:标志紧急指针是否有效。
  • ACK:标志确认号是否有效(确认报文段)。用于解决丢包问题。
  • PSH:提示接收端立即从缓冲读走数据。
  • RST:表示要求对方重新建立连接(复位报文段)。
  • SYN:表示请求建立一个连接(连接报文段)。
  • FIN:表示关闭连接(断开报文段)。
窗口 16bit):接收窗口。用于告知对方(发送方)本方的缓冲还能接收多少字节数据。用于解决流控。
校验和 16bit ):接收端用 CRC 检验整个报文段有无损坏。
udp头部: 

在这里插入图片描述


为什么 UDP 头部没有「首部长度」字段,而 TCP 头部有「首部长度」字段呢?

原因是 TCP 有可变长的「选项」字段,而 UDP 头部长度则是不会变化的,无需多一个字段去记录 UDP 的首部长度。


TCP粘包问题

在TCP的socket编程中,发送端和接收端都有成对的socket。发送端为了将多个发往接收端的包,更加高效的的发给接收端,于是采用了优化算法(Nagle算法),将多次间隔较小、数据量较小的数据,合并成一个数据量大的数据块,然后进行封包。那么这样一来,接收端就必须使用高效科学的拆包机制来分辨这些数据。

什么是TCP粘包问题?

TCP粘包就是指发送方发送的若干包数据到达接收方时粘成了一包,从接收缓冲区来看,后一包数据的头紧接着前一包数据的尾,出现粘包的原因是多方面的,可能是来自发送方,也可能是来自接收方。

造成TCP粘包的原因
(1)发送方原因

TCP默认使用Nagle算法(主要作用:减少网络中报文段的数量),而Nagle算法主要做两件事:

  • 只有上一个分组得到确认,才会发送下一个分组
  • 收集多个小分组,在一个确认到来时一起发送

Nagle算法造成了发送方可能会出现粘包问题

(2)接收方原因

TCP接收到数据包时,并不会马上交到应用层进行处理,或者说应用层并不会立即处理。实际上,TCP将接收到的数据包保存在接收缓存里,然后应用程序主动从缓存读取收到的分组。这样一来,如果TCP接收数据包到缓存的速度大于应用程序从缓存中读取数据包的速度,多个包就会被缓存,应用程序就有可能读取到多个首尾相接粘到一起的包。

什么时候需要处理粘包现象?

如果发送方发送的多组数据本来就是同一块数据的不同部分,比如说一个文件被分成多个部分发送,这时当然不需要处理粘包现象
如果多个分组毫不相干,甚至是并列关系,那么这个时候就一定要处理粘包现象了

如何处理粘包现象?
(1)发送方

对于发送方造成的粘包问题,可以通过关闭Nagle算法来解决,使用TCP_NODELAY选项来关闭算法。

(2)接收方

接收方没有办法来处理粘包现象,只能将问题交给应用层来处理。

(2)应用层

应用层的解决办法简单可行,不仅能解决接收方的粘包问题,还可以解决发送方的粘包问题。

解决办法

  • 循环处理:应用程序从接收缓存中读取分组时,读完一条数据,就应该循环读取下一条数据,直到所有数据都被处理完成,但是如何判断每条数据的长度呢?
  • 格式化数据:每条数据有固定的格式(开始符,结束符),这种方法简单易行,但是选择开始符和结束符时一定要确保每条数据的内部不包含开始符和结束符。     
  • 发送长度:发送每条数据时,将数据的长度一并发送,例如规定数据的前4位是数据的长度,应用层在处理时可以根据长度来判断每个分组的开始和结束位置。

UDP会不会产生粘包问题呢?

TCP为了保证可靠传输并减少额外的开销(每次发包都要验证),采用了基于流的传输,基于流的传输不认为消息是一条一条的,是无保护消息边界的(保护消息边界:指传输协议把数据当做一条独立的消息在网上传输,接收端一次只能接受一条独立的消息)。

UDP则是面向消息传输的,是有保护消息边界的,接收方一次只接受一条独立的信息,所以不存在粘包问题。

举个例子:有三个数据包,大小分别为2k、4k、6k,如果采用UDP发送的话,不管接受方的接收缓存有多大,我们必须要进行至少三次以上的发送才能把数据包发送完,但是使用TCP协议发送的话,我们只需要接受方的接收缓存有12k的大小,就可以一次把这3个数据包全部发送完毕。

Linux发送网络包的过程

  •  首先,应用程序会调用 Socket 发送数据包的接口,由于这个是系统调用,所以会从用户态陷入到内核态中的 Socket 层,Socket 层会将应用层数据拷贝到 Socket 发送缓冲区中。
  • 接下来,网络协议栈从 Socket 发送缓冲区中取出数据包,并按照 TCP/IP 协议栈从上到下逐层处理。
  • 如果使用的是 TCP 传输协议发送数据,那么会在传输层增加 TCP 包头,然后交给网络层,网络层会给数据包增加 IP 包,然后通过查询路由表确认下一跳的 IP,并按照 MTU 大小进行分片。
  • 分片后的网络包,就会被送到网络接口层,在这里会通过 ARP 协议获得下一跳的 MAC 地址,然后增加帧头和帧尾,放到发包队列中。
  • 这一些准备好后,会触发软中断告诉网卡驱动程序,这里有新的网络包需要发送,最后驱动程序通过 DMA,从发包队列中读取网络包,将其放入到硬件网卡的队列中,随后物理网卡再将它发送出去。

上面加TCP,IP报文头操作都是在内核态状态下,由操作系统完成的。

HTTPS 对称加密 非对称加密

基本概念

HTTP 由于是明文传输,所谓的明文,就是说客户端与服务端通信的信息都是肉眼可见的,随意使用一个抓包工具都可以截获通信的内容。

所以安全上存在以下三个风险:

  • 窃听风险,比如通信链路上可以获取通信内容

  • 篡改风险,比如强制植入垃圾广告

  • 冒充风险,比如冒充淘宝网站

HTTPS 在 HTTP 与 TCP 层之间加入了 TLS 协议,来解决上述的风险。

两个协议的默认端口号也有所不同,HTTP是80,HTTPS是443

TLS 协议解决 HTTP 的风险方式?

  • 信息加密:HTTP 交互信息是被加密的,第三方就无法被窃取;

  • 校验机制:校验信息传输过程中是否有被第三方篡改过,如果被篡改过,则会有警告提示;

  • 身份证书:证明淘宝是真的淘宝网;

首先明确,HTTPS协议的连接顺序为TCP三次握手连接——>TLS握手连接——>HTTP数据传输 

公钥用来给数据加密;私钥用来给数据解密

公钥是对外开放的,任何终端都能获得并且用它加密报文发送给目标端,但私钥只有目标端拥有,目标端可以用私钥对发送对来的数据进行解密并筛选

关于对称加密和非对称加密

对称加密指的是客户端和服务器用同一个密钥加密与解密,但是如果密钥泄漏了,通讯就不安全了。

在这里插入图片描述

所以出现了非对称加密,非对称加密是客户端和服务器端双方一次都产生一对密钥, 也称为密钥对,也就是一个公钥和一个私钥,这两个密钥 ,一个用来加密,一个用来解密。 通讯的双方分别把公钥给对方,留存私钥。发送信息给对方是使用对方的公钥对信息加密,对方使用自己的私钥解密

在这里插入图片描述

非对称加密的安全问题及解决办法

在使用非对称加密算法通讯的过程中,有两个问题:

从客户端发送给服务器的数据可能被黑客在中途截获,并且将数据修改后再发送给客户端。因此需要验证消息是否被修改。所以出现了数字签名。数字签名就是使用密钥加密过的的消息摘要。

消息摘要是对一个数据块的数字指纹,这个数字指纹是一串固定长度的字符串,其有一个特性就是不同的消息的消息摘要不一样, 也就是如果消息被更改了, 消息摘要肯定不一样。

在上面的例子中, 客户端使用自己的私钥对消息摘要进行加密,连同服务器的公钥加密的正文一起发送给服务器;服务器使用客户端的公钥解密消息摘要, 使用自己的私钥解密得到正文,再运算出正文的摘要, 将这两个摘要进行对比,如果这两个摘要相同说明这个正文没有被篡改过。


另一个问题就是黑客有可能传递一个假的信息。 因为公钥是公开的,黑客可以获取某一方的公钥,如果黑客替换了发送者的公钥,则其就可以用自己的私钥发送签名的内容给接收者。接收者使用错误的公钥验证黑客发送的内容却是可以通过校验的,这样接收者就接收了错误的内容。

关键字在于如何确保公钥是合法的

此时需要引入CAB颁发的数字证书,证书包含的信息如下:

640?wx_fmt=png

 具体的过程如下

  • 服务端首先把自己的公钥KEY发给证书颁发机构,向证书颁发机构申请证书。 
  • 证书颁发机构自己也有一对公钥私钥。机构利用自己的私钥来加密服务器公钥KEY,
  1. 首先 CA 会把持有者的公钥、用途、颁发者、有效时间等信息打成一个包,然后对这些信息进行 Hash 计算,得到一个 Hash 值;
  2. 然后 CA 会使用自己的私钥将该 Hash 值加密,生成数字签名,并将其添加在文件证书上形成数字证书;证书制作完成后,机构把证书发送给服务端。
  • 当客户端向服务器请求通信的时候,服务器不再直接返回自己的公钥,而是把自己申请的证书返回给客户端。
  • 客户端收到证书以后,要做的第一件事情是验证证书的真伪。需要说明的是,各大浏览器和操作系统已经维护了所有权威证书机构的名称和公钥
  1. 首先客户端会使用同样的 Hash 算法获取该证书的 Hash 值 H1;
  2. 通常浏览器和操作系统中集成了 CA 的公钥信息,浏览器收到证书后从本地找到对应的机构公钥,使用公钥解密数字签名内容,得到一个 Hash 值 H2 ;
  3. 最后比较 H1 和 H2,如果值相同,则为可信赖的证书,否则则认为证书不可信。
  • 验证成功后,客户端就可以放心地再次利用机构公钥,解密出服务端的公钥KEY。

两者的优缺点

对称加密

优点:

  • 速度快,对称性加密通常在消息发送方需要加密大量数据时使用,算法公开、计算量小、加密速度快、加密效率高。

缺点:

  • 在数据传送前,发送方和接收方必须商定好秘钥,然后 使双方都能保存好秘钥。其次如果一方的秘钥被泄露,那么加密信息也就不安全了。
  • 另外,每对用户每次使用对称加密算法时,都需要使用其他人不知道的唯一密钥,这会使得收、发双方所拥有的钥匙数量巨大,密钥管理成为双方的负担。

非对称加密

  • 优点:安全性更高,公钥是公开的,秘钥是自己保存的,不需要将私钥给别人。
  • 缺点:加密和解密花费时间长、速度慢,只适合对少量数据进行加密。

HTTPS使用对称加密和非对称加密结合的方式

对称加密和非对称加密各有优缺点,所以HTTPS中采用对称加密和对称加密结合的方式来进行数据传输。 简单来说,就是使用非对称加密的方式来生成客户端与服务器端的一个公共密钥,后续从这个密钥进行对称加密通讯。

备注:机构颁发的数字证书内容主要有两个:数字签名和服务器公钥

LISTEN ACCEPT 全连接队列 半连接队列

在 TCP 三次握手的时候,Linux 内核会维护两个队列,分别是:

  • 半连接队列,也称 SYN 队列

  • 全连接队列,也称 accepet 队列

服务端收到客户端发起的 SYN 请求后,内核会把该连接存储到半连接队列,并向客户端响应 SYN+ACK,接着客户端会返回 ACK,服务端收到第三次握手的 ACK 后,内核会把连接从半连接队列移除,然后创建新的完全的连接,并将其添加到 accept 队列,等待进程调用 accept 函数时把连接取出来。

不管是半连接队列还是全连接队列,都有最大长度限制,超过限制时,后续的连接内核会直接丢弃,或返回 RST 包。

所以,对应用服务器来说,如果全连接队列中有已经建立好的TCP连接,却没有及时的把它取出来,这样,一旦导致两个队列满了后,就会使客户端不能再建立新连接,引发严重问题。

常见状态码

状态码的类别:

类别原因短语
1XXInformational(信息性状态码) 接受的请求正在处理
2XXSuccess(成功状态码) 请求正常处理完毕
3XX Redirection(重定向状态码)需要进行附加操作以完成请求
4XX Client Error(客户端错误状态码)服务器无法处理请求
5XX Server Error(服务器错误状态码)服务器处理请求出错

2XX——表明请求被正常处理了

  • 200 OK:请求已正常处理。
  • 204 No Content:请求处理成功,但没有任何资源可以返回给客户端,一般在只需要从客户端往服务器发送信息,而对客户端不需要发送新信息内容的情况下使用。
  • 206 Partial Content:是对资源某一部分的请求,该状态码表示客户端进行了范围请求,而服务器成功执行了这部分的GET请求。响应报文中包含由Content-Range指定范围的实体内容。

3XX——表明浏览器需要执行某些特殊的处理以正确处理请求

  • 301 Moved Permanently:资源的uri已更新,你也更新下你的书签引用吧。永久性重定向,请求的资源已经被分配了新的URI,以后应使用资源现在所指的URI。
  • 302 Found:资源的URI已临时定位到其他位置了,姑且算你已经知道了这个情况了。临时性重定向。和301相似,但302代表的资源不是永久性移动,只是临时性性质的。换句话说,已移动的资源对应的URI将来还有可能发生改变。
  • 303 See Other:资源的URI已更新,你是否能临时按新的URI访问。该状态码表示由于请求对应的资源存在着另一个URL,应使用GET方法定向获取请求的资源。303状态码和302状态码有着相同的功能,但303状态码明确表示客户端应当采用GET方法获取资源,这点与302状态码有区别。
  • 当301,302,303响应状态码返回时,几乎所有的浏览器都会把POST改成GET,并删除请求报文内的主体,之后请求会自动再次发送。
  • 304 Not Modified:资源已找到,但未符合条件请求。该状态码表示客户端发送附带条件的请求时(采用GET方法的请求报文中包含If-Match,If-Modified-Since,If-None-Match,If-Range,If-Unmodified-Since中任一首部)服务端允许请求访问资源,但因发生请求未满足条件的情况后,直接返回304.。
  • 307 Temporary Redirect:临时重定向。与302有相同的含义。

4XX——表明客户端是发生错误的原因所在。

  • 400 Bad Request:服务器端无法理解客户端发送的请求,请求报文中可能存在语法错误。
  • 401 Unauthorized:该状态码表示发送的请求需要有通过HTTP认证(BASIC认证,DIGEST认证)的认证信息。
  • 403 Forbidden:不允许访问那个资源。该状态码表明对请求资源的访问被服务器拒绝了。(权限,未授权IP等)
  • 404 Not Found:服务器上没有请求的资源。路径错误等。

5XX——服务器本身发生错误

  • 500 Internal Server Error:貌似内部资源出故障了。该状态码表明服务器端在执行请求时发生了错误。也有可能是web应用存在bug或某些临时故障。
  • 503 Service Unavailable:抱歉,我现在正在忙着。该状态码表明服务器暂时处于超负载或正在停机维护,现在无法处理请求。

IP地址与MAC地址     

MAC地址(也叫物理地址或硬件地址)。组成网络的各个站点都具有一个机器可以识别的地址,称为该站点的物理地址,在出厂时已经固化在网卡上,是不会变的,电脑上的MAC地址是唯一的。物理地址是数据链路层和物理层使用的地址。    

但是MAC地址的数量庞大,如果只凭一个MAC地址来寻找主机的话,效率极低。所以需要引入IP地址,对整个网络进行更为细小的子网划分,加快查找的速度。

IP地址作用在网络层及以上,它的作用空间是整个地球的网络。一个IP地址在整个互联网范围里是唯一的。IP地址由网络地址和主机地址两部分组成,分配给这两部分的位数随地址类(A类、B类、C类等)的不同而不同。网络地址用于路由选择,而主机地址用于在网络或子网内部寻找一个单独的主机。

IP层抽象的互联网屏蔽了下层很复杂的细节,在抽象的网络层上讨论问题,就能够使用统一的、抽象的 IP 地址研究主机和主机或主机和路由器之间的通信。通俗一点来说就是有了IP地址,就只通过路由器找到目的主机,屏蔽了下层网络的异构型,由MAC地址完成下层的实际转发。   

子网划分与子网掩码

原来的二级IP地址的空间利用率不高,会浪费掉空闲的IP资源 ,所以引入了三级网络。

划分子网只是将IP地址的主机号这部分进行了再划分,不会改变IP地址中原来的网络号,这些子网对外仍然表现为一个网络。

而子网掩码就是用来判断当前IP地址所在子网的一个工具。将IP地址与子网掩码相与,分离出IP地址中的网络地址和主机地址,就能判断该IP地址是在本地网络上,还是在远程网络网上。

子网掩码中,全为1的部分长度是可变的,不同的长度决定了当前子网所能容纳的主机的数量。

路由器在于其他路由器交换路由信息时,必须将自己所在网络(或者子网)的子网掩码告诉其他路由器。在路由器的路由表中的每一个项目,除了要给出目的网络地址之外,还需要给出目的网络的子网掩码。如果一个路由器连接在两个子网上,它就拥有两个网络地址和子网掩码。

路由器相关

路由器工作在网络层。路由器同时拥有IP和MAC地址

TCP 重传、滑动窗口、流量控制、拥塞控制

总的来说,TCP 是通过序列号、确认应答、重发控制、连接管理以及窗口控制等机制实现可靠性传输的

重传机制

TCP 实现可靠传输的方式之一,是通过序列号与确认应答。在 TCP 中,当发送端的数据到达接收主机时,接收端主机会返回一个确认应答消息,表示已收到消息。

 TCP 针对数据包丢失的情况,会用重传机制解决。如超时重传,快重传。

超时重传

重传机制的其中一个方式,就是在发送数据时,设定一个定时器,当超过指定的时间后,没有收到对方的 ACK 确认应答报文,就会重发该数据,也就是我们常说的超时重传

TCP 会在以下两种情况发生超时重传:

  • 数据包丢失

  • 确认应答丢失

 快速重传

TCP 还有另外一种快速重传(Fast Retransmit)机制它不以时间为驱动,而是以数据驱动重传。机制如下:

快速重传的工作方式是当收到三个相同的 ACK 报文时,会在定时器过期之前,重传丢失的报文段

快速重传机制只解决了一个问题,就是超时时间的问题,但是它依然面临着另外一个问题。就是重传的时候,是重传之前的一个,还是重传所有的问题。

比如对于上面的例子,是重传 Seq2 呢?还是重传 Seq2、Seq3、Seq4、Seq5 呢?因为有可能后面的seq3,seq4,seq5也有可能发生丢失,但是接收方发送的还是Ack2报文。也就是说发送端并不清楚这连续的三个 Ack 2 是谁传回来的。

为了解决不知道该重传哪些 TCP 报文,于是就有 SACK 方法。

SACK 方法

SACK( Selective Acknowledgment 选择性确认)。

这种方式需要在 TCP 头部「选项」字段里加一个 SACK 的东西,它可以将缓存的地图发送给发送方,这样发送方就可以知道哪些数据收到了,哪些数据没收到,知道了这些信息,就可以只重传丢失的数据

如下图,发送方收到了三次同样的 ACK 确认报文,于是就会触发快速重发机制,通过 SACK 信息发现只有 200~299 这段数据丢失,则重发时,就只选择了这个 TCP 段进行重复。

滑动窗口 

TCP每发送一个数据,都需要进行一次应答。当收到了上一个应答,再发下一个数据,但这种方式效率比较低。数据包往返时间越长,通信的效率就越低。  

为了解决这个问题,TCP引入了窗口概念。即在接收窗口范围内的数据,无需等待确认,可以继续发送窗口内数据,直到把发送窗口数据传输完毕。窗口大小就是指无需等待确认应答,而可以继续发送数据的最大值

假设窗口大小为 3 个 TCP 段,那么发送方就可以「连续发送」 3 个 TCP 段,并且中途若有 ACK 丢失,可以通过「下一个确认应答进行确认」。如下图:

图中的 ACK 600 确认应答报文丢失,也没关系,因为可以通话下一个确认应答进行确认,只要发送方收到了 ACK 700 确认应答,就意味着 700 之前的所有数据「接收方」都收到了。这个模式就叫累计确认或者累计应答。 


 窗口的实现实际上是在操作系统开辟一个缓存空间(空间和序号都是有限的,并且要循环使用,一般为环形队列),发送主机在等到确认应答返回之前,必须在缓冲区保留已发送窗口的数据(超时重传)。收到应答后,将此数据清除。

接收方根据它的缓冲区,可以计算出后续能够接收多少字节的报文,这个数字叫做接收窗口。

当内核接收到报文时,必须用缓冲区存放它们,这样剩余缓冲区空间变小,接收窗口也就变小了;

当进程调用 read 函数后,数据被读入了用户空间,内核缓冲区就被清空,这意味着主机可以接收更多的报文,接收窗口就会变大。

因此,接收窗口并不是恒定不变的,接收方会把当前可接收的大小放在 TCP 报文头部中的窗口字段,这样就可以起到窗口大小通知的作用。

发送方的窗口等价于接收方的窗口吗?如果不考虑拥塞控制,发送方的窗口大小「约等于」接收方的窗口大小,因为窗口通知报文在网络传输是存在时延的,所以是约等于的关系。

 从上图中可以看到,窗口字段只有 2 个字节,因此它最多能表达 65535 字节大小的窗口,也就是 64KB 大小。

流量控制

发送方不能一下子将大量的发数据给接收方,要考虑接收方处理能力。如果一直发送大量数据给对方,但对方处理不过来,那么就会导致触发重发机制,从而导致网络流量的无端的浪费。

为了解决这种现象发生,TCP 提供一种机制可以让「发送方」根据「接收方」的实际接收能力控制发送的数据量,这就是所谓的流量控制。

主要的方式就是接收方返回的 ACK 中会包含自己的接收窗口的大小,并且利用大小来控制发送方的数据发送。

拥塞控制

拥塞控制和流量控制的区别

所谓拥塞控制就是防止过多的数据注入到网络中,这样可以使网络中的路由器或链路不过载。

拥塞控制要做的前提就是网络能够承受住现有的网络负荷,它是一个全局性的过程,涉及到所有的主机,路由器,以及降低网络传输性能有关的所有因素。

相反,流量控制往往是指点对点通信量的控制,是端到端的问题,它要做的就是抑制发送端的速率,以便接收端来的及接收。

具体实现

TCP 不能忽略网络上发生的事,当网络发送拥塞时,TCP 会自我牺牲,降低发送的数据量。

于是,就有了拥塞控制,控制的目的就是避免「发送方」的数据填满整个网络

拥塞窗口

为了在「发送方」调节所要发送数据的量,定义了一个叫做「拥塞窗口」的概念。

拥塞窗口 cwnd是发送方维护的一个 的状态变量,它会根据网络的拥塞程度动态变化的

在前面提到过发送窗口 swnd 和接收窗口 rwnd 是约等于的关系,那么由于入了拥塞窗口的概念后,此时发送窗口的值是swnd = min(cwnd, rwnd),也就是拥塞窗口和接收窗口中的最小值

拥塞窗口 cwnd 变化的规则:

  • 只要网络中没有出现拥塞,cwnd 就会增大;

  • 但网络中出现了拥塞,cwnd 就减少;

如何判断出现拥塞

其实只要「发送方」没有在规定时间内接收到 ACK 应答报文,也就是发生了超时重传,就会认为网络出现了拥塞。

也就是说,拥塞控制中,通过接收到的ACK报文数量来进行控制,相应的,流量控制中,通过另一端发送回来的滑动窗口的大小来进行流量的控制。

拥塞控制算法(慢启动;拥塞避免;拥塞发生;快速恢复)

慢启动

TCP 在刚建立连接完成后,首先是有个慢启动的过程,慢启动的意思就是一点一点的提高发送数据包的数量。

慢启动的算法:当发送方每收到一个 ACK,就拥塞窗口 cwnd 的大小就会加 1。

这里假定拥塞窗口 cwnd 和发送窗口 swnd 相等,如下:

  • 连接建立完成后,一开始初始化 cwnd = 1,表示可以传一个 MSS 大小的数据。

  • 当收到一个 ACK 确认应答后,cwnd 增加 1,于是一次能够发送 2 个

  • 当收到 2 个的 ACK 确认应答后, cwnd 增加 2,于是就可以比之前多发2 个,所以这一次能够发送 4 个

  • 当这 4 个的 ACK 确认到来的时候,每个确认 cwnd 增加 1, 4 个确认 cwnd 增加 4,于是就可以比之前多发 4 个,所以这一次能够发送 8 个。

虽然看起来,每次拥塞窗口一次只加一,但是拥塞窗口增加后,返回的ACK报文也增加,而拥塞窗口是之前所有ACK报文的累积和,总的看来,发包的个数是指数性的增长

有一个叫慢启动门限  ssthresh (slow start threshold)状态变量。

  • 当 cwnd < ssthresh 时,使用慢启动算法。

  • 当 cwnd >= ssthresh 时,就会使用「拥塞避免算法」。

拥塞避免算法

当拥塞窗口 cwnd 「超过」慢启动门限 ssthresh 就会进入拥塞避免算法。一般来说 ssthresh 的大小是 65535 字节。

那么进入拥塞避免算法后,它的规则是:每当收到一个 ACK 时,cwnd 增加 1/cwnd。

现假定 ssthresh 为 8,当 8 个 ACK 应答确认到来时,每个确认增加 1/8,8 个 ACK 确认 cwnd 一共增加 1,于是这一次能够发送 9 个 MSS 大小的数据,变成了线性增长。

所以,我们可以发现,拥塞避免算法就是将原本慢启动算法的指数增长变成了线性增长,还是增长阶段,但是增长速度缓慢了一些。就这么一直增长着后,网络就会慢慢进入了拥塞的状况了,于是就会出现丢包现象,这时就需要对丢失的数据包进行重传。

当触发了重传机制,也就进入了「拥塞发生算法」。

拥塞发生算法

 当网络出现拥塞,也就是会发生数据包重传,重传机制主要有两种:超时重传;快速重传。

发生超时重传的拥塞发生算法

当发生了「超时重传」,则就会使用拥塞发生算法。

这个时候,sshresh 和 cwnd 的值会发生变化:

  • ssthresh 设为 cwnd/2

  • cwnd 重置为 1

接着,就重新开始慢启动,慢启动会突然减少数据流,所以对应的有了快重传算法

发生快速重传的拥塞发生算法

还有更好的方式,前面我们讲过「快速重传算法」。当接收方发现丢了一个中间包的时候,发送三次前一个包的 ACK,于是发送端就会快速地重传,不必等待超时再重传。

TCP 认为这种情况不严重,因为大部分没丢,只丢了一小部分,则 ssthresh 和 cwnd 变化如下:

  • cwnd = cwnd/2 ,也就是设置为原来的一半;

  • ssthresh = cwnd;

  • 进入快速恢复算法

快速恢复算法

快速重传和快速恢复算法一般同时使用,快速恢复算法认为,还能收到 3 个重复 ACK 说明网络的拥塞程度并不是特别严重。正如前面所说,进入快速恢复之前,cwnd 和 ssthresh 已被更新了:

  • cwnd = cwnd/2 ,也就是设置为原来的一半;

  • ssthresh = cwnd;

然后,进入快速恢复算法如下:

  • 拥塞窗口 cwnd = ssthresh + 3 ( 3 的意思是确认有 3 个数据包被收到了)

  • 重传丢失的数据包

  • 如果再收到重复的 ACK,那么 cwnd 增加 1

  • 如果收到新数据的 ACK 后,设置 cwnd 为 ssthresh,接着就进入了拥塞避免算法

HTTP3.0 HTTP2.0 HTTP1.1与HTTP1.0

HTTP1.0和HTTP1.1的区别

  1. 长连接(Persistent Connection): HTTP1.1支持长连接和请求的流水线处理,在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟,在HTTP1.1中默认开启长连接keep-alive,一定程度上弥补了HTTP1.0每次请求都要创建连接的缺点。HTTP1.0需要使用keep-alive参数来告知服务器端要建立一个长连接。
  2. 节约带宽: HTTP1.0中存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能。HTTP1.1支持只发送header信息(不带任何body信息),如果服务器认为客户端有权限请求服务器,则返回100,客户端接收到100才开始把请求body发送到服务器;如果返回401,客户端就可以不用发送请求body了节约了带宽。
  3. HTTP1.1支持管道化运输,在没有接受到第一个请求回应的时候,就可以发送第二个网络请求。
  4. 错误通知的管理: 在HTTP1.1中新增了24个错误状态响应码,如409(Conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除。

http1.1的不足

  • 头部阻塞(接收队列):在http1.1的管道化传输中,虽然可以同时发送多个请求,但是在另一端接收时,只有收到第一个请求之后才能接受后面的请求,所以说如果第一个(队头)接受过程发生了阻塞,那么之后所有的请求都会被迫等待它完成。
  • 头部冗长重复:每次发送http都要发送一个冗长的请求,但是绝大部分重复的部分居多。
  • 客户端永远都是发起方,服务端永远都是被动方。

http2.0在http1.1的基础上,做了几点提升

  • 第一点,对于常见的 HTTP 头部通过静态表和 Huffman 编码的方式,将体积压缩了近一半,而且针对后续的请求头部,还可以建立动态表,将体积压缩近 90%,大大提高了编码效率,同时节约了带宽资源。不过,动态表并非可以无限增大, 因为动态表是会占用内存的,动态表越大,内存也越大,容易影响服务器总体的并发能力,因此服务器需要限制 HTTP/2 连接时长或者请求次数。
  • 第二点,HTTP/2 实现了 Stream(数据流) 并发,多个 Stream 只需复用 1 个 TCP 连接,节约了 TCP 和 TLS 握手时间,以及减少了 TCP 慢启动阶段对流量的影响。不同的 Stream ID 才可以并发,即时乱序发送帧也没问题,但是同一个 Stream 里的帧必须严格有序。另外,可以根据资源的渲染顺序来设置 Stream 的优先级,从而提高用户体验。
  • 第三点,服务器支持主动推送资源,大大提升了消息的传输性能,服务器推送资源时,会先发送 PUSH_PROMISE 帧,告诉客户端接下来在哪个 Stream 发送资源,然后用偶数号 Stream 发送资源给客户端。

HTTP/2 协议是基于 TCP 实现的,于是存在的缺陷有三个。

  • 队头阻塞,HTTP/2 多个请求跑在一个 TCP 连接中,如果序列号较低的 TCP 段在网络传输中丢失了,即使序列号较高的 TCP 段已经被接收了,应用层也无法从内核中读取到这部分数据,从 HTTP 视角看,就是多个请求被阻塞了;

  • TCP 和 TLS 握手时延,TCL 三次握手和 TLS 四次握手,共有 3-RTT 的时延;

  • 连接迁移需要重新连接,移动设备从 4G 网络环境切换到 WIFI 时,由于 TCP 是基于四元组来确认一条 TCP 连接的,那么网络环境变化后,就会导致 IP 地址或端口变化,于是 TCP 只能断开连接,然后再重新建立连接,切换网络环境的成本高;

HTTP/3 就将传输层从 TCP 替换成了 UDP,并在 UDP 协议上开发了 QUIC 协议,来保证数据的可靠传输。 

QUIC 协议的特点:

  • 无队头阻塞,QUIC 连接上的多个 Stream 之间并没有依赖,都是独立的,也不会有底层协议限制,某个流发生丢包了,只会影响该流,其他流不受影响;

  • 建立连接速度快,因为 QUIC 内部包含 TLS1.3,因此仅需 1 个 RTT 就可以「同时」完成建立连接与 TLS 密钥协商,甚至在第二次连接的时候,应用数据包可以和 QUIC 握手信息(连接信息 + TLS 信息)一起发送,达到 0-RTT 的效果。

  • 连接迁移,QUIC 协议没有用四元组的方式来“绑定”连接,而是通过连接 ID 来标记通信的两个端点,客户端和服务器可以各自选择一组 ID 来标记自己,因此即使移动设备的网络变化后,导致 IP 地址变化了,只要仍保有上下文信息(比如连接 ID、TLS 密钥等),就可以“无缝”地复用原连接,消除重连的成本;

另外 HTTP/3 的 QPACK 通过两个特殊的单向流来同步双方的动态表,解决了 HTTP/2 的 HPACK 队头阻塞问题。

HTTP的keep-alive和TCP的keepalive

这两者是两个层面的东西,不恰当的说,HTTP的Keep-Alive是对TCP的Keepalive的补充

  • HTTP 的 Keep-Alive,是由应用层(用户态) 实现的,称为 HTTP 长连接;

  • TCP 的 Keepalive,是由 TCP 层(内核态) 实现的,称为 TCP 保活机制;

TCP的keepalive

这是由内核实现的,用以回收空闲的tcp连接,以释放服务器的资源,提升服务器的性能。

过程描述      

连接中启动保活功能的一端,在保活时间内连接处于非活动状态,则向对方发送一个保活探测报文,如果收到对方对于检测报文发回来的响应报文,则重置保活计时器,如果没有收到响应报文,则经过一个保活时间间隔后再次向对方发送一个保活探测报文,如果还没有收到响应报文,则继续,直到发送次数到达保活探测数,此时,对方主机将被确认为不可到达,连接被中断。

TCP保活功能工作过程中,开启该功能的请求端会发现对方处于以下四种状态之一:

  • 对方主机仍在工作,并且可以到达。此时请求端将保活计时器重置。如果在计时器超时之前应用程序通过该连接传输数据,计时器再次被设定为保活时间值。
  • 对方主机已经崩溃,包括已经关闭或者正在重新启动。这时对方的TCP将不会响应。请求端不会接收到响应报文,并在经过保活时间间隔指定的时间后超时。超时前,请求端会持续发送探测报文,一共发送保活探测数指定次数的探测报文,如果请求端一直没有收到任何探测报文的响应,那么它将认为对方主机已经关闭,连接也将被断开
  • 客户主机崩溃并且已重启。在这种情况下,请求端会收到一个对其保活探测报文的响应,但这个响应报文是一个重置报文段RST,请求端将会断开连接。
  • 对方主机仍在工作,但是由于某些原因不能到达请求端(例如网络无法传输,而且可能使用ICMP通知也可能不通知对方这一事实)。这种情况与状态2相同,因为TCP不能区分状态2与状态4,结果是都没有收到探测报文的响应。

保活时间的设置

在Linux内核可以有对应的参数可以设置保活时间、保活探测的次数、保活探测的时间间
隔,以下都为默认值:

net.ipv4.tcp_keepalive_time = 7200
net.ipv4.tcp_keepalive_intvl = 75
net.ipv4.tcp_keepalive_probes = 9

●tcp_ keepalive_time = 7200: 表示保活时间是7200秒(2小时),也就2小时内如果没有任何连接相关的活动,则会启动保活机制
●tcp_ keepalive_ intvl = 75: 表示每次检测间隔75秒;
●tcp_ _keepalive_ probes = 9: 表示检测9次无响应,认为对方是不可达的,从而中断本次的连接。
也就是说在Linux 系统中,最少需要经过2小时11分15秒才可以发现一个「死亡」连接 

注意,应用程序若想使用 TCP 保活机制,需要通过 socket 接口设置SO_KEEPALIVE选项才能够生效,如果没有设置,那么就无法使用 TCP 保活机制。

TCP连接的保活机制,使得所有基于TCP连接的上层连接都有一个基本的保活机制。当TCP保活机制断开无效的TCP连接后,TCP连接断开这一事件会被反馈到上层,上层将断开上层连接,从而释放上层连接占据的资源。

但是TCP保活机制不能满足各种应用的需求。比如TCP保活机制断开连接的及时性不好(默认2小时没收到对方消息后才开始探测连接是否有效),企图通过将 tcp_keepalive_time 设置成一个小值(比如60)来提高及时性也不可行,因为这个设置是操作系统全局设置,会影响到所有TCP连接,而不是每个应用都希望 tcp_keepalive_time 设置得这么小。       

所以基于TCP的应用需要在应用层建立符合自己业务需求的保活机制。

HTTP的keep-alive

HTTP keep-alive用于复用同一个tcp连接以承载多个http请求,这样减少了连接建立的三次握手和关闭的四次握手,这样降低了网络开销,也减轻了服务器压力。

比如设置了 HTTP 长连接的超时时间是 60 秒,web 服务软件就会启动一个定时器,如果客户端在完后一个 HTTP 请求后,在 60 秒内都没有再发起新的请求,定时器的时间一到,就会触发回调函数来释放该连接。

TCP保活机制和应用层保活机制的关注点不同。

对比TCP保活机制和HTTP长连接超时机制,前者只关注TCP连接是否有效(不关注上层过去的通信活动,将来的通信意愿),而后者只关注最近一段时间是否收到过对方消息(不关注下层TCP连接是否有效)。这体现了分层、关注点隔离的思想。

什么情况下传输RST报文

产生RST的三个条件是:

  1. 目的地为某端口的SYN到达,然而并没有服务器在监听该端口;
  2. TCP想取消一个已有连接;
  3. TCP接收到一个根本不存在的连接上的分节。

访问不存在的端口 内核收到客户端发来请求连接,但是连接报文中的目的端口不存在,则直接返回RST,同时RST报文接收通告窗口大小为0。此外客户端向服务器的某个端口发起连接,如果端口被处于TIME_WAIT 状态的连接占用时,客户端也会收到RST

异常终止连接。一方主动发送RST报文,表示异常终止连接。与FIN报文不同,FIN是在之前所有排队数据都已发送后才被发送出去,通常不会出现丢失数据的情况。然而一旦发送方发送RST报文段,发送端所有排队等待发送的数据都被丢弃

处理半打开连接。一方关闭了连接,另一方却没有收到结束报文(如网络故障),此时另一方还维持着原来的连接。而一方即使重启,也没有该连接的任何信息。对于另一方来说这种状态就叫做半打开连接。

例如服务器主机被切断电源后重启(切断电源前可将网线断开,重启后再接上),此时的客户端是一个半开的连接。当客户端再次向服务端发送数据时,服务端对此连接一无所知,会回复一个重置报文段RST后,中断连接。同样的,在此情况下,当开启了保活机制的客户端向服务器发送保活检测报文时,会收到服务器发过来的RST报文

Time Wait的作用

MSL的定义      

MSL 是 Maximum Segment Lifetime,报文最大生存时间,它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。因为 TCP 报文基于是 IP 协议的,而 IP 头中有一个 TTL 字段,是 IP 数据报可以经过的最大路由数,每经过一个处理他的路由器此值就减 1,当此值为 0 则数据报将被丢弃,同时发送 ICMP 报文通知源主机。

MSL 与 TTL 的区别: MSL 的单位是时间,而 TTL 是经过路由跳数。所以 MSL 应该要大于等于 TTL 消耗为 0 的时间,以确保报文已被自然消亡。

TIME_WAIT 等待 2 倍的 MSL,比较合理的解释是: 网络中可能存在来自发送方的数据包,当这些发送方的数据包被接收方处理后又会向对方发送响应,所以一来一回需要等待 2 倍的时间

2MSL 的时间是从客户端接收到 FIN 后发送 ACK 开始计时的。如果在 TIME-WAIT 时间内,因为客户端的 ACK 没有传输到服务端,客户端又接收到了服务端重发的 FIN 报文,那么 2MSL 时间将重新计时

在 Linux 系统里 2MSL 默认是 60 秒,那么一个 MSL 也就是 30 秒。Linux 系统停留在 TIME_WAIT 的时间为固定的 60 秒

为什么需要 TIME_WAIT 状态

主动发起关闭连接的一方,才会有 TIME-WAIT 状态。

需要 TIME-WAIT 状态,主要是两个原因:

  • 防止具有相同「四元组」的「旧」数据包被收到;
  • 保证「被动关闭连接」的一方能被正确的关闭,即保证最后的 ACK 能让被动关闭方接收,从而帮助其正常关闭;

原因一:防止旧连接的数据包

假设 TIME-WAIT 没有等待时间或时间过短,如下

  • 如上图黄色框框服务端在关闭连接之前发送的 SEQ = 301 报文,被网络延迟了。
  • 这时有相同端口的 TCP 连接被复用后,被延迟的 SEQ = 301 抵达了客户端,那么客户端是有可能正常接收这个过期的报文,这就会产生数据错乱等严重的问题。

所以,TCP 就设计出了这么一个机制,经过 2MSL 这个时间,足以让两个方向上的数据包都被丢弃,使得原来连接的数据包在网络中都自然消失,再出现的数据包一定都是新建立连接所产生的。

原因二:保证连接正确关闭

TIME-WAIT 作用是等待足够的时间以确保最后的 ACK 能让被动关闭方接收,从而帮助其正常关闭。假设 TIME-WAIT 没有等待时间或时间过短,如下:

  • 如上图红色框框客户端四次挥手的最后一个 ACK 报文如果在网络中被丢失了,此时如果客户端 TIME-WAIT 过短或没有,则就直接进入了 CLOSE 状态了,那么服务端则会一直处在 LASE-ACK 状态。
  • 当客户端发起建立连接的 SYN 请求报文后,服务端会发送 RST 报文给客户端,连接建立的过程就会被终止。

如果 TIME-WAIT 等待足够长的情况就会遇到两种情况:

  • 服务端正常收到四次挥手的最后一个 ACK 报文,则服务端正常关闭连接。
  • 服务端没有收到四次挥手的最后一个 ACK 报文时,则会重发 FIN 关闭连接报文并等待新的 ACK 报文。

所以客户端在Time Wait状态等待2MSL时间后,就可以保证双方的连接都可以正常的关闭

TIME_WAIT 过长有什么危害?

如果服务器有处于 TIME-WAIT 状态的 TCP,则说明是由服务器方主动发起的断开请求。

过多的 TIME-WAIT 状态主要的危害有两种:

  • 第一是内存资源占用;
  • 第二是对端口资源的占用,一个 TCP 连接至少消耗一个本地端口;

第二个危害会造成严重的后果,端口资源也是有限的,一般可以开启的端口为 32768~61000

如果发起连接一方的 TIME_WAIT 状态过多,占满了所有端口资源,则会导致无法创建新连接

客户端受端口资源限制:

客户端TIME_WAIT过多,就会导致端口资源被占用,因为端口就65536个,被占满就会导致无法创建新的连接。

服务端受系统资源限制:

由于一个四元组表示 TCP 连接,理论上服务端可以建立很多连接,服务端确实只监听一个端口,但是会把连接扔给处理线程,所以理论上监听的端口可以继续监听,如果已建立的连接的Time Wait时间过长,就会一直占用线程池的线程资源。所以当服务端出现大量 TIME_WAIT 时,系统资源被占满时,会导致处理不过来新的连接。         


在TCP正常挥手过程中,处于TIME_WAIT状态的连接,收到相同四元组的SYN后会发生什么

  • 如果 SYN 报文的「序列号+时间戳」都是合法的话,就会重新建立连接;

  • 如果 SYN 报文的「序列号+时间戳」其中一个不合法的话,就会回 RST。


TIME_WAIT 状态快速回收

内核有两个参数可以实现

  • 参数一:net.ipv4.tcp_tw_reuse,如果开启该选项的话,客户端(连接发起方) 在调用 connect() 函数时,内核会随机找一个 TIME_WAIT 状态超过 1 秒的连接给新的连接复用,所以该选项只适用于连接发起方。

  • 参数二:net.ipv4.tcp_tw_recycle,如果开启该选项的话,允许处于 TIME_WAIT 状态的连接被快速回收。

关于客户端与服务器的连接问题

杀死客户端和拔掉客户端网线之后都会发生什么

杀死客户端和拔掉网线的区别就是能否在网络里根据四元组信息再找到客户端

在客户端杀掉进程的话,内核就会主动向服务端发送 FIN 报文,来断开这个客户端进程与服务端建立的所有 TCP 连接

拔掉客户端的网线后,客户端就直接从网络里下线了,通过IP地址和端口号无法再找到该进程,但是进程在客户端里还是正常运行。需要分类讨论

客户端和服务器有数据传输的情况:

  • 在客户端拔掉网线后,服务端发送了数据报文但是没有收到回复,就会进行报文的超时重传,如果在服务端重传次数没有达到最大值之前,客户端就插回了网线,那么双方原本的 TCP 连接还是能正常存在,就好像什么事情都没有发生。

  • 在客户端拔掉网线后,服务端发送了数据报文但是没有收到回复,在客户端插回网线之前,如果服务端重传次数达到了最大值,服务端就会断开 TCP 连接。等到客户端插回网线后,向服务端发送了数据,因为服务端已经断开了与客户端相同四元组的 TCP 连接,所以就会回 RST 报文,客户端收到后就会断开 TCP 连接。至此, 双方的 TCP 连接都断开了。

客户端和服务器没有数据传输的情况

  • 如果双方都没有开启 TCP keepalive 机制,那么在客户端拔掉网线后,如果客户端一直不插回网线,那么客户端和服务端的 TCP 连接状态将会一直保持存在。

  • 如果双方都开启了 TCP keepalive 机制,那么在客户端拔掉网线后,如果客户端一直不插回网线,TCP keepalive 机制会探测到对方的 TCP 连接没有存活,于是就会断开 TCP 连接。而如果在 TCP 探测期间,客户端插回了网线,那么双方原本的 TCP 连接还是能正常存在。

TCP 和 UDP 可以同时绑定相同的端口吗?

可以的。TCP 和 UDP 传输协议,在内核中是由两个完全独立的软件模块实现的。

当主机收到数据包后,可以在 IP 包头的「协议号」字段知道该数据包是 TCP/UDP,所以可以根据这个信息确定送给哪个模块(TCP/UDP)处理,送给 TCP/UDP 模块的报文根据「端口号」确定送给哪个应用程序处理。

因此, TCP/UDP 各自的端口号也相互独立,互不影响。

多个服务器进程可以绑定同一个端口吗?

  • 如果两个 TCP 服务进程同时绑定的 IP 地址和端口都相同,那么执行 bind() 时候就会出错,错误是“Address already in use”
  • 如果这两个 TCP 服务进程绑定的IP地址不同,端口相同,则是可以绑定成功的。

注意:

如果 TCP 服务进程 A 绑定的地址是  0.0.0.0 和端口 8888,而如果 TCP 服务进程 B 绑定的地址是 192.168.1.100 地址(或者其他地址)和端口 8888,那么执行 bind() 时候也会出错。

这是因为 0.0.0.0  地址比较特殊,代表任意地址,意味着绑定了 0.0.0.0  地址,相当于把主机上的所有 IP 地址都绑定了

重启 TCP 服务进程时,为什么会有“Address in use”的报错信息?

当我们重启 TCP 服务进程的时候,意味着通过服务器端发起了关闭连接操作,于是就会经过四次挥手,而对于主动关闭方,会在 TIME_WAIT 这个状态里停留一段时间,这个时间大约为 2MSL。

当 TCP 服务进程重启时,服务端会出现 TIME_WAIT 状态的连接,TIME_WAIT 状态的连接使用的 IP+PORT 仍然被认为是一个有效的 IP+PORT 组合,相同机器上不能够在该 IP+PORT 组合上进行绑定,那么执行 bind() 函数的时候,就会返回了 Address already in use 的错误

而等 TIME_WAIT 状态的连接结束后,重启 TCP 服务进程就能成功。

如何避免

在调用 bind 前,对 socket 设置 SO_REUSEADDR 属性进行端口复用

int on = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

SO_REUSEADDR 作用是:如果当前启动进程绑定的 IP+PORT 与处于TIME_WAIT 状态的连接占用的 IP+PORT 存在冲突,但是新启动的进程使用了 SO_REUSEADDR 选项,那么该进程就可以绑定成功。

客户端的一个端口可以重复使用吗(即一个客户端可以和多个服务端建立连接吗)?

在客户端执行 connect 函数的时候,只要客户端连接的服务器不是同一个,内核允许端口重复使用。

TCP 连接是由四元组(源IP地址,源端口,目的IP地址,目的端口)唯一确认的,那么只要四元组中其中一个元素发生了变化,那么就表示不同的 TCP 连接。

所以,如果客户端已使用端口 64992 与服务端 A 建立了连接,那么客户端要与服务端 B (服务端A与B的IP或端口号不一样)建立连接,还是可以使用端口 64992 的,因为内核是通过四元祖信息来定位一个 TCP 连接的,并不会因为客户端的端口号相同,而导致连接冲突的问题。

多个客户端可以 bind 同一个端口吗?

首先声明:客户端是在调用 connect 函数的时候,由内核随机选取一个端口作为连接的端口。bind 函数虽然常用于服务端网络编程中,但是它也是用于客户端的。但是一般而言,客户端不建议使用 bind 函数,应该交由 connect 函数来选择端口会比较好,因为客户端的端口通常都没什么意义。

和TCP一样,如果多个客户端绑定的IP与端口号完全一样,则不可以绑定。

客户端 TCP 连接 TIME_WAIT 状态过多,会导致端口资源耗尽而无法建立新的连接吗?

  • 要看客户端是否都是与同一个服务器(目标地址和目标端口一样)建立连接。
  • 如果客户端都是与同一个服务器(目标地址和目标端口一样)建立连接,那么如果客户端 TIME_WAIT 状态的连接过多,当端口资源被耗尽,就无法与这个服务器再建立连接了。
  • 但是,因为只要客户端连接的服务器不同,端口资源可以重复使用的

如何解决客户端 TCP 连接 TIME_WAIT 过多,导致无法与同一个服务器建立连接的问题?

打开 net.ipv4.tcp_tw_reuse  这个内核参数。

因为开启了这个内核参数后,客户端调用 connect  函数时,如果选择到的端口,已经被相同四元组的连接占用的时候,就会判断该连接是否处于  TIME_WAIT 状态。如果该连接处于 TIME_WAIT 状态并且 TIME_WAIT 状态持续的时间超过了 1 秒,那么就会重用这个连接,然后就可以正常使用该端口了。

注意:开启了 net.ipv4.tcp_tw_reuse  内核参数,是客户端(连接发起方) 在调用 connect() 函数时才起作用,所以在服务端开启这个参数是没有效果的。

GET 和 POST 的区别

1. get是获取数据,post是修改数据

2. get把请求的数据放在url上, 以?分割URL和传输数据,参数之间以&相连,而post 把数据放在HTTP的包体内(requrest body)

3. get提交的数据最大是2k( 限制实际上取决于浏览器的URL长度限制), post理论上没有限制。

4. GET请求会被浏览器主动缓存,而POST不会,除非手动设置。

5. 本质区别:GET是幂等的,而POST不是幂等的

幂等性是指一次和多次请求某一个资源应该具有同样的副作用。简单来说意味着 对同一URL的多个请求应该返回同样的结果

HTTP请求和响应报文的主要字段

 请求报文

1.请求行:Request Line

2.请求头:Request Headers

3.请求体:Request Body

在这里插入图片描述 响应报文

1.状态行:Status Line

2.响应头:Response Headers

3.响应体:Response Body

在这里插入图片描述


cookie与session

  • HTTP/1.1 引 入 Cookie 来保存状态信息。Cookies是一些存储在用户端上的小文件。它是被设计用来保存一些站点的用户数据,这样能够让服务器为这样的用户定制内容。          
  • Cookie 是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器之后向同一服务器再次发起请求时被携带上,用于告知服务端两个请求是否来自同一浏览器。由于之后每次请求都会需要携带 Cookie 数据,因此会带来额外的性能开销(尤其是在移动环境下)。
  • cookie 的出现是因为 HTTP 是无状态的一种协议,换句话说,服务器记不住客户端的信息,可能每刷新一次网页,就要重新输入一次账号密码进行登录。cookie 的作用就好比服务器给客户端贴上标签,然后每次向服务器再发请求时,服务器就能够通过cookie认出客户端 。       

相应的cookie也有它的缺点:

  1. 保存在客户端,用户是可见的,容易被篡改。
  2. 大小受限,本身最大4kb。

所以需要sesson技术

session

Session 可以 存储在服务器上的文件、数据库或者内存中 。也可以将 Session 存储在 Redis 这种内存型数据库中,效率会更高。
使用 Session 维护用户登录状态的过程如下:
  •  用户进行登录时,用户提交包含用户名和密码的表单,放入 HTTP 请求报文中;
  • 服务器验证该用户名和密码,如果正确则把用户信息存储到 Redis 中,它在 Redis 中的 Key 称为 Session ID
  • 服务器返回的响应报文的 Set-Cookie 首部字段包含了这个 Session ID,客户端收到响应报文之后将该 Cookie 值存入浏览器中
  • 客户端之后对同一个服务器进行请求时会包含该 Cookie 值,服务器收到之后提取出 Session ID,从 Redis 中取出用户信息,继续之前的业务操作。                             
当然,对于大型网站,如果用户所有的信息都存储在 Session 中,那么开销是非常大的,因此不建议将所有 的用户信息都存储到 Session 中。

两者区别

cookie和session都是用来跟踪浏览器用户身份的会话方式。

Cookie   
Cookie 客户端保持状态 的方法。
Cookie简单的理解就是存储由服务器发至客户端并由客户端保存的一段字符串,为了保持会话。
Session
Session 服务器保持状态 的方法。
每个用户有一个独一无二的 Session ID作为 Session 文件的 Hash 键,通过这个值可以锁定具体的 Session 结构的数据,这个 Session结构中存储了用户操作行为。
抽象地概括一下:一个 cookie 可以认为是一个「变量」,形如 name=value,存储在浏览器;一个 session 可以理解为一种数据结构,多数情况是「映射」(键值对),存储在服务器上。

SYN攻击

服务器端的资源分配是在二次握手时分配的,而客户端的资源是在完成三次握手时分配的

所以 服务器容易受到SYN洪泛攻击。SYN攻击就是Client在短时间内伪造大量不存在的IP地址,并向Server不断地发送SYN包,Server则回复确认包,并等待Client确认,由于源地址不存在,因此Server需要不断重发直至超时,这些伪造的SYN包将长时间占用未连接队列,导致正常的SYN请求因为队列满而被丢弃,从而引起网络拥塞甚至系统瘫痪。
SYN 攻击是一种典型的 DoS/DDoS 攻击。       
   
检测 SYN 攻击非常的方便,当在服务器上看到大量的半连接状态时,特别是源IP地址是随机的,基本上可以断定这是一次SYN攻击。
常见的防御 SYN 攻击的方法有如下几种:
  • 缩短超时(SYN Timeout)时间
  • 增加最大半连接数
  • 过滤网关防护
  • SYN cookies技术,在服务端接收到SYN后不立即分配连接资源,而是根据这个SYN计算出一个Cookie,连同第二次握手回复给客户端,在客户端回复ACK的时候带上这个Cookie值,服务端验证 Cookie 合法之后才分配连接资源。

一台机器能够使用的端口号上限

一台机器能够使用的端口号上限是65536
因为 TCP 的报文头部中源端口号和目的端口号的长度是 16 位,也就是可以表示 2^16=65536 个不同
端口号,因此 TCP 可供识别的端口号最多只有 65536 个。但是由于 0 1023是知名服务端口,所以实际上 还要少 1024 个端口号。
而对于服务器来说,可以开的端口号与 65536 无关,其实是受限于 Linux可以打开的文件数量,并且可以 通过 MaxUserPort 来进行配置
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值