一口气真的吃不下 TCP,对着资料整理好多天,滑动窗口、重传等留到下一次博客,这里先简单认识下 TCP 以及其连接与断开
内容比较干,如果认真看完一定对您有一定帮助。

TCP基本认识

TCP头格式

序列号:在建立连接时由计算机生成的随机数作为其初始值,通过 SYN 包传给接收端主机,每发送一次数据,就累加一次该数据字节数的大小。用来解决网络包乱序问题

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

控制位

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

为什么需要 TCP 协议?TCP 工作在那一层

IP 层是不可靠的,他不保证网络包的交付、不保证网络包的按序交付、也不保证网络包中的数据的完整性。

如果需要保障网络数据包的可靠性,那么就需要由上层(传输层)的 TCP 协议来负责。

因为 TCP 是一个工作在传输层可靠数据传输的服务,它能确保接收端接收的网络包是无损坏、无间隔、非冗余和按序的

什么是 TCP?

TCP 是面向连接的、可靠的、基于字节流的传输层通信协议。

  • 面向连接 : 一定是一对一才能连接,不能像 UDP 协议可以一个主机同时向多个主机发送消息,也就是一对多是无法做到的;
  • 可靠的 : 无论网络链路中出现了怎样的链路变化,TCP 都可以保证一个报文一定能够到达接收端;
  • 字节流 : 消息是没有边界的,所以无论我们消息有多大都可以进行传输。并且消息时有序的,当前一个消息没有收到的时候,即使它先收到了后面的字节,那么也不能扔给应用层去处理,同时对重复的报文会自动丢弃。

什么是 TCP 连接?

TCP 是用于保证可靠性和流量控制维护的某些状态信息,这些信息的组合,包括 Socket、序列号和窗口大小称为连接。

所以我们可以知道,建立一个 TCP 连接是需要客户端与服务器端达成上述三个信息的共识。

  • Socket : 由 IP 地址和端口号组成
  • 序列号 : 用来解决乱序问题等
  • 窗口大小 : 用来做流量控制

如何唯一确定一个 TCP 连接呢?

TCP 四元组可以唯一的确定一个连接,四元组包括如下:

  • 源地址
  • 源端口
  • 目的地址
  • 目的端口

源地址和目的地址的字段(32位)是在 IP 头部中,作用是通过 IP 协议发送报文给对方主机。

源端口和目的端口的字段(16位)是在 TCP 头部中,作用是告诉 TCP 协议应该把报文发送给哪个进程。

有一个 IP 的服务器监听了一个端口,它的 TCP 的最大连接数是多少。

服务器通常固定在某个本地端口上监听,等待客户端的连接请求。

因此,客户端 IP 和端口是可变的,其理论值计算公式如下:

最大 TCP 连接数 = 客户端的 IP 数 * 客户端的端口数

对于 IPv4,客户端的 IP 数最多为 232,客户端的端口数最多为216次方,也就是服务端单机最大 TCP 连接数,约为 2^48次方。

当然,服务端最大并发 TCP 连接数远不能达到理论上限。

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

UDP 和 TCP 有什么区别的?分别应用场景?

UDP 不提供复杂的控制机制,利用 IP 提供面向无连接的通信服务。

UDP 协议真的非常简单,头部只有 8 个字节(64位),UDP 头部格式如下:

  • 目标和源端口号 :主要告诉 UDP 协议应该把报文发送给哪个进程。
  • 包长度 :该字段保存了UDP首部的长度跟数据的长度之和。
  • 校验和 : 校验和是为了提供可靠的 UDP 首部和数据而设计。

