Back

网络

计算机网络、HTTP协议、TCP协议相关

[TOC]

基本

OSI七层模型 对应网络协议 作用
应用层 HTTP、TFTP、FTP、NFS、SMTP、Telnet 应用程序间通信的网络协议
表示层 Rlogin、SNMP、Gopher 数据格式化、加密、解密
会话层 SMTP、DNS 建立、维护、管理会话连接
传输层 TCP、UDP 建立、维护、管理端到端连接
网络层 IP、ICMP、ARP、RARP、AKP、UUCP IP寻址和路由选择
数据链路层 FDDI、Ethernet、Arpanet、PDN、SLIP、PPP 控制网络层与物理层间的通信
物理层 IEEE 802.1A、IEEE 802.2到802.11 比特流传输

数据链路层:

  • 数据包叫Frame,“帧”;
  • 由两部分组成:标头和数据,标头标明数据发送者、接收者、数据类型;
  • 用MAC地址定位数据包路径;
  • 相关设备是交换机;

网络层:

  • 数据包叫packet,“包”;
  • IPv4:32个二进制,4字节*8位;IPv6:1同一子网28个二进制,8字节*16位;
  • 子网掩码与IP的and运算判断是否为同一子网下;
  • 路由:把数据从原地址转发到目标地址,同一局域网内,通过广播的方式找到,不同局域网内,原主机先将包根据网关添加路由器/主机地址,通过交换机的广播方式发给目标主机,原主机将数据包传输给目标主机,再由目标主机根据MAC广播交给对应目标
  • ARP协议:IP与MAC地址的映射,仅限IPv4,IPv6使用 Neighbor Discovery Protocol替代;
  • 相关设备是路由器,网关

传输层:

  • 数据包叫segment,“段”;

  • Socket、UDP、TCP见下

应用层使用TCP传输数据时,会先将数据打到TCP的Segment中,然后TCP的Segment会打到IP的Packet中,然后再打到以太网Ethernet的Frame中,传输到目标主机后,再一层层解析,往上传递。

从浏览器输入URL之后都发生了什么

浏览器输入URL,按回车,

  1. 浏览器根据输入内容,匹配对应的URL和关键词,校验URL的合法性,从历史记录、书签等地方,找到可能对应的URL,进行补全,使其符合通用URI的语法。

  2. 发起请求,从URL中解析出域名,首先查看本地hosts文件,判断是否有这个域名对应的ip,如果没有,请求本地DNS服务器,先查询本地DNS服务的缓存,如果没有再向上级DNS服务器发送请求,递归查询,直到找到对应的IP地址,然后本地DNS服务器就把对应关系保存在缓存中

    注意根DNS服务器没有记录具体的域名和IP的对应关系,而是告诉本地DNS服务器可以到域服务器上继续查询,给出的是域服务器的地址。

  3. 拿到域名对应的IP后,应用层程序准备好数据后,委托给操作系统,复制应用层数据到内核的内存空间中,交给网络协议栈(将其打包为tcp包(传输层),帧(数据链路层),并将数据从内核拷贝到网卡,后续由网卡负责数据的发送),与Web服务器建立 TCP/IP 连接(三次握手具体过程)。

  4. 建立连接后,发起一个HTTP 请求,经过路由器的转发,通过Web服务器(CDN、反向代理之类的)的防火墙,该 HTTP 请求到达了Web服务器。

  5. 服务器处理该 HTTP 请求,返回一个 HTML 文件。

  6. 浏览器解析该 HTML 文件,解析HTML文件后,构建dom树 -》构建render树 -》布局render树 -》绘制 render树,自上而下加载,边加载边解析渲染,显示在浏览器端,对于图片音频等则是异步加载。

本质上是OSI七层模型 + 相应协议、组件实现;建立一次TCP后,在HTTP 1.1请求头配置keep-alive=true后,默认保持两小时的连接,可以一直进行http请求,但同时只处理一个http请求,HTTP 2.0才允许并行多个;

HTTP方法

菜鸟HTTP教程/HTTP请求方法

Get和Post的区别

  • 语义上的区别,Get一般表示查询、获取,Post是更新,创建

  • Get具有幂等性,Post没有

  • 参数传递方面,Get一般参数接在Url上,对外暴露,有长度限制(1024个字节即256个字符),只接收ASCII字符,需要进行url编码

    Post参数放在request body里,支持多种编码

  • GET请求会被浏览器主动cache,而POST不会,除非手动设置

  • GET产生的URL地址可以加入书签,而POST不可以

  • GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留

  • GET在浏览器回退时是无害的,而POST会再次提交请求

