TCP协议的基本知识点

1、TCP协议特点

1.1连接的建立与断开

TCP 协议提供的是:面向连接、可靠的、字节流服务。使用 TCP 协议通信的双方必须先建立连接,然后才能开始数据的读写。双方都必须为该连接分配必要的内核资源,以管理连接的状态和连接上数据的传输。TCP 连接是全双工的,双方的数据可以通过一个连接进行读写。完成数据交换之后,通信双方都必须断开连接以释放系统资源。使用 tcpdump 可以抓包观察 TCP 连接的建立与关闭。该命令需要管理员权限,格式如下(假设两个测试用的主机 IP 地址为 192.168.43.10和 192.168.43.15) :

tcpdump -i eth0 -nt ‘(src 192.168.43.10 and dst 192.168.43.15 ) or ( src192.168.43.10and dst 192.168.43.15 )

三次握手

三次握手发生在客户端执行 connect()的时候,该方法返回成功,则说明三次握手已经建立。三次握手示例图如下:
在这里插入图片描述
1, TCP服务器进程先创建传输控制块TCB, 时刻准备接受客户端进程的连接请求, 此时服务器就进入了 LISTEN(监听)状态
2, TCP客户端进程也是先创建传输控制块TCB, 然后向服务器发出连接请求报文,此时报文首部中的同步标志位SYN=1, 同时选择一个初始序列号 seq = x, 此时,TCP客户端进程进入了 SYN-SENT(同步已发送状态)状态。TCP规定, SYN报文段(SYN=1的报文段)不能携带数据,但需要消耗掉一个序号。
3, TCP服务器收到请求报文后, 如果同意连接, 则发出确认报文。确认报文中的 ACK=1, SYN=1, 确认序号是 x+1, 同时也要为自己初始化一个序列号 seq = y, 此时, TCP服务器进程进入了SYN-RCVD(同步收到)状态。这个报文也不能携带数据, 但是同样要消耗一个序号。
4, TCP客户端进程收到确认后还, 要向服务器给出确认。确认报文的ACK=1,确认序号是 y+1,自己的序列号是 x+1.
5, 此时,TCP连接建立,客户端进入ESTABLISHED(已建立连接)状态。当服务器收到客户端的确认后也进入ESTABLISHED状态,此后双方就可以开始通信了。
两次握手可以吗?
可以;但是为了防止已经失效的连接请求报文突然又传送到了服务器,从而产生错误。如果使用的是两次握手建立连接,假设有这样一种场景,客户端发送的第一个请求连接并且没有丢失,只是因为在网络中滞留的时间太长了,由于TCP的客户端迟迟没有收到确认报文,以为服务器没有收到,此时重新向服务器发送这条报文,此后客户端和服务器经过两次握手完成连接,传输数据,然后关闭连接。此时之前滞留的那一次请求连接,因为网络通畅了, 到达了服务器,这个报文本该是失效的,但是,两次握手的机制将会让客户端和服务器再次建立连接,这将导致不必要的错误和资源的费。
如果采用的是三次握手,就算是那一次失效的报文传送过来了,服务端接受到了那条失效报文并且回复了确认报文,但是客户端不会再次发出确认。由于服务器收不到确认,就知道客户端并没有请求连接。
使用命令查看
在这里插入图片描述
当客户端给服务端发送“hello”时
在这里插入图片描述

四次挥手

四次挥手发生在客户端或服务端执行 close()关闭连接的时候,示例图如下:
在这里插入图片描述
1, 客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时客户端进入FIN-WAIT-1(终止等待1)状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。
2, 服务器收到连接释放报文,发出确认报文,ACK=1,确认序号为 u+1,并且带上自己的序列号seq=v,此时服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。
3, 客户端收到服务器的确认请求后,此时客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最终数据)
4, 服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,确认序号为v+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。
5, 客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,确认序号为w+1,而自己的序列号是u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。
6, 服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。
使用命令查看
在这里插入图片描述

1.2、TCP的状态转移