TCP 和 UDP 区别

  1. 连接

    • TCP 是面向连接的传输层协议,传输数据前先要建立连接
    • UDP 是不需要连接,即刻传输数据
  2. 服务对象
    • TCP 是一对一的两点服务,即一条连接只有两个端点
    • UDP 支持一对一、一对多、多对多的交互通信
  3. 可靠性
    • TCP 是可靠交付数据,数据可以无差错、不丢失、不重复、按需到达
    • UDP 是尽最大努力交付,不保证可靠交付数据
  4. 拥塞控制、流量控制
    • TCP 有拥塞控制和流量控制机制,保证数据传输的安全性
    • UDP 则没有,即使网络非常拥堵了,也不会影响 UDP 的发送速率
  5. 首部开销
    • TCP 首部长度较长,会有一定的开销,首部没有使用选项字段时是 20 个字节,如果使用了选项字段则会边长
    • UDP 首部只有8个字节,并且是固定不变的,开销较小
  6. 传输方式
    • TCP 是流式传输,没有边界,但保证顺序和可靠
    • UDP 是一个包一个包的发送,是有边界的,但可能会丢包和乱序
  7. 分片不同
    • TCP 的数据大小如果大于 MSS 大小,则会在传输层进行分片,目标主机收到后,同样也在传输层组装 TCP 数据包,如果中途丢失了一个分片,只需要传输丢失的这个分片
    • UDP 的数据大小如果大于 MTU 大小,则会在 IP 层进行分片,目标主机收到后,在 IP 层组装完数据,接着再传给传输层,但是如果中途丢了一个分片,这就需要重传所有的数据包,这样传输效率非常差,所以通常 UDP 的报文应该小于 MTU

TCP 和 UDP 应用场景

由于 TCP 是面向连接,能保证数据的可靠性交付,因此经常用于:

  • FTP 文件传输
  • HTTP/HTTPS

由于 UDP 面向无连接,它可以随时发送数据,再加上 UDP 本身的处理既简单又高效,因此经常用于:

  • 包总量较少的通信,如 DNS、SNMP等
  • 视频、音频等多媒体通信
  • 广播通信

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

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

为什么 UDP 头部有包长度字段,而 TCP 头部则没有包长度字段呢?

先说说 TCP 是如何计算负载数据长度:

TCP 数据的长度 = IP 总长度 - IP 首部长度 - TCP 首部长度

其中 IP 总长度和 IP 首部长度,在 IP 首部格式是已知的。TCP 首部长度,则是在 TCP 首部格式一直的,所以就可以求得 TCP 数据的长度。

大家这时就奇怪了问:“ UDP 也是基于 IP 层的呀,那 UDP 的数据长度也可以通过这个公式计算呀?

为何还要有「包长度」呢?”

因为设计了网络设备硬件设计和处理方便,首部长度需要是 4 字节的整数倍。

如果去掉 UDP 包长度字段,那么 UDP 首部长度就不是 4 字节的整数倍了,所以我觉得这可能是为了补全 UDP 首部长度是 4 字节的整数倍,才补充了包长度字段。

TCP连接建立

TCP 三次握手过程和状态变迁

TCP 是面向连接的协议,所以使用 TCP 前必须先建立连接,而建立连接是通过三次握手来进行的。

三次握手过程

  • 一开始客户端和服务端都处于 CLOSE 状态。先是服务端主动监听某个端口,处于 LISTEN 状态。

  • 客户端会随机初始化序号(client_isn),将此序号置于 TCP 首部的序列号字段中,同时把 SYN 标志位置 1 ,表示 SYN 报文。接着把第一个 SYN 报文发送给服务端,表示向服务端发起连接,该报文不包含应用层 数据,之后客户端处于 SYN-SENT 状态。

  • 服务端收到客户端的 SYN 报文后,首先服务端也随机初始化自己的序号(server_isn),将此序号填入 TCP 首部的序列号字段中 ,其次把 TCP 首部的确认应答号字段填入 client_isn + 1 ,接着把 SYN 和 ACK 标志位置为 1。最后把该报文发给客户端,该报文也不包含应用层数据,之后服务端处于 SYN-RCVD 状态

  • 客户端收到服务端报文后,还要向服务端回应最后一个应答报文,首先该应答报文 TCP 首部 ACK 标志位置为 1,其次确认应答号字段填入 server_isn +1,最后吧报文发送给服务端,这次报文可以携带客户到服务器的数据,之后客户端处于 ESTABLISHED 状态。
  • 服务器收到客户端的应答报文后,也进入 ESTABLISHED 状态。

从上面的过程可以发现第三次握手是可以携带数据的,前两次握手是不可携带数据的。

一旦完成三次握手,双方都处于 ESTABLISHED(已建立)状态,此时连接就已经建立完成,客户端和服务端就可以相互发送数据了。

如何在 Linux 系统中查看 TCP 状态

TCP 的连接状态查看,在 Linux 可以通过 netstat -napt 命令查看。

为什么是三次握手?不是两次、四次 ?