其实本质都是一种协议的规范,规定参数的存放位置,参数长度大小等,当然也可以反着来,只要服务器能够理解即可

幂等性:同样的请求被执行一次与连续执行多次的效果是一样的,服务器的状态也是一样的,每次返回的结果一样,不产生副作用;

根据语义,简单的把get看成查询,只要服务器的数据没变,每次查询得到的结果是一样的,而把post看成添加,每次post请求都会创建新资源,服务器状态改变

具有幂等性的方法:GET、HEAD、OPTIONS、DELETE、PUT

没有幂等性的方法:POST

安全性:安全的 HTTP 方法不会改变服务器状态,也就是说它只是可读的。

常见状态码

参考HTTP状态码

  • 301:永久移动请求的网页已永久移动到新位置,即永久重定向;返回信息会包括新的URI,浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替,新的URI会放在响应header的Location字段中;搜索引擎会抓取新内容同时也将旧地址修改为新地址。301调整默认会被浏览器cache,用户后续多次访问该url时会,浏览器会直接请求跳转地址。
  • 302:临时移动请求的网页暂时跳转到其他页面,即暂时重定向;旧地址的资源还在,只是重定向到临时的新地址中,对SEO有利,搜索引擎会抓取新内容保存旧地址。如果在响应头中通过Cache-Control或Expires,也可以实现301中浏览器缓冲的效果。
  • 405:请求的Method被禁止,比如Post的接口用成了Get
  • 502:Bad Gateway,作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应
  • 503:Service Unavailable,服务不可用
  • 504:Gateway Time-out,充当网关或代理的服务器,未及时从上游服务器收到请求

HTTP头

通用头

cache-control

设置http缓存规则,控制缓存开关,缓存策略,缓存的有效时间等,请求头和响应头有不同的枚举值

keep-alive

保持长连接,连接复用,避免频繁建立连接带来的性能损耗。HTTP1.0默认是关闭的,HTTP1.1默认是开启的。对应的key是Connection。可以设置参数Keep-Alive: max=5, timeout=120表示一次keep-alive可以发送的请求次数是5,连接保持时间是120s,默认是2小时,可以一直进行HTTP请求,但一个TCP连接同一时间只支持一个HTTP请求

不同的浏览器对同一Host建立的TCP连接数量的限制取决于浏览器本身,像Chrome最多允许同时建立6个TCP连接,假如一个HTML页面有很多图片需要加载,且这些图片都是同一Host,且HTTPs,那浏览器在SSL后会和服务器商品是否启用HTTP2,如果启用就可以同一连接下同时下载多个,如果不启用,就仍然用那几个连接,排队去等了。

与TCP的keep-alive不同:

  • TCP 的 KeepAlive 是由操作系统内核来控制,通过 keep-alive 报文来防止 TCP 连接被对端、防火墙或其他中间设备意外中断,和上层应用没有任何关系,只负责维护单个 TCP 连接的状态,其上层应用可以复用该 TCP 长连接,也可以关闭该 TCP 长连接。
  • HTTP 的 KeepAlive 机制则是和自己的业务密切相关的,浏览器通过头部告知服务器要复用这个 TCP 连接,请不要随意关闭。只有到了 keepalive 头部规定的 timeout 才会关闭该 TCP 连接,不过这具体依赖应用服务器,应用服务器也可以根据自己的设置在响应后主动关闭这个 TCP 连接,只要在响应的时候携带 Connection: Close 告知对方

HTTPs

HTTPS = HTTP + SSL/TLS,SSL是介于HTTP之下TCP之上的协议层,提供 加密明文,验证身份,保证报文完整 的保障。TLS是升级版的SSL,作用类似,比SSL多了些其他功能,现在绝大多数浏览器都不支持SSL了,而是支持TLS。

HTTP 先和 SSL(Secure Sockets Layer)通信,再由 SSL 和 TCP 通信。一般情况下,TLS需要先经过TCP三次握手,建立可靠连接之后,才能做TLS握手的事。

  • 加密

    使用 对称加密 加密 报文(私钥加密私钥解密)

    使用 非对称加密 加密 对称加密的密钥,保证该密钥的传输安全(客户端公钥加密,服务端私钥解密;或者 服务端私钥加密,客户端公钥解密)

  • 验证身份

    通过第三方(CA)发布SSL证书,对通信方进行认证

    服务器的运营人员向 CA 提出公开密钥的申请,CA 在判明提出申请者的身份之后,会对已申请的公开密钥做数字签名,然后分配这个已签名的公开密钥,并将该公开密钥放入公开密钥证书后绑定在一起。

    所以,SSL证书主要是表明了该域名归属,日期等信息,还包含了用于报文加密的公钥和私钥以及数字签名,SSL证书被服务端持有

    进行 HTTPs 通信时,服务器会把证书发送给客户端。客户端取得其中的公开密钥之后,先使用数字签名进行验证,如果验证通过,就可以开始通信了,通信时使用上述的加密机制保护报文;

    证书信任的方式:操作系统和浏览器内置;CA颁发;手动导入证书

  • 保护报文完整

    SSL 提供报文摘要功能(即签名)结合加密和认证来进行完整性保护

