这是偶然网上搜出来的一本书,由于也正好想补习一下TCPIP网络协议的基础,发现这本书很适合我这种上课时没能好好听课的人,虽然其它层的知识也不是很熟,不过由于TCP/IP的知识跟日常使用接近,于是也直接从传输层协议一章开始看起了。相比某些跟教科书差不多的关于网络基础的技术文章,借用原文以及书评的评价就是:
Wordy but worthy, comprehensive but comprehensible.
其实也是因为是英语原文,所以能让人更仔细审视到每一个细节,从而不至于像读教材那般枯燥。本文作为TCP入门,但最好读者对协议有一定学习经验。
UDP小结
UDP和TCP协议的内容相比大概只有后者的十几分之一,UDP首部只有四个字段,共8个字节,不存在可选字段,它们是: - 源端口号 - 目的端口号 - UDP数据长度(包含头部和数据部分) - UDP checksum(校验和)
checksum由伪首部和消息体计算得出,伪首部不会被实际传输。 几个使用场景: - 时效性以及效率的重要程度大于完整和可靠性的时候,时效性意味着重传没有意义 - 当一个简单协议可以处理,并且只需处理TCP特性提供所处理的一两个问题的情况(其它TCP所解决的大部分问题在这里没有)。比如一些简单请求/应答,数据长度较短,不需组装数据等其它处理,对于唯一的数据丢失问题有简单重传机制来解决。 - 要应用广播、多播的情况等
由于length field只有16位,所以一次只支持最大64K的数据包传输。UDP相当于使用IP协议的最简单接口,除了应用层所必须使用的端口,以及简单附加的另外两个字段,UDP几乎不提供其它信息和机制,相当于把数据直接给网络层,传走就完事了。
TCP概要
TCP需要应用程序独立地处理每一个独立的连接,一个连接由一个四元组(双方ip和端口)来唯一识别; TCP能做什么: - Addressing/Multiplexing:多个应用程序都通过TCP层通信,那么就必须区分它们。 - Connection Establishment, Management and Termination - Data Handling and Packaging:因为要分段,所以要有打包工作 - Data Transfer:把数据交给网络层传输 - Providing Reliability and Transmission Quality Service - Providing Flow Control and Congestion Avoidance Features
TCP不能做什么: - 指定应用程序如何使用TCP - 保证安全性 - 数据内部的分界;TCP只负责处理一整段连续的字节流,数据内部的分段应该由应用层来识别,应用程序要想发送几段不同的数据,识别这几段也只能由应用程序自己来处理。 - 保证万无一失的连接;TCP只靠重传来提供数据可靠性,不能解决网络环境中的每一个问题。
因为有重传机制,所以TCP协议背后的设计它代表的是程序健壮性的思想。一旦建立了连接,代表今后两台设备的通信都要按照既定的程序来走,连接建立的时候会约定MSS(Maximum segment size)以及其它一些信息,传输中会告诉对方自己准备好了能接受多少数据,应用以字节流的形式发送给TCP,通过TCP接受字节流,应用层不关心中间的数据拆分传输和组装等过程,因为这些是TCP的工作,并且传输长度无限制。 应用程序的字节流→TCP→转换成segment交给IP datagram,这就带来两个问题需要解决: - 这些数据分段需要保证被全部完好接收; - 这些数据需要按照一定排序还给应用程序。
可能会觉得意外的一点是,为了提供确认机制,TCP里面每一个字节都有自己的编号,通过这种方式能追踪到每一个字节,但并不是一个一个字节来确认的,用的也不是记录了每一个编号这种做法,具体下文。
TCP 滑动窗口机制
先说明一下,这里写的TCP数据包英文有时用的是message,有时是segment,它们指的大都是同一件东西。
为了循序渐进的理解,书中采用渐进的方法引入模型,第一个模型没有数据确认机制这里不放上来了,第二个模型如下:
缺点就是一次只能传输一份message,要等到确认完才能继续下一个。所以有了能同时传输多份message的模型:
因为不可能一个个字节来收发确认,所以TCP里这里面的id field(号码)就成了Sequence Number,这个seq就代表了所在TCP segment中,数据最后一个字节的编号,这样每次发送和确认的就是一个范围内的所有字节。
发送缓冲区里的字节,在任意时刻都可分为四类:
两个窗口:
- Send window(发送窗口,大小等于接收端允许的未被确认的字节数,等于已发送未被确认+可发送的字节数)
- Usable window:大小等于可发送(可立即发送)的字节数
后两类Byte的分界线取决于第一个未被确认的字节+窗口大小。如果第二类中,前面32\~36这5个字节被确认收到了,也就是说收到了ACK=37(代表着编号少于37的字节都被收到了,所以并不是36),此时窗口大小不变,那么窗口就会向右移动5个字节,对应发送窗口就会多出5个字节。按照字节范围确认接收的办法,体现原文说的,TCP是个“cumulative acknowledgment system”。 发送端中,TCP不会分别离散地去确认segment,一排segment中,只要中间有一个segment没收到,后面的所有segment就不会给你发ACK,意味着即使后面的segment收到也有可能需要对面重新发一遍(这里保留一个问题,后面的segment会保留还是会在前一个包未被收到的条件下被立即丢弃?这里还未讲解到SACK。)
TCP连接的建立、终止
这里提一下一些要点: - 连接开启时,每一个连接分配一个TCB(Transmission Control Block)里面存放:socket号、缓冲区指针等,实现滑动窗口系统,负责管理窗口各种数据 - TCP连接建立做好三件事:联系交流,序列号同步,参数交换 - 控制信息用控制位(control flag)代表控制信息的意图,通过打开多个控制位,一条信息可以既负责传输数据又负责确认接收数据 - 本来有四个收/发过程,中间两个SYN和ACK合并成一条信息,所以是三次握手 - 同步开启连接的情况发生,是在发出SYN之后,ACK回来之前这个时间段收到了SYN。(对于这个过程说法是两次同时握手?还是四次收发比较准确吧。) - 对应C/S的不同角色,有主动开启和被动开启两种方式 - 连接建立实现三个目的: 1. 接触对方,知道你是从哪来 2. Sequence Number的同步,连接双方都得让对方知道自己选择的用于第一次传输的ISN(Initial Sequence Number) 3. 其它一些参数的交换 - SYN表明所在的这个segment是用来初始化一个TCP连接的 - ACK表明这个segment上填了用于确认接受到数据的ack号
三次握手、四次挥手很多人都熟悉,然而这两个词只是方便记忆,盯着字面来看对理解整个过程是不利的。这里有整个流程的状态转移图:
.png)
TCP建立一个连接的过程
正常建立连接:

同步开启连接:

一些理解:SYN_RCVD代表已发出SYN并且收到过SYN(发出和收到前后顺序不限),并且未收到且正在等待ACK,之后一旦收到ACK就会进入ESTABLISHED。
seq并不像某些例子举的那样总是0或1,每次开启连接都选择同样的一个ISN会造成问题,比如与上一次连接的数据混淆等等,所以每次会挑选不同的ISN发起连接,挑选ISN的机制用了时钟,这里不叙述。对面收到SYN segment后,会让回复的segment中ACK=Sequence Number + 1,这个ACK就告诉了对方:“你下次发的数据中字节的编号应从这个号码开始”,然后带着自己的ISN一起发回去。以下图为例,当连接建立好后,Client实际传输的字节就会从4568开始编号,Server端发出去的字节则是从12999开始。

其它交换的参数其中有以下几个: - Window Scale Factor(用来扩大发送窗口) - Selective Acknowledgment Permitted(是否允许使用SACK) - AlternateChecksumMethod(表示会换一种计算检验和的方式)
后面讨论了一些关于半开状态、reset信号、keep-alive的内容,这里也不做细述。
TCP关闭一个连接的过程
理解关闭连接背后的需求与问题:TCP连接关闭两方走的流程是不一样的,因为发送请求的一方,它的行为肯定与接收请求的一方不一样。发送方是由其应用程序主动发起,接收方还要等待此侧应用程序关闭。 在接收方等待自己这边应用程序关闭,即发出自己的FIN之前,还可以发出数据(outstanding data)。 双方在发出FIN之后都只能接收不能发出数据。
正常发出关闭请求的过程:
同步发起了关闭请求的过程:

对关闭连接的一些理解:收到FIN,发出ACK,进入CLOSE_WAIT,进入CLOSE_WAIT后,会告知应用程序对面要关闭连接,并等待应用程序返回关闭请求到TCP层,然后才发出FIN过去,进入LAST_ACK,当此状态下收到对于自己的FIN的ACK,即直接进入CLOSED状态。
(一方主动)关闭,发出FIN,并进入FIN_WAIT_1,等待收到ACK,或此时有可能收到另一个FIN: 1、收到ACK时,还要等待对面发一个FIN,如果FIN过来,就会发ACK回去并进入TIME_WAIT; 2、收到FIN时,会回一个ACK回去,但此时自己发的FIN还未收回ACK,进入CLOSING,然后等到ACK回来后,进入TIME_WAIT。
个人总结: - 进入连接,一方应收到一个SYN,一个ACK(对于自己SYN的ACK); - 退出连接,一方应收到一个FIN,一个ACK(对于自己FIN的ACK);
TCP首部格式
// 待续..