“因为三次握手才能保证双方具有接收和发送的能力”

这样的回答是没问题,但是又有点片面,没有说出主要的原因。

TCP 连接

  • 用于保证可靠性和流量控制维护的某些状态信息,这些信息的组合,包括 Socket、序列号和窗口大小称为连接

所以,重要的是为什么三次握手才可以初始化Socket、序列号和窗口大小并建立 TCP 连接。

接下来以三个方面分析三次握手的原因:

  • 三次握手才可以阻止重复历史连接的初始化(主要原因)
  • 三次握手才可以同步双方的初始序列号
  • 三次握手才可以避免资源浪费

原因一:避免历史连接

三次握手的首要原因是为了防止旧的重复连接初始化造成混乱。

网络环境可能是错综复杂的,往往并不是和我们期望的一样,先发送数据包,就先到达目标主机,反而它会由于网络拥堵等乱七八糟的原因,会使得旧的数据包,先到达目标主机,那么这种情况下 TCP 三次握手是如何避免的呢?

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

  • 一个旧 SYN 报文 比新的 SYN 报文早到达了服务端
  • 那么此时服务端就会回一个 SYN + ACK 报文给客户端
  • 客户端收到后可以根据自身的上下文,判断这是一个历史连接(序列号过期或超时),那么客户端会发送 RST 报文给服务端,表示中止这一次连接

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

  • 如果历史连接(序列号过期或超时),则第三次握手发送的报文是 RST 报文,一次中止历史连接
  • 如果不是历史连接,则第三次发送方的报文是 ACK 报文,通信双方就会成功建立连接

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

原因二:同步双方初始序列号

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

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

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

四次握手其实也能够可靠的同步双方的初始化序号,但由于第二步和第三步可以优化成一步,所以就成了三次握手。

而两次握手只保证了一方的初始序列号能被对方成功接收,没办法保证双方的初始序列号都能被确认接收。

原因三:避免资源浪费

如果只有两次握手,当客户端的 SYN 请求连接在网络中阻塞,客户端没有收到 ACK 报文,就会重新发送 SYN,由于没有第三次握手,服务器不清楚客户端是否收到了自己发送的建立连接的 ACK 确认信号,所以每收到一个 SYN 就只能先主动建立一个连接,这会造成什么情况呢?

如果客户端的 SYN 阻塞了,重复发送多次 SYN 报文,那么服务器在收到请求后就会建立多个冗余的无效链接,造成不必要的资源浪费。

即两次握手会造成消息滞留情况下,服务器重复接受无用的连接请求 SYN 报文,而造成重复分配资源。

小结

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

不使用两次握手和四次握手的原因:

  • 两次握手:无法防止历史连接的建立,会造成双方资源的浪费,也无法可靠的同步双方序列
  • 四次握手:三次握手就已经理论上最少可靠连接建立,所以不需要使用更多的通信次数

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

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

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

另一方面是为了安全性,防止黑客伪造的相同序列号的 TCP 报文被对方接收。

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

起始 ISN 是基于时钟的,每 4 毫秒 +1,转一圈要 4.55 个小时。

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

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

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

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

我们先来认识下 MTU 和 MSS

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

如果在 TCP 的整个报文(头部+数据)交给 IP 层进行分片,会有什么异常呢?

当 IP 层有一个超过 MTU 大小的数据(TCP 头部 + TCP 数据)要发送,你那么 IP 层就要进行分片,把数据分片成若干片,保证每一个分片都小于 MTU。把一份 IP 数据报进行分片以后,由目标主机 IP 层来进行重写组装后,再交给上一层 TCP 传输层。

这看起来井然有序,但这存在隐患的,那么当如果一个 IP 分片丢失,整个 IP 报文的所有分片都得重传。

因为 IP 层本身没有超时重传机制,它由传输层的 TCP 来负责超时和重传。

当接收方发现 TCP 报文(头部+数据)的某一片丢失后,则不会响应 ACK 给对方,那么发送方 TCP 在超时后,就会重发整个 TCP 报文(头部 + 数据)。

因此,可以得知由 IP 层进行分片传输,是非常没有效率的。

所以,为了达到最佳的传输效能 TCP 协议在建立连接的时候通常要写上双方的 MSS 值,当 TCP 层发现数据超过 MSS 时,则就会先 进行分片,当然由它形成的 IP 包的长度也就不会大于 MTU,自然也就不用 IP 分片了。