流程:

在HTTP的基础上,以TLS 1.2版本为例

  1. 客户端访问服务端的网页,首先经过浏览器内置的受信任的CA机构列表,查看该服务器是否向CA机构提供了证书;

  2. 如果服务器证书中的信息与当前正在访问的网站(域名等)一致,那么浏览器就认为服务端是可信的,并从服务器证书中取得服务器公钥;

  3. 如果还没有取得服务器中的证书,则与服务器建立TCP连接,之后开始TLS的流程:

    1. 客户端向服务端发送请求,把自己支持的TLS版本,加密套件、一个随机数 发给服务端
    2. 服务端收到请求后,确认客户端的TLS版本,加密套件,然后也生成一个随机数,与证书和公钥一起发给客户端
    3. 客户端收到服务端的证书、公钥,再生成一个随机数(也称预主密钥),使用刚刚收到的公钥进行加密后发送出去
    4. 服务端收到预主密钥后,用私钥进行解密,得到预主密钥的值
    5. 最后,客户端用预主密钥,使用第1步和第2步的随机数计算出会话密钥
  4. 建立会话密钥:客户端通过服务器公钥加密会话密钥发送给服务端,服务端用自己私钥解密得到会话密钥,用于接收和发送数据,之后传输的http数据都是经过加密的。

    每个会话会生成一个会话密钥,每个会话的会话密钥均不同。

总结:非对称加密的手段传递密钥,然后用密钥进行对称加密传递数据,密钥由客户端生成,每次会话密钥都不一样,CA会保存服务端提供的非对称加密的公钥并签名;

过程中,客户端会产生两个随机数,第一个用于制作会话密钥,第二个用于验证公钥是否可用;服务端会生成一个随机数,给客户端制作会话密钥;

RSA非对称加密

例子:

公钥为(7,33)
假设源数据翻译成十进制为:3,1,15
对其求7次幂为:2187,1,170859375
对其求33的余:9,1,27
得到密文:9,1,27

私钥为(3,33)
得到密文:9,1,27
对其求3次幂为:729,1,19683
对其求33的余:3,1,15
得到明文:3,1,15

设公钥为(e,n),私钥为(d,n)
即 明文^e%n=密文,密文^d%n=明文

公钥和私钥的制作过程

  1. 生成两个质数:p和q
  2. 两个质数相乘:N = p * q
  3. 使用欧拉函数计算:T = (p-1) * (q-1)
  4. 选出公钥,条件:质数 && 1 < 公钥 < T,不是T的因子,即E
  5. 计算私钥,条件:(D * E)% T = 1

当p和q特别大时,生成的T和N都非常大,即使公开N,也很难暴力算出p和q,所以破解非常困难

HTTP 2.0

HTTP2.0建立在Https协议的基础上,支持二进制流而不是文本,支持多路复用而不是有序阻塞,支持数据压缩减少包大小,支持server push等特性,低延时,高性能。

二进制分层帧

  • 帧:包含 类型Type、长度Length、标记Flags、流标识Stream和载荷frame payload
  • 消息:一个完整的请求或响应,由一个或多个帧组成

流是连接中的一个虚拟信道,可以承载双向消息传输。每个流有唯一整数标识符。为了防止两端流ID冲突,客户端发起的流具有奇数ID,服务器端发起的流具有偶数ID。

流标识是描述二进制frame的格式,使得每个frame能够基于http2发送,与流标识联系的是一个流,每个流是一个逻辑联系,一个独立的双向的frame存在于客户端和服务器端之间的http2连接中。一个http2连接上可包含多个并发打开的流,这个并发流的数量能够由客户端设置。

在二进制分帧层上,http2.0会将所有传输信息分割为更小的消息和帧,并对它们采用二进制格式的编码将其封装,新增的二进制分帧层同时也能够保证http的各种动词,方法,首部都不受影响,兼容上一代http标准。其中,http1.X中的首部信息header封装到Headers帧中,而request body将被封装到Data帧中。

多路复用