TCP 连接的任意一端在任一时刻都处于某种状态,当前状态可以通过 nett stat 命令查看,下图是 TCP 连接从建立到关闭整个过程中通信两端状态的变化。其中 CLOSED 是假想的起始点,并不是一个实际的状态。
在这里插入图片描述
上图中,TIME_WAIT 状态一般情况下是主动关闭的一端才会出现的状态。该状态出现后,会维持一段长为 2MSL(Maximum Segment Life)的时间,才能完全关闭。MSL 是 TCP 报文段在网络中的最大生存时间,标准文档 RFC1122 的建议值是 2min。
TIME_WAIT 状态存在的原因有两点:
◼ 可靠的终止 TCP 连接。
◼ 保证让迟来的 TCP 报文有足够的时间被识别并被丢弃。
在 Linux 系统上,一个 TCP 端口不能被同时打开多次(两次及以上)。当一个 TCP 连接处于 TIME_WAIT 状态时,我们将无法立即使用该连接占用着的端口来建立一个新连接。如果确实需要强制进程立即使用处于 TIME_WAIT 状态的连接所占用的端口,可以通过setsockopt()方法设置 socket 选项 SO_REUSEADDR 来完成。

1.3、复位报文段

在某些特殊条件下,TCP 连接的一端会向另一端发送携带 RST 标志的报文段,即复位报文段,已通知对方关闭连接或重新建立连接。这里介绍一下三种情况:
1)当客户端端程序访问一个不存在的端口时,目标主机给它发送一个复位报文段。
2)异常终止连接。正常情况下,数据交换完成之后,一方给另一方发送 FIN 结束报文段。TCP 提供了异常终止一个连接的方法,即给对方发送一个复位报文段。一但发送了复位报文段,发送端所有排队等待发送的数据都将被丢弃。应用程序可以使用 socket 选项SO_LINGER 来设置发送复位报文段,以异常终止连接。
3) 处理半打开连接。例如 TCP 一端关闭了连接,由于网络故障对方没有收到结束报文,对方误以为连接仍然正常。处于这种状态的连接称为半打开连接。此时如果对端向连接写入数据,则会收到本端回复的复位报文段。

1.4、交互数据流和成块数据流

TCP 按照携带应用程序数据长度可以分为两种:交互数据和成块数据。交互数据仅包含很少的字节。使用交互数据的应用程序对实时性要求极高,比如 telnet、ssh 等。成块数据的长度则通过为 TCP 报文段允许的最大数据长度。使用成块数据的应用程序对传输效率要求高,比如 FTP。

1.5、带外数据

有些传输层协议具有带外(out of Band,OOB)数据的概念,用于迅速通告对方本端发生的重要事件。因此,带外数据比普通数据有更高的优先级,它应该总是立即被发送,而不论发送缓冲区中是否有排队等待发送的普通数据。
UDP 没有实现带外数据传输,TCP 也没有真正的带外数据。不过 TCP 利用其头部中的紧急指针标志和紧急指针两个字段,给应用程序提供了一种传输紧急数据的方式。一般只有一个字节数据。

1.6、流式服务特点

TCP 字节流的特点,发送端执行的写操作次数和接收端执行的读操作次数之间没有任何数量关系,应用程序对数据的发送和接收是没有边界限制的。如下图:
在这里插入图片描述
什么是tcp粘包?怎样解决?
粘包就如上图所示;TCP 发送端应用程序发送了10次100字节的消息,那么,在接收端的应用程序接收到的可能是一个1000字节的连续不间断的数据
。 TCP 头部中并没有长度信息;
我们可以分析以下几种情况:

(1)如果利用 TCP 每次发送数据,就与对方建立连接,然后双方发送完一段数据后,就关闭连接,这样就不会出现粘包问题(因为只有一种数据结构)。
(2)如果发送数据无结构,如文件传输,这样发送端只管发送,接收端只管接收存储就行,也不用考虑粘包。
(3)如果双方建立连接,需要在连接后一段时间内发送多个不同结构的数据,这时候接收端收到就可能是一堆粘在一起的数据,这样接收端应用程序就傻了,到底是要干嘛?不知道,因为协议并没有规定这么诡异的数据。
    