经过 TCP 层分片后,如果一个 TCP 分片丢失后,进行重发时也是以 MSS 为单位,而不用重传所有的分片,大大增加了重传的效率。

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

SYN 攻击

我们都知道 TCP 连接建立是需要三次握手,假设攻击者顿时间伪造不同 IP 地址的 SYN 报文服务端每接收到一个 SYN 报文,就进入 SYN_RCVD 状态,但服务端发送出去的 ACK+SYN 报文,无法得到未知 IP 主机的 ACK 应答,久而久之就会占满服务端的 SYN 接收队列(未连接队列),使得服务器不能为正常用户服务。

避免 SYN 攻击方式一

其中一种解决方式是通过修改 Linux 内核参数,控制队列大小和当队列满时应做什么处理。

  • 当网卡接收数据包的速度大于内核处理速度时,会有一个队列保存这些数据包。控制该队列的最大值如下参数:

    net.core.netdev_max_backlog
    
  • SYN_RCVD 状态连接的最大个数:

    net.ipv4.tcp_max_syn_backlog
    
  • 超出处理能时,对新的 SYN 直接回报 RST,丢弃连接:

    net.ipv4.tcp_abort_on_overflow
    

避免 SYN 攻击方式二

我们先来看下 Linux 内核的 SYN (未完成连接建立)队列与 Accpet (已完成连接建立)队列是如

何工作的?

正常流程

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

应用程序过慢

  • 如果应用程序过慢时,就会导致 Accept队列被占满

受到 SYN 攻击

  • 如果不断受到 SYN 攻击,就会导致 SYN 队列被占满。

tcp_syncookies 的方式可以应对 SYN 攻击的方法:

net.ipv4.tcp_syncookies = 1

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

TCP连接断开

四次挥手过程和状态变迁

天下没有不散的宴席,对于 TCP 连接也是这样,TCP 断开连接是通过四次挥手方式。

双方都可以主动断开连接,断开连接后主机中的资源将被释放。

  • 客户端打算关闭连接,此时会发送一个 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 和 ACK ,因此通常被称为四次挥手。

但是需要注意的是:主动关闭连接的,才有 TIME_WAIT 状态

为什么挥手需要四次

再来回顾下四次挥手双方发 FIN 包的过程,就能理解为什么需要四次了。

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

从上面过程可知,服务端通常需要等待完成数据的发送和处理,所以服务端的 ACK 和 FIN 一般都会分开发送,从而比三次握手导致多了一次。

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

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

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

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

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

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

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

其定义在 Linux 内核代码里的名称为 TCP_TIMEWAIT_LEN:

#define TCP_TIMEWAIT_LEN (60*HZ) /* how long to wait to destroy TIME-WAIT state, about 60 seconds */

如果要修改 TIME_WAIT 的时间长度,只能修改 Linux 内核代码里 TCP_TIMEWAIT_LEN 的值,并重新编译 Linux 内核。

为什么需要 TIME_WAIT 状态?

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

需要 TIME_WAIT 状态主要有两个原因:

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

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

假设 TIME_WAIT 没有等待时间或时间过短,被延迟的数据包抵达后会发生什么呢?

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

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

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

在 RFC 793 支出 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 连接至少小号一个本地端口

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

Socket编程

针对 TCP 应该如何 Socket 编程

  • 服务端和客户端初始化 Socket,得到文件描述符
  • 服务端调用 bind,将绑定在 IP 地址和端口
  • 服务端调用 listen,进行监听
  • 服务端调用 accept,等待客户端连接
  • 客户端调用connect,向服务器端的地址和端口号发起连接请求
  • 服务端 accept 返回用于传输的 socket 的文件描述符
  • 客户端调用 write 写入数据;服务端调用 read 读取数据
  • 客户端断开连接时,会调用close,那么服务端 read 读取数据的时候,就会读取到了 EOF,待处理完数据后,服务端调用 close,表示连接关闭。

这里需要注意的是,服务端调用 accept 时,连接成功了会返回一个已完成连接的 socket,后续用来传输数据。

所以,监听的 socket 和真正用来传送数据的 socket,是两个 socket,一个叫做监听 socket,一个叫作已完成连接 socket。