连接是持久的,客户端和服务器之间只需要一个连接,每个数据流可以拆分成很多不依赖的帧,这些帧可以乱序发送,也可以分优先级,多个流的数据包能够混合在一起通过同样的连接传输,服务端在根据不同帧首部的流标识进行区分和组装。

头部压缩

http1.x的头带有大量信息,而且每次都要重复发送。http/2使用encoder来减少需要传输的header大小,通讯双方各自缓存一份头部字段表,既避免了重复header的传输,又减小了需要传输的大小。

各自缓存之后,之后发送的请求如果不包含首部,就会自动使用之前请求发送的首部,如果首部发生变化,则只需将变化的部分加入到header帧中,改变的部分会加入到头部字段表中,首部表在HTTP2.0的连接存续期内始终存在,由客户端喝服务端共同渐进式更新。

请求优先级

将HTTP消息分为很多独立帧之后,就可以通过优化这些帧的交错喝传输顺序进一步优化性能,服务端也可以根据流的优先级,优先将最高优先级的帧发送给客户端。

服务端推送

服务端可以对一个客户端请求发送多个响应,而无需客户端明确地请求,省去客户端重复请求的步骤

TCP(Transmission Control Protocol 传输控制协议)

特点

  • 面向连接的,提供可靠交付,丢包重传,有状态服务
  • 有流量控制,拥塞控制
  • 提供全双工通信
  • 面向字节流(把应用层传下来的报文看成字节流,把字节流组织成大小不等的数据块)
  • 头部20字节
  • 每一条 TCP 连接只能是点对点的(一对一)。

应用场景

  • HTTP、FTP、SSH、SMTP

连接与关闭连接

SYN:发起一个新连接

ACK:回复,确认序号有效

FIN:释放一个连接

ack:回复,确认序号,=发送方seq+1

RST:复位标志,需要重新连接

FIN:结束连接

三次握手

三次握手
三次握手

  1. 第一次握手:Client将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给Server,Client进入SYN_SENT状态,等待Server确认。 每一端先发出去的都会有SYN,收到之后会发出ACK。

    如果client发送失败,会周期性进行超时重传,直到收到server的确认。

  2. 第二次握手:Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给Client以确认连接请求,Server进入SYN_RCVD状态。

    如果server发送响应失败,会周期性进行超时重传,直到收到client的确认。

  3. 第三次握手:Client收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给Server,Server检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,可以开始传输数据了。

    client第三次握手,此时client会认为自己已经established,server还未收到,此时仍然为active状态:如果server一直没收到连接请求,server会重复第二次握手,直到自己收到第三次握手的请求,此时server才是established;如果此时client发送了data数据且加上了ACK,server也会切换为established;如果server有数据发送却发送不出,也会重复第二次握手。

三次握手建立连接后,还有一件重要的事情是确定TCP包的序号,每个连接都要有不同的序号,序号的起始号随时间变化,每4ms加一,如果有重复,需要4个多小时后才会出现,因为IP包头里有TTL(生存时间),超过4小时早过期了。

之所以要确认包的序号是因为有可能出现client与server建立连接并进行通信后断开重连,如果每次连接没有新的起始序号,会导致server分辨不出收到的包是这次连接的还是上次连接的。这个号会作为以后的数据通信序号,以保证应用层接收到的数据不会因网络传输问题而乱序。

为什么不能用两次握手进行连接?

一个重要的原因,是双方需要确认好起始的序列号,双方的初始序列号必须保持一致,来保证连接后的可靠传输,即双方都需要确认对方收到了自己的序列号

如果两次握手确定连接,client发送连接请求给server,server接收后发送响应给client,此时连接建立,client可以正常的发送和接收,但是server并不知道自己发送的请求client有没有收到,此时server端无法确认自己的序列号,那它会认为没有确立连接,一直等待client的确认信号,忽略其他数据,server发出的数据被忽略,则会一直发送。

另外,还会有一个问题,如果是两次握手建立连接,如果client在重复发送连接请求给server,server收到连接请求,响应回去,建立连接后,因为序列号问题,那些重复的连接请求包到达了server,此时server端也无法分辨是数据包还是连接包

另外,三次握手是因为第二次握手的时候,server收到client的请求和自己的请求一次性响应回了client,也可以把这一步拆出来,变成四次握手也是可以的。四次挥手的第二和三次不能合并是因为此前连接已建立,贸然关闭会导致部分报文没有接受完成。

四次挥手

四次挥手
四次挥手

  1. 第一次挥手:Client发送一个FIN、一个seq,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。
  2. 第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号ack为收到seq+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。
  3. 第三次挥手:Server发送一个FIN、一个ACK、ack为上面的seq+1、一个seq,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。
  4. 第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态(等待时间设为2MSL,即报文最大生存时间),接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手,Server会比Client早一点关闭TCP连接。

