TCP/IP

本文为小林coding图解计算机网络系列tcp部分的学习总结,原文请移步https://blog.csdn.net/qq_34827674/article/details/105331617

基本认识

数据格式

    TCP头的格式如图,其中标色部分是与tcp连接和断开相关的字段

TCP/IP

 序列号:在建立连接时由计算机生成的随机数作为其初始值。用来解决网络包乱序问题。

 确认应答号:指下一次「期望」收到的数据的序列号,发送端收到这个确认应答以后可以认为在这个序号以前的数据都已经被正常接收。用来解决不丢包的问题。

  •  ACK:该位为 1 时,「确认应答」的字段变为有效,TCP 规定除了最初建立连接时的 SYN 包之外该位必须设置为 1 。
  • RST:该位为 1 时,表示 TCP 连接中出现异常必须强制断开连接。
  • SYN:该位为 1 时,表示希望建立连接,并在其「序列号」的字段进行序列号初始值的设定。
  • FIN:该位为 1 时,表示今后不会再有数据发送,希望断开连接。当通信结束希望断开连接时,通信双方的主机之间就可以相互交换 FIN 位为 1 的 TCP 段。

四元组

    TCP/IP的四元组可以唯一确定一个连接,他们是:原地址,源端口,目的地之,目的端口。(端口是对应的进程)

    通常一个端口对应的最大连接数为:

TCP/IP

     在IPv4中,客户端IP最多为2^32,客户端的端口最多为2^16,所以最大连接数为2^48。实际上最大连接数远远达不到理论值,主要是一下两点原因:

  • 首先主要是文件描述符限制,Socket 都是文件,所以首先要通过 ulimit 配置文件描述符的数目;
  • 另一个是内存限制,每个 TCP 连接都要占用一定内存,操作系统的内存是有限的。

TCP连接建立——三次握手

三次握手的连接过程

TCP的连接过程如图所示:    

TCP/IP

    最开始服务端处于监听状态(监听对象是端口),客户端开始进行连接请求

  1. 客户端会随机初始化序号(client_isn),将此序号置于 TCP 首部的「序号」字段中,同时把 SYN 标志位置为 1 ,表示 SYN 报文。接着把第一个 SYN 报文发送给服务端,表示向服务端发起连接,该报文不包含应用层数据,之后客户端处于 SYN-SENT 状态。
  2. 服务端收到客户端的 SYN 报文后,首先服务端也随机初始化自己的序号(server_isn),将此序号填入 TCP 首部的「序号」字段中,其次把 TCP 首部的「确认应答号」字段填入 client_isn + 1, 接着把 SYN 和 ACK 标志位置为 1。最后把该报文发给客户端,该报文也不包含应用层数据,之后服务端处于 SYN-RCVD 状态。
  3. 客户端收到服务端报文后,还要向服务端回应最后一个应答报文,首先该应答报文 TCP 首部 ACK 标志位置为 1 ,其次「确认应答号」字段填入 server_isn + 1 ,最后把报文发送给服务端,这次报文可以携带客户到服务器的数据,之后客户端处于 ESTABLISHED 状态。

  4. 服务器收到客户端的应答报文后,也进入 ESTABLISHED 状态。

    由客户端先发起连接请求,将SYN置一,然后初始化序列号,发送服务端,服务端的ACK置为1,确认应答设置为客户端的序列号+1,并将服务端的SYN设为1,初始化服务端的序列号,在发送给客户端,客户端ACK置1,确认应答填上服务端传来的序列号+1。至此,客户端和服务端的syn和ack都设置为1,序列号和确认应答也都有数值。

三次握手原因

1、可以防止旧的重复连接初始化造成混乱

TCP/IP

     如果历史连接被服务端接受,服务端会放回syn+ack给客户端,客户端可以根据自身的上下文判断这个是否是历史连接(序列号过期或者超时),如果是会发送RST终止这次连接。只有两次的话服务端无法确定这个连接是否是历史链接。

2、同步双方序列号

TCP 协议的通信双方, 都必须维护一个「序列号」, 序列号是可靠传输的一个关键因素,它的作用:

  • 接收方可以去除重复的数据;
  • 接收方可以根据数据包的序列号按序接收;
  • 可以标识发送出去的数据包中, 哪些是已经被对方收到的;