成功连接建立之后,双方开始通过 read 和 write 函数来读写数据,就像往一个文件流里面写东西一样。

listen 时候参数backlog的意义

Linux 内核中会维护两个队列

  • 未完成连接队列(SYN 队列):接收到一个 SYN 建立连接请求,处于 SYN_RCVD 状态
  • 已完成连接队列(Accept队列):已完成 TCP 三次握手过程,处于 ESTABLISHED 状态

int listen (int socketfd, int backlog)
  • 参数一 socketfd 为 socketfd 文件描述符

  • 参数二 backlog,这参数在历史版本有一定的变化

在早期 Linux 内核 backlog 是 SYN 队列大小,也就是未完成的队列大小。

在 Linux 内核 2.2 之后,backlog 变成 accept 队列,也就是已完成连接建立的队列长度,所以现在通常

认为 backlog accept 队列。

但是上限值是内核参数 somaxconn 的大小,也就说 accpet 队列长度 = min(backlog,somaxconn)

accept 发生在三次握手的哪一步

我们先看看客户端连接服务端时,发送了什么?

  • 客户端的协议栈向服务器端发送了 SYN包,并告诉服务器端发送序列号 client_isn,客户端进入 SYN_SENT 状态
  • 服务器端的协议栈收到这个包之后,和客户端进行 ACK 应答,应答的值为 client_isn + 1,表示对 SYN 包 client_isn 的确认,同时服务器也发送一个 SYN 包,告诉客户端当前我的发送序列号为 server_isn,服务器端进入SYN_RCVD状态
  • 客户端协议栈收到 ACK 之后,使得应用程序从 connect 调用返回,表示客户端到服务器端的单向连接建立成功,客户端的状态为 ESTSBLISHED,同时客户端协议栈也会对服务器端的 SYN 包进行应答,应答数据为 server_isn+1
  • 应答包到达服务器端协议栈使得 accept 阻塞调用返回,这个时候服务器端到客户端的单向连接也建立成功,服务器端也进入 ESTABLISHED 状态。

从上面的描述过程,我们可以得知客户端 connect 成功返回是在第二次握手,服务端 accept 成功返回是在三次握手成功之后。

客户端调用 close 了,连接断开的流程是什么?

我们看看客户端主动调用了 close,会发生什么?

  • 客户端调用 close,表明客户端没有数据需要发送了,则此时会向服务端发送 FIN 报文,进入 FIN_WAIT_1 状态;
  • 服务端接收到了 FIN 报文,TCP 协议栈会为了 FIN 包插入一个文件结束符 EOF 到接收缓冲区中,应用程序可以通过 read 调用来感知这个 FIN 包。这个 EOF 会被放在以排队等候的其他已接收的数据之后,这就意味着服务端需要处理这种异常情况,因为 EOF 表示在该连接上再无额外数据到达。此时,服务端进入 CLOSE_WAIT 状态
  • 接着,当处理完数据后,自然就会读到 EOF,于是也调用 close 关闭它的套接字,这会使得客户端会发出一个 FIN 包,之后处于 LAST_ACK 状态
  • 客户端接收到服务端的 FIN 包,并发送 ACK 确认包给服务端,此时客户端将进入 TIME_WAIT 状态
  • 服务端收到 ACK 确认包后,就进入了最后的 CLOSE 状态
  • 客户端经过 2MSL 时间之后,也进入 CLOSE 状态