由于TCP连接是全双工的,因此,每个方向都必须要单独进行关闭。这一原则是当一方完成数据发送任务后,发送一个FIN来终止这一方向的连接,收到一个FIN只是意味着这一方向上没有数据流动了,即不会再收到新数据了(但仍然可能收到在途的数据),但是在这个TCP连接上仍然能够发送数据,直到这一方向也发送了FIN。首先进行关闭的一方将执行主动关闭,而另一方则执行被动关闭。

为什么建立连接是三次握手,而关闭连接却是四次挥手呢?

  • 三次握手:发送一次信息就是一次握手,第一次握手确定Client可以发送消息给Server,第二次握手确定Server可以收到Client的信息,并且发送信息给Client,第三次握手Client确定可以收到Server的信息,三次握手是为了防止失效的连接请求到达服务器,让服务器错误打开连接。

  • 四次挥手:第一次挥手Client发送消息确定Client想关闭连接,第二次挥手Server发送消息确定Client可以关闭连接,第三次挥手Server发送消息确定Server想关闭连接,第四次挥手Client发送消息确定Server可以关闭连接,少了哪一次都可能导致没有完全关闭,造成一方可发送或者接收。

之所以要四次,是因为server收到FIN后,不能同时将ACK确认信号和FIN,有可能此时client有一些报文还没收完,如果client没有收到,server才有机会重发,所以第二、三次挥手不能合并。

关闭连接时,当收到对方的FIN报文时,仅仅表示对方不再发送新数据,但对方还能发送旧数据或接收数据,己方也未必把全部数据都发送给对方了,所以己方先发送ACK,告知对方我知道你不会再发送新数据,之后己方还仍然可以发送一些数据给对方,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方的ACK和FIN一般都会分开发送。

挥手时,为什么client在发送最后一个ACK后进入 TIME-WAIT 状态,为什么 TIME-WAIT 状态必须等待2MSL的时间?

  • 主要是为了保证client发送的最后一个ACK报文能到达server。因为这个ACK报文可能丢失,此时server接收不到,那他就会重新发送FIN+ACK报文,client就能在这2MSL内收到这个重传的报文,再次进入2MSL等待时间,发送ACK给server,直到两者都进入closed状态。如果没有2MSL等待时间,而是client发送完报文直接关闭,就会出现server无法接收而导致无法进入closed状态。
  • 如果server超过了2MSL时间依然没收到client的ACK,会再次重发,但此时client会发送RST标志,表示异常关闭连接
  • client在发送完最后一个ACK报文段后,在经过2MSL,可以使本连接持续的时间内所产生的所有报文都从网络消失,用这个时间让这个连接不会和后面的连接混在一起,使得下一个新的连接不会出现旧的连接请求报文。

连接过程中超时怎么办?

server端接收到client发的SYN后回了SYN-ACK后,client掉线,server没有收到client响应回来的ACK,此时连接处于一个中间状态,此时,server端如果一定时间内没有收到client的ACK,则会重试发送SYN-ACK,Linux下重试次数是5次,以2的指数增长,如果5次都超时,总共要等等63s,TCP才会断开连接。

建立连接后出故障了怎么办?

  • TCP设有一个保活计时器,每收到一次请求都会复位这个计时器,如果规定时间(2小时)内没收到,则发送探测报文测试对方是否出现故障,连续10次/75分钟,仍没反应,说明对方故障

  • IP头有一个TTL时间,配合序号可判断该数据包是否过期

包头

TCP包头
TCP包头

  • 端口号:用于找到对应的应用,TCP包是没有IP地址的,因为IP数据是网络层的事,所以这一层只有端口号;

  • 序号:让包能顺序发送和接收,解决乱序问题,也可以区分连接的阶段,区分不同的连接。三次握手除了确立双方建立连接,最重要的是确立双方包的序列号,每一次连接都要有不同的序列号用于区别,序列号的起始通常是随时间,如果每次连接的序列号相同,可能会导致前一次连接的包发送到了下一次连接里;序号的增加和传输的字节数相关;

  • 确认序列号:发出去的数据包时进行确认的标记,解决丢包问题,接收方回复的ACK包的确认序号 = 发送方数据包的序号 + TCP的数据载荷字节数,注意此时的载荷数据可能是这一次报文的完整数据,也有可能是包含了上一次报文的部分数据和此次报文的部分数据;

  • 状态位:客户端和服务端连接的状态,即包的类型,操控TCP的状态机;

  • 窗口大小:解决流量控制问题,标识自己当前的处理能力;