可见,序列号在 TCP 连接中占据着非常重要的作用,所以当客户端发送携带「初始序列号」的 SYN 报文的时候,需要服务端回一个 ACK 应答报文,表示客户端的 SYN 报文已被服务端成功接收,那当服务端发送「初始序列号」给客户端的时候,依然也要得到客户端的应答回应,这样一来一回,才能确保双方的初始序列号能被可靠的同步。

3、避免资源浪费

    如果客户端的请求被阻塞重发,由于没有客户端确认的第三次握手,所以服务端不知道自己的ACK有没有被受到,所以只能每受到一个syn就建立一个连接。

TCP/IP

     TCP 建立连接时,通过三次握手能防止历史连接的建立,能减少双方不必要的资源开销,能帮助双方同步初始化序列号。序列号能够保证数据包不重复、不丢弃和按序传输。

半连接队列和全连接队列

    

TCP/IP

     

正常流程:

  • 当服务端接收到客户端的 SYN 报文时,会将其加入到内核的「 SYN 队列」;
  • 接着发送 SYN + ACK 给客户端,等待客户端回应 ACK 报文;
  • 服务端接收到 ACK 报文后,从「 SYN 队列」移除放入到「 Accept 队列」;
  • 应用通过调用 accpet() socket 接口,从「 Accept 队列」取出连接。

 TCP连接的其他问题

1、为什么客户端和服务端的初始序列号 ISN 是不相同的

    如果一个已经失效的连接被重用了,但是该旧连接的历史报文还残留在网络中,如果序列号相同,那么就无法分辨出该报文是不是历史报文,如果历史报文被新的连接接收了,则会产生数据错乱。

所以,每次建立连接前重新初始化一个序列号主要是为了通信双方能够根据序号将不属于本连接的报文段丢弃。

2、初始序列号 ISN 是如何随机产生的?

起始 ISN 是基于时钟的,每4毫秒 + 1。

RFC1948 中提出了一个较好的初始化序列号 ISN 随机生成算法。

ISN = M + F (localhost, localport, remotehost, remoteport)

M 是一个计时器,这个计时器每隔 4 毫秒加 1。
F 是一个 Hash 算法,根据源 IP、目的 IP、源端口、目的端口生成一个随机数值。要保证 Hash 算法不能被外部轻易推算得出,用 MD5 算法是一个比较好的选择。

3、既然 IP 层会分片,为什么 TCP 层还需要 MSS 呢?

  • MTU:一个网络包的最大长度,以太网中一般为 1500 字节;
  • MSS:除去 IP 和 TCP 头部之后,一个网络包所能容纳的 TCP 数据的最大长度;

    IP层进行分片,保证每片都小于MTU,但是如果一个分片丢失,IP报文的所有分片都得重传,但是IP没有重传机制所以需要TCP负责重传。

    为了达到最佳的传输效能 TCP 协议在建立连接的时候通常要协商双方的 MSS 值,当 TCP 层发现数据超过 MSS 时,则就先会进行分片,当然由它形成的 IP 包的长度也就不会大于 MTU ,自然也就不用 IP 分片了。经过 TCP 层分片后,如果一个 TCP 分片丢失后,进行重发时也是以 MSS 为单位,而不用重传所有的分片,大大增加了重传的效率。

4、什么是 SYN 攻击?如何避免 SYN 攻击?

    攻击者短时间伪造不同 IP 地址的 SYN 报文,服务端每接收到一个 SYN 报文,就进入SYN_RCVD 状态,但服务端发送出去的 ACK + SYN 报文,无法得到未知 IP 主机的 ACK 应答,久而久之就会占满服务端的 SYN 接收队列(未连接队列),使得服务器不能为正常用户服务。

避免 SYN 攻击方式一

    SYN_RCVD 状态连接的最大个数,超出处理能时,对新的 SYN 直接回报 RST,丢弃连接:

避免 SYN 攻击方式二

    上文提到过半连接和全连接队列,如果应用程序过慢,会导致全连接队占满,当收到syn攻击的时候,由于无法就收客户端的ack应答,会导致全连接队列慢慢被应用取走,最后为空。

TCP/IP TCP/IP

     可以使用cookie方式避免syn攻击

