目录
2.tcp协议通信是全双工的,且双方地位对等。即通信双端既可以发送数据,又可以接收数据。
摘要
这篇文章主要介绍网络协议栈中传输层的tcp协议,除了tcp协议本身,还会谈到linux内核中网络协议栈的周边问题的理解,介绍使得tcp协议可靠、高效的一些策略。
可靠性:校验和 序列号(按序到达) 确认应答 超时重传 连接管理 流量控制 拥塞控制
高效性:滑动窗口 快速重传 延迟应答 捎带应答
首先我们看到,tcp在网络协议栈是位于++传输层++ ,它的全称是"++传输控制协议++"。在发送端将数据包通过应用层(使用系统调用)交给传输层,再向下交付。在接收端将数据包从传输层向上交付交给应用层。
网络协议栈的认识
谈谈我对于网络协议栈的理解:所谓的这种层状结构的网络协议栈,只不过是画出来为了让人们方便理解这种先后交付的逻辑。在操作系统中,网络协议栈的本质不过是程序执行的顺序结构,各层协议本质就是程序中的各个不同逻辑,这些逻辑分别对报文进行封装,解包,分用,各司其职。
计算机中同一时刻一定会存在大量的数据报文在协议栈中上下传送,所以操作系统要想对这些数据报文进行管理,就要先描述,再组织。即有描述数据报文的结构体,并用双向链表将这些结构体对象连接起来。这些结构体对象负责在操作系统内的网络协议栈上下传送。
协议在操作系统中也是以结构体的形式存在,由各种结构体来共同描述一个协议。
数据在各层协议中加报头:
sk_buff的缓冲区指针data向前移动sizeof(Protocol),将协议结构体对象拷贝到缓冲区。
数据在各层协议中解报头:
将前sizeof(Protocol)字节数据拷贝出来,强制类型转换得到协议报头。缓冲区起始指针向后移动sizeof(Protocol)。
详谈tcp
报头剖析
有了上面的认识,接下来专门谈谈tcp协议,tcp报头如下。
1.端口号的作用是为了将报文发送给应用层指定的进程。
2.tcp协议通信是全双工的,且双方地位对等。即通信双端既可以发送数据,又可以接收数据。
发送数据时,应用层只需要++将数据拷贝给tcp的发送缓冲区++ ,托管给tcp,交由tcp在合适的时机将数据发送出去。接收端主机只需要++将接收到的数据拷贝给tcp的接收缓冲区++,等待应用层读取。
如果应用层疯狂发送数据,发送缓冲区被写满了,此时应用层进程阻塞在send接口处,进程被挂起,当发送缓冲区内有空间了,再调度该进程。
如果应用层疯狂读取接收缓冲区的数据,接收缓冲区已经空了,此时应用层进程阻塞在recv接口处,进程被挂起,当接收缓冲区内有数据了,再调度该进程。
这些特性和文件的读写是类似的。
3.确认应答机制
tcp协议要保证数据传输的可靠,确定性。即一个报文发给对端主机,发送端要知道对端是否真正收到了这个报文(可能在网络中丢包了),++确认应答机制++应运而生。
确认应答机制:tcp在每给对端发送一个报文,对端就一定要响应回来一个ack应答报文,来证明对端已经收到了对应报文。 这个机制是通过报头中的++序号、确认序号和标志位++实现的。
用序号来标定tcp发送缓冲区中数据的顺序性,唯一性,用确认序号来标定发送的数据已经收到,并将ack标志位 置为1表明这个报头内含有应答。例如一个报文的序号为300,发送到对端主机,对端主机ack回来一个确认序号为301的报文,表明301以前的报文对方全部收到。
同时序号还可以保证数据按序到达接收端。
这里有一个有意思的定义,确认序号的定义:确认序号=发送报文序号+1 ,表明确认序号之前的所有报文全部收到,而不是只表明一个报文的收到 。这样的设计好处是,当我们给对端发送了若干个报文,比如序号为100,200,300,对端都收到了,但是在ack应答这三个报文时,101和202ack丢失了,但是301ack被发送端收到了,发送端也就得知了100,200报文对方也收到了,这样的设计,就让tcp协议支持了ack应答的少量丢失,加强了tcp的可靠性。
是
捎带应答机制: 报头中既有序号,又有确认序号,这样的设计让一个报文既可以传输数据,又可以作为某个报文的应答ack报文。把两个报文巧妙的合在了一起,增强了tcp传输的效率。
超时重传机制: 如果发送端主机将报文发出,在固定时间之后并没有收到对端主机的ack应答,此时有可能ack应答丢失了,甚至有可能发送的报文丢失了。如果是报文丢失了 ,发送端在固定时间之后没有收到ack应答,就会自动重新发送一份刚刚发送的报文,确保可靠性。如果是ack应答丢了 ,即对端已经收到了报文,发送端不会收到应答,同样超时重传,但是对端已经收到了这个报文,对端收到重复报文该怎么办?通过序号去重,将相同报文直接丢弃!
快速重传机制: 如果发送端发出连续的多个报文,接收端给的ack应答中有许多冗余的相同ack,此时即是下面的这种情况
此时就能唯一确定序号为2的报文在传输过程丢失了,发送端在收到三次冗余ack就马上补发丢失的报文(2号),++快速重传++,不需要等待超时重传定时器溢出。
4.流量控制
流量控制指的是双端使用tcp协议时,发送端根据接收端的接收能力来判断每次发送报文的多少。
通俗的理解就是:接收端接收缓冲区剩余空间较多,发送端就可以每次多发送报文,接收端接收缓冲区剩余空间较少,发送端就每次少发送报文。
发送端是如何得知接收端接收缓冲区剩余大小的呢?tcp如何实现的?
在tcp报头中有一字段:窗口大小
每次发送端给接收端发送报文后,在对端就会将缓冲区的剩余空间设置到ack应答报文中的16位窗口大小字段中,发送端就可以得知接收端缓冲区接收能力,进而进行流量控制。
具体发送端是如何控制的呢?滑动窗口
为了保证数据发送的顺序性,把tcp的缓冲区看作一个数组,滑动窗口就是两个指针分别指向窗口的前和后,每次发送报文时,++tcp负责将滑动窗口内的所有数据打包成若干报文全部发送++ ,对端根据接收缓冲区的剩余大小调整ack报头中的窗口大小,发送端收到ack应答时,再++根据ack报头中的窗口大小调整滑动窗口的大小++。这便是tcp的流量控制策略。
上面谈到的超时重传策略,如果报文发出一段时间后没有收到ack应答,发送端将重新发送报文,在这个等待期间,报文数据保存在哪里?发送端的滑动窗口内。滑动窗口只有在最左侧数据对端可靠性收到后,才会向前移动继续发送新的数据。
延迟应答: 为了使得tcp传输更加高效,接收端在收到数据时,可以++稍微等待接收缓冲区的数据被应用层读取,然后就可以在ack应答报头中设置更大的窗口大小,发送端收到ack应答使得滑动窗口尽可能大++,一次性发送更多数据。
5.拥塞控制
两台主机在网络通信过程中,不仅要各自自身保证传输的可靠性、高效性,还要考虑到网络的拥塞状态。如果网络状态已经十分拥塞了,贸然向网络中发送大量数据会导致网络状态雪上加霜。
tcp在向对端发送大量数据时,采用慢启动 的机制,++先向网络中发送少量数据 "探路",再发送大量数据++ 。具体的策略是每次向网络中发送的数据量指数级增长。
在发送数据指数级增长的过程中,如果某次发送出现大面积丢包,即网络发生拥塞,会立即重新开始"探路"。
而实际每次发送数据量要根据发送端滑动窗口大小和拥塞窗口大小共同决定,实际发送数据量=min(滑动窗口大小,拥塞窗口大小)。
总结:拥塞控制是tcp为了尽可能给对方发送多的数据还要避免给网络造成太大压力的折中方案。
TCP连接和断开
连接:三次握手
在两台主机建立TCP连接时,需要通过三次握手来验证双端互发数据是否通畅,即A主机可以将数据成功发给B主机,B主机可以将数据成功发给A主机。
A主机主动建立连接,调用connect()向B主机发起SYN,B主机收到SYN后,给A主机发送ACK应答并一并向A主机发送SYN(捎带应答),A主机收到SYN再向B主机发送ACK应答,到此一个双向畅通连接建立成功。
断开:四次挥手
由于tcp通信是全双工的,tcp连接断开之前,两台主机要进行四次挥手,使得发送端到接收端的信道关闭,接收端向发送端的信道关闭。
在三次握手中,ACK应答和FIN通过捎带应答的方式合并了,但四次挥手中为什么没有合并呢?
首先,发送端应用层关闭套接字文件描述符,此时如果发送缓冲区内还有数据,则将剩余数据发送之后再向对端发送FIN表明要关闭单方向连接,即A发送端不再向B发送数据了,B端进行ACK应答表明FIN已收到,A到B单方向连接正式关闭。
同样,此时B端发送缓冲区内可能还有未发送完的数据,等待将发送缓冲区剩余数据发送之后再向对端发送FIN表明要关闭B到A的单方向连接,A端进行ACK应答表明FIN已收到,B到A单方向连接正式关闭。
由于断开时对端的数据可能还没发送完,所以中间两次报文合并的几率很小,而在建立连接时,要向对端建立连接,对端没有要等待的时间而导致时间不同步,就可以捎带应答将两次报文合并了。
网络编程Socket套接字,文件,传输层之间的联系
用一张图将linux文件系统和网络通信联系起来,linux内核使用各种结构体将一个的网络连接维护起来。