如何实现靠谱协议

  • TCP协议规定在建立连接后,会确定包的序号的起始ID,按照ID一个个发送,对于发送的包会进行应答,应答是累计应答,应答某个ID的包就表示在这个ID之前的包都收到了
  • 在发送端和接收端会分别使用缓存来保存这些包的记录,一般分为四个:1. 已发送并确认的、2. 已发送未确认的、3. 没有发送但准备发送的、4. 没有发送且暂时不会发送的,滑动窗口就是处理第二、三部分的数据包

滑动窗口

窗口大小即自己的数据接收缓冲池的大小

由发送方和接收方在三次握手阶段,互相通知自己的最大可接收的字节数。

当发送方窗口左部字节已发送且收到通知,窗口右滑直到左部第一个字节不是已发送并且已确认的状态,接受方窗口移动同理

接收窗口只会对窗口内最后一个按序到达的字节进行确认,确认之后表示之前的所有字节都接收到了

在处理过程中,当接收缓冲池的大小发生变化时,要给对方发送更新窗口大小的通知。

粘包问题

实际上因为TCP是面向字节流,而字节流本身没有粘包/拆包的概念,TCP层只管提供可靠性消息传输,不然为什么会对网络分层呢,粘包是应用层没有处理好导致

发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。

只有TCP会(通过窗口大小来接收数据,窗口大小又是动态的),而UDP因为有消息边界(头部有规定报文的大小),所以不会

  • 产生的原因

    • 发送端粘包:发送端等到缓冲区满了才发送出去,造成粘包;

    有时为了提高发送数据的效率,服务端会把多个数据块合并成一个大的数据块后封包发送,但由于面向流的通信是无消息保护边界的,接收端就很难分辨出完成的数据包

    • 接收端粘包:接收端不及时接收缓冲区的包,造成多个包接收;
  • 解决方法

    粘包主要就是没有处理好数据包的边界,因此我们可以

    • 发送固定长度的消息;消息尺寸和消息一块发送;特殊标记标记消息区间;
    • 通信双方规定好协议 + 编解码器(比如规定定长的请求头、请求体),按协议的格式进行解析;
    • 程序控制发送和接收频率;

TCP粘包问题分析和解决(全)

丢包问题:

发送方按顺序发送一系列的包,接收方接收这些包是中间一部分包没收到

  • 产生原因
    • 中间这些包经过其他链路导致延时接收方还没收到;
    • 接收方收到且发送了ACK给发送方,但是发送方没有收到ACK;
    • 数据链路有点问题,数据包真的丢了;
    • 因为顺序发送和接收问题,接收方后面的包收到了,但是前面的包还没收到,导致后面的包收到了也不能发送ACK给发送端;

所以,为了保证可靠传输,解决丢包问题,就需要保证当报文丢失,在一定次数内持续重试,直到成功,又要确保如果重传都失败了,及时放弃传输,避免死循环得问题。

  • 解决方法

    • 超时重传(Retransmission Timeout,RTO),超时重传有一个计时器,在报文发送出去后开始计时,如果在时限内收到回复的ACK,计时器就清零,如果在时限内还没收到ACK,就触发重传。

      超时时间由自适应重传算法来决定,另外是每次重试的时间间隔会加倍,超过两次认为网络环境差,取消重传。超时重传主要解决丢包时干等回复ACK的问题

      RTO初始值是1秒,建立连接后,RTO会被动态计算,上限是2min,下限是200ms。RTO的设置尤为关键,设置过长,重发就慢,效率低性能差;设置过短,就会导致没有丢就重发,重发过快,就会增加网络拥塞,导致更多的超时,从而又导致更多的重发,因此无法设置成一个写死的值,而是通过RTT算法动态调整。

      由于ACK本身没有ACK了,如果ACK发生丢失,ACK会视情况重传,比如,如果发送方收到了后续的ACK,就说明前面的内容都接收到了,就不会重传;如果没收到后续的ACK,则触发超时重传机制。

    • 快速重传机制,因为包是有顺序的,所以客户端可以检测出缺失的包,然后发送对应的冗余ACK(称为DupAck)给服务端,通过在服务端设置收到规定通过发送/接收冗余的ACK包的次数(一般是3个),如果达到了规定次数就把序号等于这个ACK号的包进行重传,不等超时时间,so 快速重传主要是解决超时等待过久的问题

      快速重传的触发条件是:收到3个或以上的重复ACK,即DupACK

      比如:接收端中间漏收了Seq2,后面又接收到了Seq3、4、5,那就会在接收时重复发送ACK2,发送端收到重复的ACK后就会重新发送Seq2了

      接收端接收的报文只有字节的长度,回复报文时的确认号,只能是连续字节数的最后一个位置,如果有相同长度的报文ABC按顺序发送给接收端,但是A丢了,接收端收到BC后会回复3个相同长度的确认号,导致发送方得把ABC重传一遍,SACK就是为了解决这个问题存在。

      在Linux中的SACK机制,它会在TCP头里加一个SACK(Selective ACK)的东西,SACK还是基于快速重传的ACK,只是SACK会把接收端收到的所有包的序列,都反馈给发送端,发送端根据遗漏的ACK序号,进行重传。SACK部分最多只能容纳4个块。

      另外,还有个D-SACK(Duplicate SACK),使用SACK来告诉发送方有哪些数据被重复接收了,其使用SACK的第一个段来做标志:

      • 如果SAKC的第一个段范围被ACK覆盖,就是D-SACK

      • 如果SACK的第一个段的范围被SACK的第二个段覆盖,就是D-SACK。

      引入D-SACK可以让发送方知道是发出去的包丢了,还是回来的ACK包丢了;还是发送方的超时太短,导致重传;还是先发出去的包后到的情况,还是数据包被复制了。