针对这些问题,一般会有如下解决方法:

  • 调用发送函数之后都强制数据立即传送(PUSH 指令)。
  • 对于接收端引起的粘包,则可通过优化程序设计、精简接收进程工作量、提高接收进程优先级等措施,使其及时接收数据,从而尽量避免出现粘包现象。
  • 添加一个固定的消息头,该消息头包含数据长度信息,每次数据时先接收固定大小的消息头,再根据其携带的长度信息接收消息实体。也就是说,通过人为控制多次接收来避免粘包。
  • 设置 TCP_NODELAY 选项,禁止 Nagle 算法。
  • 设置 SO_RCVBUF 和 SO_SNDBUF 选项,根据应用需求修改一个合适的接收、发送缓冲区大小。
  • 添加报文分隔标识,比如发送报文是在末尾添加 ‘\n’ ,同时接收端使用 recv() 函数接收报文,并且设置参数 flags 的值为 MSG_PEEK。注意:当 flags 参数的值设置为 MSG_PEEK 时,recv() 可以从 socket缓存中读取数据,但是不会将缓存中该部分数据清除,但如果使用 read() 函数直接读取 socket 缓存区中的内容,会清空缓存区中的内容。假设两段报文粘包,read() 会清空缓存区中所有内容,从而导致后一段报文中的粘包的部分数据丢失。

1.7、应答确认和超时重传

TCP 发送的报文段是交给 IP 层传送的。但 IP 层只能提供尽最大努力的服务,也就是说,TCP 下面的网络所提供的是不可靠的传输。因此,TCP 必须采用适当的措施才能使两个运输层之间的通信变得可靠。TCP 的可靠传输是通过使用应答确认和超时重传来完成。
下图是无差错时,数据交互的流程:发送端发送数据 m1 给接收端,接收端收到数据后会给发送端一个确认信息,以表明数据已经被成功收到。在发送方未收到确认信息前,M1应继续被保留,直到确认信息到达才能丢弃。
在这里插入图片描述
下图是出现差错时,数据交互的流程:
在这里插入图片描述

1.8、滑动窗口

TCP 协议是利用滑动窗口实现流量控制的。一般来说,我们总是希望数据传输得更快一些,不会一次只发一个字节。但是如果发送方把数据发得过快,接受方就可能来不及接收,这就会造成数据的丢失。所谓流量控制就是让发送方的发送速率不要太快,要让接收方来的及接收。
在 TCP 的报头中有一个字段叫做接收通告窗口,这个字段由接收端填充,是接收端告诉发送端自己还有多少缓冲区可以接收数据。于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来。所以发送端就会有一个发送窗口,这个发送窗口的大小是由接收端填充的接收通告窗口的大小决定的,并且窗口的位置会随着发送端数据的发送和接收到接收端对数据的确认而不断的向右滑动,将之称为滑动窗口。
发送方的滑动窗口示意图如下:
在这里插入图片描述
当收到 36 的 ack,并发出 46-51 的字节后,窗口滑动的示意图如下:
在这里插入图片描述

1.9、拥塞控制

在计算机网络中的链路容量(即带宽)、交换结点中的缓存和处理机等,都是网络的资源。在某段时间,若对网络中某一资源的需求超过了该资源所能提供的可用部分,网络的性能就要变坏。这种情况就叫做拥塞。所谓拥塞控制就是防止过多的数据注入到网络中,这样可以使网络中的路由器或链路不致过载。拥塞控制是一个全局性的过程,涉及到所有主机,所有路由器,以及与降低网络传输性能有关的所有因素。
几种拥塞控制的方法:

  • 慢开始
  • 拥塞避免
  • 快速重传
  • 快速恢复
    慢开始、拥塞避免、快速恢复示意图:
    在这里插入图片描述

快速重传示意图:
在这里插入图片描述

参考书籍:
linux程序设计 中文第4版
linux内核设计与实现 第3版

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值