TCP/IP

  • 当 「 SYN 队列」满之后,后续服务器收到 SYN 包,不进入「 SYN 队列」;
  • 计算出一个 cookie 值,再以 SYN + ACK 中的「序列号」返回客户端,
  • 服务端接收到客户端的应答报文时,服务器会检查这个 ACK 包的合法性。如果合法,直接放入到「 Accept 队列」。
  • 最后应用通过调用 accpet() socket 接口,从「 Accept 队列」取出的连接。

TCP连接断开——四次挥手

TCP/IP

  •     客户端打算关闭连接,此时会发送一个 TCP 首部 FIN 标志位被置为 1 的报文,也即 FIN 报文,之后客户端进入 FIN_WAIT_1 状态。
  • 服务端收到该报文后,就向客户端发送 ACK 应答报文,接着服务端进入 CLOSED_WAIT 状态。
  • 客户端收到服务端的 ACK 应答报文后,之后进入 FIN_WAIT_2 状态。
  • 等待服务端处理完数据后,也向客户端发送 FIN 报文,之后服务端进入 LAST_ACK 状态。
  • 客户端收到服务端的 FIN 报文后,回一个 ACK 应答报文,之后进入 TIME_WAIT 状态
  • 服务器收到了 ACK 应答报文后,就进入了 CLOSED 状态,至此服务端已经完成连接的关闭。
  • 客户端在经过 2MSL 一段时间后,自动进入 CLOSED 状态,至此客户端也完成连接的关闭。

断开连接需要四次挥手原因

  • 关闭连接时,客户端向服务端发送 FIN 时,仅仅表示客户端不再发送数据了但是还能接收数据。
  • 服务器收到客户端的 FIN 报文时,先回一个 ACK 应答报文,而服务端可能还有数据需要处理和发送,等服务端不再发送数据时,才发送 FIN 报文给客户端来表示同意现在关闭连接。

TCP断开的其他问题

1、为什么 TIME_WAIT 等待的时间是 2MSL?

    MSL是报文最大生存时间,超过这个时间报文将被丢弃。

    TIME_WAIT 等待 2 倍的 MSL,比较合理的解释是: 网络中可能存在来自发送方的数据包,当这些发送方的数据包被接收方处理后又会向对方发送响应,所以一来一回需要等待 2 倍的时间。比如如果被动关闭方没有收到断开连接的最后的 ACK 报文,就会触发超时重发 Fin 报文,另一方接收到 FIN 后,会重发 ACK 给被动关闭方, 一来一去正好 2 个 MSL。2MSL 的时间是从客户端接收到 FIN 后发送 ACK 开始计时的。如果在 TIME-WAIT 时间内,因为客户端的 ACK 没有传输到服务端,客户端又接收到了服务端重发的 FIN 报文,那么 2MSL 时间将重新计时。

2、为什么需要 TIME_WAIT 状态?

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

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

1、 防止旧数据包被收到;

TCP/IP

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

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

2、保证连接正确关闭

    TIME-WAIT 作用是等待足够的时间以确保最后的 ACK 能让被动关闭方接收,从而帮助其正常关闭。

TCP/IP

TCP保活机制

    定义一个时间段,在这个时间段内,如果没有任何连接相关的活动,TCP 保活机制会开始作用,每隔一个时间间隔,发送一个探测报文,该探测报文包含的数据非常少,如果连续几个探测报文都没有得到响应,则认为当前的 TCP 连接已经死亡,系统内核将错误信息通知给上层应用程序。

如果开启了 TCP 保活,需要考虑以下几种情况:

  1. 对端程序是正常工作的。当 TCP 保活的探测报文发送给对端, 对端会正常响应,这样 TCP 保活时间会被重置,等待下一个 TCP 保活时间的到来。
  2. 对端程序崩溃并重启。当 TCP 保活的探测报文发送给对端后,对端是可以响应的,但由于没有该连接的有效信息,会产生一个 RST 报文,这样很快就会发现 TCP 连接已经被重置。
  3. 是对端程序崩溃,或对端由于其他原因导致报文不可达。当 TCP 保活的探测报文发送给对端后,石沉大海,没有响应,连续几次,达到保活探测次数后,TCP 会报告该 TCP 连接已经死亡。

socket编程中对应的TCP三次握手:

TCP/IP

上一篇:网络协议和Netty(4):大白话说三次握手及用一个数据库连接解释三次握手


下一篇:docker安装windows镜像,实战案例