流量控制

TCP需要解决可靠传输和包的乱序问题,就需要知道网络实际的数据处理带宽或数据处理速度,才不会引起网络拥塞,导致丢包,so需要做流量控制。在TCP的包头中通过Window字段控制,这个字段是接收端每次ACK时会告诉发送端自己还有多少缓冲区可以接收数据,于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来。滑动窗口需要依赖发送端和接收端。

实现滑动窗口,发送端和接收端在进行数据交互时,会商定滑动窗口的大小,在对于包的确认时会携带一个窗口的大小,通过该窗口大小来实现流量控制(或者时不时发送一个窗口探测的数据段来确认双方的窗口大小),当发送方和接收方的滑动窗口大小为0时,发送方会定时发送窗口探测数据包,来更新窗口大小,即Zero Window。

  • Zero Window:当滑动窗口的大小变成0,意味着发送端不发数据了,当接收方的滑动窗口可以更新时,会使用Zero Window Probe来通知发送端可以发送数据,流程是当发送端在窗口变成0后,会发ZWP包给接收方,让接收方重新确定滑动窗口的大小,这个值会被设置成3次,每次大约30-60s,如果3次过后还是0,可能就会断开连接了。

  • Silly Window Syndrome:如果接收方太忙,来不及取走滑动窗口里的数据,就会导致发送方可发送的数据越来越小,如果每次发送的数据太小,带宽没有占用,实际上是很亏的,这种现象就叫Silly Window Syndrome。

    解决方法有多种,一种是如果这个问题是接收端引起的,当收到的数据导致滑动窗口的大小小于某个值,就直接ack(0)回发送端,把滑动窗口关闭,等接收方的滑动窗口大于某个值是,才把滑动窗口打开,让发送端发数据过来;另一种是如果这个问题是发送端引起的,就延时处理,攒多点数据一块发。

拥塞控制

  • 产生原因:对资源的需求超过了可用的资源,网络吞吐量下降,如果网络出现拥塞,数据包将会丢失或延迟到达,发送方以为发送失败又会继续重传,从而导致网络拥塞程度更高。

TCP通过数据包发送和确认的往返时间RTT,丢包率来判断是否拥塞,使用滑动窗口来进行拥塞控制,控制发送方的发送速率,避免包丢失和超时重传。

  • 解决方法:

    • 慢开始 + 拥塞避免,一开始慢慢的发送,逐渐增大发送速率(线性上升直至网络最佳值),再慢下来依次重复。

      慢开始阶段,TCP连接每收到一个数据的ACK(不包括重复的ACK),拥塞窗口就增加一个MSS(最大报文大小),即一个RTT后MSS翻倍增加,当达到了慢开始的阈值,增长速度就会放缓,进入拥塞避免阶段,变成了每过一个RTT,拥塞窗口就会增长一个MSS;

      每一个TCP连接独立维护自己的拥塞窗口,拥塞窗口一般比MSS大,一般是MSS的某个倍数,MSS的上限一般是1460,一个窗口就是n个MSS;

      拥塞避免时会重复慢开始A,每次反复都会对拥塞窗口进行减半,在再次进入慢开始+拥塞避免,所以,慢开始不止在TCP连接启动时发生,也有可能在传输过程中反复发生。

      慢开始+拥塞避免
      慢开始+拥塞避免

    • 快重传 + 快恢复,当拥塞发生时,减少超时重传的使用,而是使用快速重传机制

      TCP每发送一个报文,就会启动一个超时计时器,如果在限定时间内没有收到这个报文的ACK,发送方就会认为报文丢失,此时就会进行超时重传,一般最小的超时重传时间是200ms,为了解决每次丢包都要等待200ms或者更长的时间才会重传的问题,TCP就会采用快速重传,一旦发送方收到3次重复确认,就不用等超时计时器了,会直接重传这个报文。

      在Reno拥塞控制算法中,TCP在遇到拥塞点后,不会反复进行慢开始+拥塞避免,而是拥塞窗口减半,直接进入快速重传,不进入慢开始,保持跟拥塞避免一样的线性增长,直到下一个拥塞点。

      网络上的限速,原理其实就是设置拥塞窗口的上限,当超过限制,就会丢弃这些报文,主动进入拥塞避免阶段,确保传输速度。

      快恢复
      快恢复

    为了实现上面两种机制,TCP使用BBR拥塞算法,来达到高带宽和低延时的平衡

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

