TCP/IP 协议相关
前言: 本文内容以菜鸟教程、图解网络为基本,一些图片和介绍也是引自其中。本篇文章主要用于自己的知识梳理。
tcp基本知识
tcp头格式
ps:重点关注标色部分,与本篇文章相关性较大。
下面我们对图片标色部分进行分开介绍:
序列号
:在建立连接时由计算机生成的随机数作为其初始值,通过 SYN 包
传给接收端主机,每发送一
次数据,就「累加」一次该「数据字节数」的大小。用来解决网络包乱序问题。
确认应答号
:指下一次「期望」收到的数据的序列号,发送端收到这个确认应答以后可以认为在这个序
号以前的数据都已经被正常接收。用来解决不丢包的问题。
控制位
:
- ACK:该位为 1 时,「确认应答」的字段变为有效,TCP 规定除了最初建立连接时的 SYN包之外该位必须设置为 1 。
- RST:该位为 1 时,表示 TCP 连接中出现异常必须强制断开连接。
- SYN:该位为 1 时,表示希望建立连接,并在其「序列号」的字段进行序列号初始值的设定。
- FIN:该位为 1 时,表示今后不会再有数据发送,希望断开连接。当通信结束希望断开连接时,通信双方的主机之间就可以相互交换 FIN 位为 1 的 TCP 段。
TCP协议的功能
你知道为什么需要TCP协议吗?TCP工作在哪一个层次?
前面我们已经讲到过IP,它用于主机与主机之间的通信,也叫点对点(end to end)通信。
但是,IP 层是「不可靠」的。它只是保证包的终点网络,不保证网络包的交付、不保证网络包的按序交付、也不保证网络包中的数据的完整性。
所以,如果需要保障网络数据包的可靠性,那么就需要由上层(传输层)的 TCP 协议来负责。
确保接收端接收的网络包是无损坏
、无间隔
、非冗余
和按序的
就是TCP协议的主要功能。
TCP是工作在传输层上的服务。
什么是TCP?
TCP 是面向
连接的
、可靠的
、基于字节流的
传输层通信协议。
面向连接
:一定是「一对一」才能连接,不能像 UDP 协议可以一个主机同时向多个主机发送消息,也就是一对多是无法做到的;可靠的
:无论的网络链路中出现了怎样的链路变化,TCP 都可以保证一个报文一定能够到达接收端;字节流
:消息是「没有边界」的,所以无论我们消息有多大都可以进行传输。并且消息是「有序的」,当「前一个」消息没有收到的时候,即使它先收到了后面的字节,那么也不能扔给应用层去处理,同时对「重复」的报文会自动丢弃。
什么是TCP连接
所谓连接,简单来说就是:用于保证可靠性和流量控制维护的某些状态信息,这些信息的组合,包括
Socket
、序列号
和窗口大小
称为连接。
TCP连接也要符合上述要求。即建立一个TCP连接需要客户端和服务端达成以上三个信息的共识。
Socket
:由 IP 地址和端口号组成序列号
:用来解决乱序问题等窗口大小
:用来做流量控制
下面我们对建议一个tcp连接的过程提出几个问题,并给予答案。
问题一:如何唯一的确定一个TCP连接?
答:
TCP 四元组
可以唯一的确定一个连接,四元组包括如下:
源地址和目的地址的字段(32位)是在 IP 头部
中,作用是通过 IP 协议发送报文给对方主机。
源端口和目的端口的字段(16位)是在 TCP 头部
中,作用是告诉 TCP 协议应该把报文发给哪个进程。
问题二:有一个 IP 的服务器监听了一个端口,它的 TCP 的最大连接数是多少?
对 IPv4,客户端的 IP 数最多为 2 的 32 次方,客户端的端口数最多为 2 的 16 次方,也就是服务端单机最大 TCP 连接数,约为 2 的 48 次方。
当然,在实际情况下,TCP 的最大连接数不可能达到这个理论最大值。原因如下:
- 首先主要是文件描述符限制,Socket 都是文件,所以首先要通过 ulimit 配置文件描述符的数目;
- 另一个是内存限制,每个 TCP 连接都要占用一定内存,操作系统的内存是有限的。
这里留下一个微信公众号的文章:公众号【低并发编程】搜索tcp即可
问题三:UDP 和 TCP 有什么区别呢?分别的应用场景是?
UDP 不提供复杂的控制机制,利用 IP 提供面向「无连接」的通信服务。
UDP 协议真的非常简,头部只有 8 个字节( 64 位),UDP 的头部格式如下:
可以与TCP头部格式进行比对,UDP头部只有8个字节,即每一个区域占用2个字节。而TCP头出去选型区域,都还有20个字节。
- 目标和源端口:主要是告诉 UDP 协议应该把报文发给哪个进程。
- 包长度:该字段保存了 UDP 首部的长度跟数据的长度之和。
- 校验和:校验和是为了提供可靠的 UDP 首部和数据而设计。
那么二者的区别都有哪些呢?
- 连接
TCP 是面向连接的传输层协议,传输数据前先要建立连接。
UDP 是不需要连接,即刻传输数据。 - 服务对象
TCP 是一对一的两点服务,即一条连接只有两个端点。
UDP 支持一对一、一对多、多对多的交互通信 - 可靠性
TCP 是可靠交付数据的,数据可以无差错、不丢失、不重复、按需到达。
UDP 是尽最大努力交付,不保证可靠交付数据。 - 拥塞控制、流量控制
TCP 有拥塞控制和流量控制机制,保证数据传输的安全性。
UDP 则没有,即使网络非常拥堵了,也不会影响 UDP 的发送速率。 - 首部开销
TCP 首部长度较长,会有一定的开销,首部在没有使用「选项」字段时是 20 个字节,如果使用了「选项」字段则会变长的。
UDP 首部只有 8 个字节,并且是固定不变的,开销较小。 - 传输方式
TCP 是流式传输,没有边界,但保证顺序和可靠。
UDP 是一个包一个包的发送,是有边界的,但可能会丢包和乱序。 - 分片不同
TCP 的数据大小如果大于 MSS 大小,则会在传输层进行分片,目标主机收到后,也同样在传输层组装 TCP 数据包,如果中途丢失了一个分片,只需要传输丢失的这个分片。
UDP 的数据大小如果大于 MTU 大小,则会在 IP 层进行分片,目标主机收到后,在 IP 层组装完数据,接着再传给传输层,但是如果中途丢了一个分片,则就需要重传所有的数据包,这样传输效率非常差,所以通常 UDP 的报文应该小于 MTU。
TCP 和 UDP 应用场景:
由于 TCP 是面向连接,能保证数据的可靠性交付,因此经常用于:
- FTP 文件传输
- HTTP / HTTPS
由于 UDP 面向无连接,它可以随时发送数据,再加上UDP本身的处理既简单又高效,因此经常用于:
- 包总量较少的通信,如 DNS 、 SNMP 等
- 视频、音频等多媒体通信
- 广播通信
QQ使用的协议就是UDP协议。
TCP连接建立
老规矩,给个框架图
TCP三次握手
TCP 是面向连接的协议,所以使用 TCP 前必须先建立连接,而建立连接是通过
三次握手
来进行的.ps:在学习计网知识之前,我就已经听说过tcp三次握手这个大名鼎鼎的知识点,那时候就很想知道它到底是个什么东西。现在,机会来了。(下面会有较多的图片对这个过程进行分析)
看了,上面的图解,相信你也对TCP连接的建立有了初步了解,但是还是要给出一定注意事项:
- 要关注每一次握手之后服务端与客户端状态的变化。
- 要关注每一次握手后TCP头部内容的变化,图片中对改变的内容做了标注。
- 第三次握手是可以携带数据的,前两次握手是不可以携带数据的。
一旦完成三次握手,双方都处于 ESTABLISHED 状态,此时连接就已建立完成,客户端和服务端就可以相互发送数据了。
下面我们会对tcp连接的建立过程中的一些注意点和扩展内容进行补充,这些内容会采用使用二级标题进行划分,但是内容归到此部分。
如何在 Linux 系统中查看 TCP 状态?
TCP 的连接状态查看,在 Linux 可以通过 netstat -napt
命令查看。
为什么是三次握手?不是两次、四次?
一个比较常见的回答是:因为三次握手才能保证双方具有接收和发送的能力。
但是这个答案是片面的,只是笼统的说法,没有给出具体的原因。
前面我们已经提到了TCP连接
的定义,这里回顾一下:用于保证可靠性和流量控制维护的某些状态信息,这些信息的组合,包括Socket
、序列号
和窗口大小
称为连接。
所以,重要的是为什么三次握手才可以初始化Socket
、序列号
和窗口大小
并建立 TCP 连接。
接下来以三个方面分析三次握手的原因:
- 三次握手才可以阻止重复历史连接的初始化(主要原因)
- 三次握手才可以同步双方的初始序列号
- 三次握手才可以避免资源
原因一:避免历史连接
简单来说,三次握手的首要原因是为了防止旧的重复连接初始化造成混乱。
网络环境是错综复杂的,往往并不是如我们期望的一样,先发送的数据包,就先到达目标主机,可能会由于网络拥堵等乱七八糟的原因,会使得旧的数据包,先到达目标主机,那么这种情况下TCP 三次握手是如何避免的呢?
图中可见,为了避免历史连接,tcp采用了数据验证的方法。因为前面我们已经知道,第一次接触时客户端发送的包的TCP段会写入序列号,而服务端会消息的时候会把这个序列号+1写入应答区,以供客户端验证;同理,当服务端把消息传回的时候也会把一个新的序列号写入,而当客户端接收后进行第三次握手时会把这个序列号+1写入应答区,以供服务端验证。
如果是两次握手连接,就不能判断当前连接是否是历史连接,三次握手则可以在客户端(发送方)准备发送第三次报文时,客户端因有足够的上下文来判断当前连接是否是历史连接:
- 如果是历史连接(序列号过期或超时),则第三次握手发送的报文是
RST 报文
,以此中止历史连接; - 如果不是历史连接,则第三次发送的报文是
ACK 报文
,通信双方就会成功建立连接;
原因二:同步双方初始序列号
TCP 协议的通信双方, 都必须维护一个「序列号」, 序列号是可靠传输的一个关键因素
序列号的作用:
- 接收方可以去除重复的数据;
- 接收方可以根据数据包的序列号按序接收;
- 可以标识发送出去的数据包中, 哪些是已经被对方收到的;
可见,四次握手其实也能够可靠的同步双方的初始化序号,但由于第二步和第三步可以优化成一步,所以就成了「三次握手」。
原因三: 避免资源浪费
如果只有「两次握手」,当客户端的 SYN 请求连接在网络中阻塞,客户端没有接收到 ACK 报文,就会重新发送 SYN ,由于没有第三次握手,服务器不清楚客户端是否收到了自己发送的建立连接的ACK 确认信号,所以每收到一个 SYN 就只能先主动建立一个连接,这会造成什么情况呢?
如果客户端的 SYN 阻塞了,重复发送多次 SYN 报文,那么服务器在收到请求后就会建立多个冗余的无效链接,造成不必要的资源浪费。
(这一点与第一点原理相同,即客户端在每接收到返回包后不会进行验证,而是建立连接)
1 | 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
状态,至此客户端也完成连接的关闭。几个注意点:
- 每一次接收或者发送后都会改变状态
- 只有主动关闭连接才有TIME_WAIT状态。
为什么需要挥手四次?
再来回顾下四次挥手双方发 FIN 包的过程,就能理解为什么需要四次了。
- 关闭连接时,客户端向服务端发送
FIN
时,仅仅表示客户端不再发送数据了但是还能接收数据。 - 服务器收到客户端的
FIN
报文时,先回一个ACK
应答报文,而服务端可能还有数据需要处理 - 和发送,等服务端不再发送数据时,才发送
FIN
报文给客户端来表示同意现在关闭连接。
从上面过程可知,服务端通常需要等待完成数据的发送和处理,所以服务端的 ACK 和 FIN 一般都会分开发送,从而比三次握手导致多了一次。
ps:
TIME_WAIT
状态是一个比较重要的知识点,这个状态有好处也有缺点,还有优化方法。以后会具体说到。
预知后事如何
到这里,关于TCP的初步知识就告一段落,但是只是这一个分类的。TCP是一个很复杂的内容,上面的文章只是讲了一些皮毛,对这个知识块的大体框架有一个了解。有关TCP的知识会在以后的计网分类继续深入说的。