简单了解下 TCP,学习握手和挥手以及各种状态到底是怎么样的相关推荐

  1. TCP协议-握手与挥手

    认识TCP协议 TCP全称为"传输控制协议",这是传输层的一个协议,对数据的传输进行一个详细的控制.  特点: 面向字节流 安全可靠 面向连接 TCP协议段格式 源端口号与目的端口 ...

  2. TCP协议 握手与挥手

    应用层协议目的是了解指定协议的实现便于我们以后使用 传输层:负责应用程序之间的数据传输-TCP/UDP 了解协议的实现,体会协议的特性,理解对于上层程序编程的影响 UDP: 协议实现: 16源端-对端 ...

  3. 深度学习核心技术精讲100篇(二十四)-简单谈下深度学习在中文分词中的应用

    前言 随着深度学习的普及,有越来越多的研究应用新模型到中文分词上,让人直呼"手快有,手慢无".不过这些神经网络方法的真实水平如何?具体数值多少?以Sighan05中的PKU数据集为 ...

  4. java网络篇-tcp的握手和挥手!

    package com.wql.test; public class Test6 { public static void main(String[] args) { test1(ErrorType. ...

  5. 计网 - 传输层协议 TCP:TCP 为什么握手是 3 次、挥手是 4 次?

    文章目录 Pre TCP 协议 主机到主机(Host-To-Host) 什么是连接和会话? 双工/单工问题 什么是可靠性? TCP 的握手和挥手 TCP 协议的基本操作 建立连接的过程(3次握手) 断 ...

  6. TCP/IP协议,握手,挥手

    TCP协议详解 - 王先生架构 - 博客园 TCP的三次握手与四次挥手理解及面试题(很全面) - 李卓航 - 博客园 为什么需要三次握手和四次挥手_白小狮的博客-CSDN博客_为什么要三次挥手 Wir ...

  7. TCP 握手和挥手图解(有限状态机)

    1.引言 TCP 这段看过好几遍,老是记不住,没办法找工作涉及到网络编程这块,各种问 TCP .今天好好整理一下握手和挥手过程.献给跟我一样忙碌,找工作的童鞋,欢迎大神批评指正. 2.TCP 的连接建 ...

  8. 【计算机网络】传输层 : TCP 连接管理 ( TCP 连接建立 | 三次握手 | TCP 连接释放 | 四次挥手 )

    文章目录 一.TCP 连接管理 二.TCP 连接建立 三.TCP 连接建立 相关报文段 字段 四.SYN 洪泛攻击 五.TCP 连接释放 一.TCP 连接管理 TCP 传输数据过程 : 建立连接 -& ...

  9. 网络基础2-3(TCP协议,三次握手,四次挥手,TIME_WAIT状态的作用,TCP如何保证可靠传输,TCP连接中状态转化,滑动窗口,流量控制,快速重传,拥塞窗口,延迟应答,捎带应答,粘包问题)

    TCP协议 TCP协议概念 TCP全称为 "传输控制协议(Transmission Control Protocol"). 人如其名, 要对数据的传输进行一个详细的控制 TCP协议 ...

最新文章

  1. gif 分支的新建与合并
  2. Lintcode 655解题思路和c++代码
  3. 解析 | K8S之网络插件exec
  4. Windows Server 2012 下安装MySQL 5.6 X64位包
  5. angular 在IIS部署运行
  6. LeetCode 128. 最长连续序列 golang
  7. vmware workstation 关于三种网络连接方式的理解
  8. element时间选择器限制到时分秒_element-ui 日期时间选择器限制日期以及时间范围...
  9. Error: could not open `C:\Program Files\Java\jre6\lib\i386\jvm.cfg#39;)
  10. C语言如何打开shx文件,shx文件怎么打开 .shx格式打开方式解答
  11. 如何用GraphPad Prism 进行pearson相关性分析
  12. Golang go-svc包源码分析
  13. 视觉目标检测-05:使用目标区域的提取方法(手机iVcam与电脑iVcam之间相机的链接)
  14. android倒计时日历软件,Hurry - 一款颜值超高的日历+倒计时 APP - Android 应用 - 纪念日 - 【最美应用】...
  15. 【中国人口金字塔2019,python,pandas,matplotlib,numpy 】
  16. 【QT】Qtcreator常用快捷键
  17. 滴滴青桔单车跨端技术方案和业务技术架构,及框架设计和性能提升实践
  18. 假如互联网公司做铁道部12306订票网站
  19. 《春风十里不如你》身体骚动的魔性,精神成长的传记
  20. js中function和Function的区别

热门文章

  1. ntp VS chrony
  2. 三项黑科技,给港珠澳大桥装上“超强大脑”
  3. 微信小程序仿照微信拖动缩放图片和截取头像
  4. Unity游戏快速制作特效
  5. 网络编程一 - 计算机网络体系基础知识
  6. CNN网络实现手写数字(MNIST)识别 代码分析
  7. vscode 无法输入输出
  8. OpenCV 文字绘制cv::putText详解
  9. 【嵌入式面试】2022年嵌入式经典面试题汇总(C语言)
  10. DataSource和SessionFactory的区别