流量控制是为了让接收方能来得及接收,而拥塞控制是为了降低整个网络的拥塞程度,此时可以把网络链路想象成水管。

参考:https://coolshell.cn/articles/11564.html、https://coolshell.cn/articles/11609.html

UDP(User Datagram Protocol 用户数据报协议)

特点

  • 不可靠的、无连接的,尽最大可能交付,只负责发送数据,无状态服务
  • 没有拥塞控制,流量控制
  • 面向报文(对于应用程序传下来的报文不合并也不拆分,只是添加 UDP 首部),一个一个地发,一个一个地收
  • 头部只有8字节
  • 支持一对一、一对多、多对一和多对多的交互通信。

应用场景

  • 针对网络资源少,对丢包不敏感的应用,比如应用层的DHCP,在获取IP地址和子网掩码的使用
  • 需要广播的应用,比如DHCP、VXLAN
  • 需要处理速度快,时延低,容忍丢包、网络拥塞的应用,比如直播、视频,允许丢包,虽然丢包会导致丢帧,但影响不会很大;实时游戏、物联网终端的数据收集,其实大多数会基于UDP做一定的改进,减少UDP劣势的影响

包头

UDP包头
UDP包头

UDP包头比较简单,两端通信时,通过网络层里的IP,将数据包发送给对应的机器,IP头里有个8位协议,表明该数据是UDP协议的,解析到传输层,通过UDP包头提供端口号,让目标机器监听该端口号的应用程序进行处理

使用场景

  • 网络环境较好,如内网应用,或者对于丢包不敏感的应用
  • 需要广播或多播
  • 需要处理速度快,时延低,容忍丢包和网络拥塞

UDP如何实现TCP

建立连接,是为了在客户端和服务端维护连接,而建立一定的数据结构来维护双方交互的状态,通过这样的数据结构来保证面向连接的特性

UDP属于传输层,协议已经定死了,要实现TCP的功能只能在应用层实现,模拟TCP有的那些功能:确认机制、重传机制、窗口确认机制、流量控制、拥塞控制等那些功能。

UDP实现可靠性,可以简单理解成,将TCP的三次握手发送数据全程用UDP去发送,模拟TCP的包头

  • seq/ack机制,确保数据发送到对端
  • 数据包 + 序号,确保有序性
  • 数据包 + 确认序号,确保不丢包
  • 添加发送和接收缓冲区,主要是用户超时重传。
  • 定时任务实现超时重传机制。

发送端发送数据时,生成一个随机seq=x,然后每一片按照数据大小分配seq。数据到达接收端后接收端放入缓存,并发送一个ack=x的包,表示对方已经收到了数据。发送端收到了ack包后,删除缓冲区对应的数据。时间到后,定时任务检查是否需要重传数据

如何实现UDP的可靠传输

套接字Socket

Socket是对TCP或UDP协议的封装,本质上是一个调用接口,而非协议,工作在OIS模型的第五层(会话层)

  • 基于TCP协议的Socket

基于TCP的Socket
基于TCP的Socket

  • 基于UDP协议的Socket

基于UDP的Socket
基于UDP的Socket

服务端如何管理这些连接和资源?

  • 父进程使用子进程来管理连接和资源,子进程在完成连接和数据通信后告诉父进程进行回收
  • 线程池 + 连接池,每个线程管理一个socket,连接池实现socket复用
  • IO多路复用,一个线程维护多个socket,如Java NIO、Netty的网络模型

参考

TCP/IP参考1

TCP/IP参考2

极客时间 - 趣谈网络协议

TCP 的那些事儿

Built with Hugo
Theme Stack designed by Jimmy