TCP(Transmission Control Protocol,传输控制协议),它是最常用传输层协议,也是最稳定传输层协议,很多上层应用都是依赖于TCP进程传输数据。
TCP 属于传输层协议,它为应用层提供了可靠的字节流服务。在网络协议栈中对它的描述要比对其它协议的描述复杂的多,这也导致了lwip中很大一部分代码都是用于描述TCP协议的。
IP包畅游在网络中,从主机A出发,风尘仆仆赶到主机B,虽然整个过程占用时间都是以毫秒或者秒计算的,但期间风险重重啊,可能发生的情况:IP包在行进中因拥塞而被路由器、交换机抛弃,以至于IP包在传输期间可能会被丢掉。但主机A会希望所有的IP包都安全地抵达主机B,这需要一个机制来保障数据能稳定传输,于是专家们制定了TCP传输协议。
TCP是面向连接的技术。也就是说,基于TCP的两台主机,在通信之前要先建立信息交互(IP协议则没有这种交互),主机之间的设备(路由器)和线路,都仅是只负责处理协议栈模型的下三层(物理层、数据链路层和网络层)的工作。
TCP一旦发现传输出错就会重新传输数据包,直到所有数据安全、正确地传送到目的地,再递交到应用层。
TCP
是一个面向连接的协议,无论哪一方向另一方发送数据之前,都必须先在双方之间建立一个连接,否则将无法发送数据,一个TCP连接必须有双方 IP
地址与端口号,而面向连接意味着两个使用 TCP
的应用(通常是一个客户端和一个服务器端)。就像打电话一样。
当TCP协议发出一个TCP报文段后,它启动一个定时器,等待目的端主机 确认
收到这个报文段,如果不能及时收到一个确认,将重发这个报文段。一个完整的TCP传输必须有两个端的主机进行数据的交互,接收方在接收到数据之后必须正面进行确认,向发送方报告接收的结果。
因为TCP协议依赖的是IP层的服务,IP数据报的传输是无连接、不可靠的,因此它要通过确认来知道接收方确实已经收到数据了。
应用数据会被分割成TCP协议认为最适合发送的报文段,这些其实是动态调整的, TCP
协议只能是尽可能发送最大报文段(MSS)以保证数据传输的速率。
在发送方想要发送数据的时候,由于应用程序的数据大小、类型都是不可预估的,而 TCP
提供了缓冲机制来处理这些数据,实际上TCP协议发送数据时,如果数据还未到 TCP
报文段合适的大小,这些数据可能会被临时缓存,知道超时或者等待到数据合适到TCP报文段时才发送出去,这也是大名鼎鼎的 Nagle
算法(当然可以禁用它哈哈哈)。这样就能保证一次传输数据的效率并且减少网络中的流量。
发送方在将数据发送出去后并不会 立即
删除数据,而是让数据依旧保存在缓冲区中(实际上是使用链表维护的),因为发送出去的数据不一定能被接收方正确接收,它需要等待到接收方的确认再将数据删除。
同样的,在接收方也需要有同样的缓冲机制,因为 TCP
协议是以字节流发送数据的,这些数据可能会分割成多个 TCP
报文段,而且在网络中传输的各个 TCP
报文段到达目标主机的时间可能是不一样的,这就导致数据失序,接收方需要把这些数据报组装成完整且有序的数据,然后再递交到应用层中,在这之前的数据都会被缓存。此外还有可能导致接收方接收到重复的数据,因为IP数据报会可能发生重复,这就必须在接收方将重复的数据剔除。
在 TCP
连接建立后,任何一个主机都可以向另一个主机发送数据,数据是双向流通的。在实际情况中,很大一部分的 TCP
的确认是通过 捎带
的方式来实现,即接收方把确认信息放到发送给发送方的报文段中,不必单独为确认信息申请一个报文。
当然断开连接也是一样的,任意一方都可以主动断开,不过对于服务器来说,应用程序一般不会主动断开 TCP
连接,这其实是有点 贪婪思想
了,不到万不得已,都不会主动断开连接。
一条 TCP
连接每一侧的主机都设置了缓冲区域,很多时候可能对于接收方来说并不会立即去处理这些数据,如果发送方一直发送数据,就很可能导致接收方来不及处理,就像喂小孩子吃饭那样,你一直给他塞饭,他根本咽不下是吧,所以 TCP
协议就提供了来了控制,消除发送方使接收方缓冲区溢出的可能,流量控制其实是一个速度匹配的服务,让接收方的处理速度赶得上发送方的发送速度,就像小孩子,吃饭的速度赶得上你喂食的速度,但是小孩子的自身(硬件)是没法改变的,总不能让他有我们的吃饭速度对不对,那么只能改变我们喂食的速度了,我们慢一点,小孩子就不会被噎着。
TCP
协议通过让维护一个接收窗口的变量来提供流量控制,它是接收方用于给发送方一个指示:我还能接收多少数据,接收方会将此窗口值放在 TCP
报文的首部中的窗口字段,然后通过确认报文发送给发送方,这个窗口的值的大小是在发送数据的时候动态调整的。
当然,如果接收窗口的大小是 0
怎么办?能想到这个问题的人很牛逼, 留个言我给你点个赞
!
简单来说说协议栈通信的处理无非是两种方式触发,要么是事件、要么是时间(定时器),事件触发就是我在等你连接事件发生,然后我响应,时间触发就是超时后我再处理,都是比较容易理解的。
回到如果接收窗口的大小是 0
怎么办这个问题,那么接收窗口为0时,发送方应该怎么发送数据呢,总不能不发,也总不能是直接断开吧,既然这样子,那应该什么时候发呢?
当发送方收到接收方的报文,报文中指定接收窗口的大小为0,那么这是一个事件,发送方就会启动一个探测定时器,来探测接收方什么时候能接收数据,而定时器就可以产生超时对吧,每次超时就发送一个字节的数据给接收方,并且记录超时的次数并且刷新定时器,如果在超时之前收到来自接收方的报文,报文中指定接收窗口的大小不为0,这也是一个事件,那么就关闭探测定时器,然后正常发送数据。当然,如果超时次数到达极限,将终止 tcp
连接或者是 reset
。
在局域网中传输数据的话,仅使用流量控制就能达到速度匹配的结果,这种理想情况还是非常好的,但是实际情况中,在发送方和接收方之间存在多个路由器和速率较慢的链路时,就有可能出现一些问题。一些中间路由器可能缓存IP数据报,并有可能耗尽存储器的空间,这就导致这些中间路由器不再接收IP数据报并转发,然后这些报文将被丢弃,而TCP协议发现被丢弃了又会重新发送,这种情况就导致越来越严重的问题——拥塞。就像过年时候回家的大塞车。
TCP协议的发展,有越来越多的控制算法来避免这些问题,比如慢启动算法,拥塞避免,拥塞发生,快速恢复等算法,慢启动为发送方的TCP增加了另一个窗口:拥塞窗口。当与主机建立 TCP连接时,拥塞窗口被初始化为 1个报文段(即另一端通告的报文段大小)。每收到一个 ACK,拥塞窗口就增加一个报文段( 注意增加的不是字节而是报文段
)。
简单来说就是:发送方开始时发送一个报文段,然后等待 ACK
。当收到该 ACK
时,拥塞窗口从 1
增加为 2
,即可以发送两个报文段。当收到这两个报文段的 ACK
时,拥塞窗口就增加为 4
,这是一种指数增加的关系。
当然啦,我这篇文章还是非常表面地介绍这些知识,更多详细知识还是得看书!
全部0条评论
快来发表一下你的评论吧 !