TCP首部有2个字节表示校验和,如果收到校验和有误的数据包,TCP会直接丢弃数据包,等待重传
每个包的TCP首都都有4个字节的序列号,用来解决乱序和重复问题(根据序列号对收到的包进行正确的排序,再交给应用层;会丢弃掉序列号相同的数据包)
❝序列号回绕 原因:序列号大小为4个字节,当传输的数据超过2^32后,下一个报文的序列号可能会变成比上一个更小。 解决办法:序列号为无符号整数,比较时将(seq2-seq1)转成有符号再比较,即可正确判断大小 ❞
❝发送数据包后,会启动一个定时器,如果在一定时间(RTO 超时重传的时间)内没有收到对端的ACK确认,会进行重传,称为超时重传 当发送端收到3个或以上相同的ACK包时,就意味着之前有报文丢失了,会立刻进行重传,称为快速重传 ❞
❝最大重传次数由/proc/sys/net/ipv4/tcp_retries2决定,但实际重传次数会受网络情况RTT大小影响 重传间隔采用指数退避算法,即重试等待间隔越来越长 tcp会根据tcp_retries2计算出一个timeout值,如果数据包第一次发送的时间距离现在的时间间隔,超过了timeout值,就会丢弃,不再重传,所以,在rtt较小情况下,重传次数由tcp_retries2决定,传了retries2次,时间也就超过了timeout值,会进行丢弃;但是,在rtt较大情况下,不需要重传retries2次,就已经到了timeout时间,就会将数据包丢弃了 ❞
❝接收端使用SACK来记录自己接收到的数据包的序列号范围,发送端通过这个可以知道需要重传哪些数据包 ❞
❝数据包到接收端的接收缓冲区后,应用程序从缓冲区读取数据,但可能由于应用程序处理速度较慢,导致接收缓冲区被占满了,这个时候发送端就应该得知道接收端的这个情况,并等待接收端接收缓冲区有空闲空间之后再继续发送数据 ❞
❝(接收端)接收窗口 接收端收到报文后,会在ACK报文中带上当前接收端接收缓冲区剩余空闲空间大小,这个就是接收窗口 ❞
❝(发送端)发送窗口/滑动窗口 ❝在发送端角度上看,数据包根据发送状态和确认状态可以分为4类:
❝发送窗口 = 区域2 + 区域3 ❞
❝双方在三次握手过程中告知彼此自己的接收窗口大小(也就是确认了区域2+区域3的大小),然后在传输过程中,发送端根据收到的ACK报文中的确认号和当前接收窗口,相应的滑动发送窗口,并调整下一次发送数据的量 ❞
❝为了避免造成网络堵塞,发送端需要限制自己的发送数据量;如果拥塞窗口小于接收窗口,则设备可以在等待确认之前传输多达拥塞窗口中定义的字节数。相反,如果接收窗口小于拥塞窗口,则设备可以在等待确认之前最多传输接收器窗口中定义的字节数。 ❞
❝接收方收到乱序报文,发送方快速重传 ❝
❞ 网络堵塞,发送方收不到ACK ❝
❞ ❞
MTU是链路层的概念,网络传输中的数据包大小受以太网的帧大小限制,最大帧是1518,最小帧是64,去掉头部和CRC校验字段,剩下的大小就是链路层的有效荷载,而该网卡支持的最大有效荷载就是MTU
受MTU的影响,需要在发送方限制数据包的大小,即将原本较大的数据包进行分段处理,考虑到在TCP/IP各层中,只有传输层有重传机制,在传输过程中,分段发生丢失、损坏时,可以通过TCP的重传机制保证接收方能收到完整的数据包,所以分段的工作应该由传输层完成。换句话说,由于MTU的存在,TCP传输层每次发送的数据包大小也收到了影响,这个值就是MSS(max segment size),表示TCP能发送的最大报文段:MSS = MTU - IP首部 - TCP首部
MSL是 TCP 报文在网络中的最大生存时间。这个值与 IP 报文头的 TTL 字段有密切的关系;TTL是一个 IP 报文最大可经过的路由数,每经过一个路由器,TTL 减 1,当 TTL 减到 0 时这个 IP 报文会被丢弃
❝l_linger=0❝
❞l_linger=xxx❝
服务端主动断开连接后,连接需要等待2MSL后才会释放,在此期间,启动服务会报「Address already in use」错误,开启SO_REUSEADDR后,可以解除这个限制
SO_REUSEADDR对FIN_WAIT2和TIME_WAIT连接都生效,值得注意的是,由于对FIN_WAIT2状态也允许端口复用,所以,重启后的服务程序有可能收到非期望数据
「作用」
「底层实现原理」内核将listen状态的socket存放在32个槽位的哈希桶中,相同hash值的端口存放在同一个槽中,使用链表存放槽中不同端口的socket,当有请求时,先定位到哈希槽,然后遍历链表,对每个socket进行打分,取出得分最高的socket进行处理 (linux内核<4.5)对于启用了SO_REUSEPORT的socket,遍历链表后最高得分的socket会有多个,然后通过随机算法取出其中一个. (linux内核>=4.5) 由于每次请求都要遍历一遍链表,效率较低,所以引入了SO_REUSEPORT group,找到匹配的socket后,进行二次哈希找到对应的group组,从中选择一个进行请求处理
「原理」
如果当前有【已发送未确认】的数据报文时,TCP会先将待发送数据先放到缓冲区,直到数据包大小达到MMS时,才会进行发送
如果之前发送的数据包都已经收到ACK了,会立刻发送数据包
「优缺点」 优点: 在网络延迟较高情况下,能有效避免大量的小包在网络中进行传输,提高带宽利用率(每个报文的有效数据较多) 缺点: 因为数据包可能会进行合组再一起发送出去,所以客户端传输数据会有一定延迟,对于需要实时预览的应用程序(ssh),nagle算法不太适用 默认开启,可在服务端通过设置TCP_NODELAY进行关闭
「延迟确认」 收到数据包后,不会立刻返回ACK,会等待一段时间再确认,如果这段时间本端刚好有数据要传给对端,ACK可以随着数据一起发送出去,如果一段时间后还没有数据要传给对端,也会返回ACK确认 「立刻回复的场景」
当一端未发送数据时,该端默认将该连接视为非交互式连接(not pingpong),当该端发送数据后,该端会将该连接视为交互式,当延迟确认时由于超时返回ack(定时器内本端没有数据需要传给对端)时,该端又会将连接变为非交互式.
TCP首部
各占2个字节,用来标示不同的应用程序,主机收到数据包后根据不同的目的端口号将数据包传递给不同的应用程序处理
❝保留端口:范围是0-1023,要监听这些端口需要root权限 已登记端口:范围是1024~49151,普通用户也能监听的端口范围 临时端口:一般客户端去连接服务端服务的时候,系统会为该连接分配一个临时端口(源端口),在 Linux 上能分配的端口范围由 /proc/sys/net/ipv4/ip_local_port_range 变量决定,在需要主动发起大量连接的服务器上(比如网络爬虫、正向代理)可以调整 ip_local_port_range 的值,允许更多的可用端口❞
每个包的TCP首都都有4个字节的序列号;序列号指的是报文段第一个字节的序列号;在SYN报文中的序列号称为初始序列号(ISN),用来交换连接双方的初始序列号;其他报文中的序列号用来解决乱序和重复问题
占4个字节;收到数据包后,TCP会发送ACK(确认号),ACK的值是下次希望收到的序列号值;确认号有两个作用:1、告知发送方序列号小于ACK的报文段都收到了;2、通知发送方下次应该要发送序列号为多少的报文
❝TCP首部只有16位表示窗口大小,也就是最大窗口大小才65535个字节,但有些报文的大小已经远远超过了65535个字节,所以引入了「窗口缩放」选项的比例因子,可选的值为0-14,表示将窗口扩大到原来的n^2倍,所以,实际的报文大小为「窗口大小」* (「窗口缩放」^2)❞
❝MSS: 最大段大小选项,是 TCP 允许的从对方接收的最大报文段 SACK: 选择确认选项 Window Scale: 窗口缩放选项❞
❝「降低三次握手带来的性能消耗的手段」 重用同一个tcp连接,避免重复创建和销毁 TCP快速打开(TFO, 在握手过程中传输数据)❝
第一次过后,客户端就有了缓存在本地的 cookie 值,后面的握手和数据传输过程如下: 1.客户端发送 SYN 数据包,里面包含数据和之前缓存在本地的 Fast Open Cookie。(注意我们此前介绍的所有 SYN 包都不能包含数据)
「客户端和服务端要求」 在Linux支持TFO的内核版本下(Client内核版本为3.6;Server内核版本为3.7),在sysctl.config(vim /etc/sysctl.conf)中添加:net.ipv4.tcp_fastopen = 3, 其中1表示客户端开启,2表示服务端开启,3表示客户端和服务器同时开启
❝「半连接队列」 当客户端发起 SYN 到服务端,服务端收到以后会回 ACK 和自己的 SYN。这时服务端这边的 TCP 从 listen 状态变为 SYN_RCVD (SYN Received),此时会将这个连接信息放入「半连接队列」;服务端发送ACK+SYN后,会开启一个定时器,如果超时还没收到ACK,将会进行重传,重传的次数由tcp_synack_retries参数决定 半连接满后,服务端会拒绝新来的请求❞
❝「全连接队列」 服务端发送ACK+SYN并收到客户端的ACK后,连接会从半连接队列移到全连接队列中,等待应用调用accept取走,应用调用 accept() 函数会移除队列头的连接 全连接满后,服务端会丢弃客户端发来的ack(此时服务端会认为连接未建立成功,会重传ACK+SYN)❞
❝「原理」客户端大量伪造 IP 发送 SYN 包,服务端回复的 ACK+SYN 去到了一个「未知」的 IP 地址,这些处于SYN_RCVD的连接占满服务端的半连接队列大小,导致服务端无法处理其他正常请求❞
❝「应对方案」减少 SYN + ACK 的 重试次数;及时将这些连接从半连接队列中清除出去 使用 tcp_syncookies 机制 ❝原理:服务端收到 SYN 包以后不会立刻将连接放到半连接队列中,而是根据这个 SYN 包计算出一个 Cookie 值,作为握手第二步的序列号回复 SYN+ACK(服务端并不保存cookie),等对方回应 ACK 包时校验回复的 ACK 值是否合法,如果合法才三次握手成功,才将其放入全连接队列中等待处理 /proc/sys/net/ipv4/tcp_syncookies 默认 为1, 表示队列满时启动❞❞
❝「int close(int sockfd)」 close会关闭两个方向的数据流 ❝读方向上,内核会将套接字设置为不可读,任何读操作都会返回异常; 输出方向上,内核会尝试将发送缓冲区的数据发送给对端,之后发送fin包结束连接,这个过程中,往套接字写入数据都会返回异常。 若对端还发送数据过来,会返回一个rst报文❞ ⚠️套接字会维护一个计数,当有一个进程持有,计数加一,close调用时会检查计数,只有当计数为0时,才会关闭连接,否则,只是将套接字的计数减一❞
❝「int shutdown(int sockfd, int howto)」 shutdown显得更加优雅,能控制只关闭连接的一个方向 ❝
howto = 0
关闭连接的读方向,对该套接字进行读操作直接返回EOF;将接收缓冲区中的数据丢弃,之后再有数据到达,会对数据进行ACK,然后悄悄丢弃。howto = 1
关闭连接的写方向,会将发送缓冲区上的数据发送出去,然后发送fin包;应用程序对该套接字的写入操作会返回异常howto = 2
0+1各操作一遍,关闭连接的两个方向。❞ ⚠️shutdown不会检查套接字的计数情况,会直接关闭连接❞
❝「为什么需要在TIME_WAIT等待一段时间」 避免新连接(使用同一个五元组的连接)收到旧连接的数据包,造成数据混乱 保证在ACK丢失后,可以进行重传,保证被动关闭连接端可以正常关闭连接(LAST_ACK->CLOSE)❞
❝「等待的时间为什么是2MSL」 保证新连接肯定不会收到旧连接的报文(因为报文在网络中最多生存1MSL) 在主动关闭方发送ACK后,被动关闭方正常情况下1个MSL内肯定可以收到,否则,被动关闭方会重发FIN包,主动关闭方会在1MSL收到,所以,这一来一回最久就是2MSL了❞
❝「TIME_WAIT连接太多会有什么问题」 ❝场景:客户端主动断开连接后立刻进行重连服务器,会导致客户端上有大量的TIME_WAIT状态 影响:客户端上临时端口不够用(大量端口处于TIME_WAIT)❞ ❝场景: 服务端主动断开连接,然后客户端立刻重连,如此往复,在服务端上会有大量的TIME_WAIT状态连接 影响:
❝「TIME_WAIT太多时的场景及解决办法」 使用nginx等负载均衡连接后端服务,客户端断开连接后,nginx也会断开与后端服务的连接,导致nginx上存在大量的TIME_WAIT ❝调整net.ipv4.ip_local_port_range参数,增加临时端口的数量 使用连接池连接后端服务 添加nginx机器数量 添加nginx的配置ip数量 在nginx机器上启用tcp_tw_reuse参数❞ 服务端主动断开连接,导致服务端上有大量的TIME_WAIT ❝启用tcp_tw_recycle参数(慎用!确保客户端不是nat环境)❞❞
❝「相关调优参数」 net.ipv4.tcp_timestamps ❝属于tcp头部选项字段,由类型、长度、发送时间戳、回显时间戳4部分构成,共10个字节 需要连接双方都开启才能工作, 是否使用该特性是在三次握手中的SYN报文中协商确定的
❝net.ipv4.tcp_tw_reuse ❝需要开启net.ipv4.tcp_timestamps 用于客户端主动断开连接,在客户端机器上启用 重用连接后,会更新连接的时间,收到时间戳小与新连接时间的数据包都会被丢弃(解决了新连接收旧连接数据导致数据混乱的问题) 重用time_wait连接流程(把处于TIME_WAIT的主动连接/断开端称为A,对端成为B) ❝情况一:旧连接ACK未丢失,还在传输过程中,导致B还没收到ACK,处于LAST_ACK状态
情况二:旧连接ACK丢失,导致B还没收到ACK,处于LAST_ACK状态
A发送SYN报文,处于SYN_SENT状态
B由于迟迟没有收到ACK,所以重传FIN报文
A收到FIN后,回复一个RST报文
A没有收到SYN的ACK,所以会进行重传,之后就就是正常的三次握手了
情况三:B处于CLOSE状态 正常三次握手 net.ipv4.tcp_tw_recycle 需要开启net.ipv4.tcp_timestamps 用于服务端主动断开连接,在服务端机器上启用 开启后,tcp会快速回收处于TIME_WAIT的连接,并且记录下最后一次收到数据包的时间戳,之后在这个连接上如果收到早于这个时间戳的数据包,会直接丢弃 ⚠️如果是处于NAT网络或使用负载均衡连接后端服务的情况下,从服务端的角度看,是一个IP(负载均衡等代理)与它建立大量的连接,代理(负载均衡)很可能会使用「服务端还处于TIME_WAIT的socket」去建立新连接,这时候如果新连接中的时间戳比服务端记录的早,就会导致创建失败了(各客户端的时间可能不会百分百同步)
http://www.ruanyifeng.com/blog/2016/08/http.html
不需要访问服务器,直接使用本地磁盘/内存资源缓存
访问服务器,由服务器决定是否使用浏览器上的缓存
「流程」
expires/cache-control
max-age:记录了缓存有效期,相对时间 缓存策略:
优先级:cache-control > expires Last-Modified/Etag
2. Etag是1.1的规范,记录资源的hash值
优先级:Etag > Last-modified
优先级:If-None-Match > If-Modified-Since
https连接建立
「流程」