本质:传输层协议
特点:相对于UDP,面向连接、字节流和可靠传输
TCP头部结构:固定部分20字节(内容略,可选信息有40字节,最长60字节)
TCP建立与关闭:(抓包过程读值m和n都是初始为随机值ISN)
报文段 | 方向 | 标志位情况 | seq值 | ack值 | 状态说明 |
---|---|---|---|---|---|
1 | A>>B | SYN | m | - | (主动打开) SYS_SEND >> |
2 | A<<B | SYN | n | m+1 | (被动打开 LISTEN)SYS_RCVD >> |
3 | A>>B | - | - | n+1 | ESTABLISHED >> ESTABLISHED |
4 | A>>B | FIN | m+1 | n+1 | (主动关闭)FIN_WAIT_1 >> |
5 | A<<B | - | - | m+2 | (被动关闭)CLOSE_WAIT >> FIN_WAIT_2 |
6 | A<<B | FIN | n+2 | m+2 | LAST_ACK >> |
7 | A>>B | - | - | n+3 | TIME_WAIT >> CLOSED |
TIME_WAIT状态
意义:理论上报文段5是不需要的,因为报文段6已经包含确认信息。但是需要存在一个状态,使得:
1.可靠的终止TCP连接(如果没有报文5,那么报文6回复时A端就直接跳过TIME_WAIT进入CLOSED状态,如果次数发生了发生了报文段7丢失的情况,那么报文6一直会重复发送,此时A端已经closed所以会发送复位报文段导致B端错误)
2.让迟来的TCP报文have enough time to abort(使后来连接同一个ip和端口的端不会收到之前连接的报文)
持续时间:TIME_WAIT要持续2MSL才会进入CLOSED。MSL是TCP报文段在网络中的最大生存时间,RFC1122的建议值是2min。(也就是说高并发下会疯狂占用资源- -)
复位报文段
TCP头里的标志位:RST
功能:通知对方关闭连接或者重新建立连接
产生的情况:
1.访问了不存在的端口:此时目标会给sender回应一个复位报文段,sender应该关闭或者重新连接。实际上端口处于TIME_WAIT时也会收到复位报文段。
2.异常终止连接:用发送复位报文段而不是发送结束报文段的方法来终止连接,此时发送端排队发送的数据会被全部丢弃。
3.处理半打开连接:一方因为异常而终止连接,而另一方还维持着,称之为半打开状态。半打开状态的如果往连接里写入数据,则对方回应一个复位报文段。
TCP交互数据流
以telnet登录本机,用shell执行ls命令为例,下面是抓包情况:
(win值:窗口大小,告诉对方本端还能接受多少字节以控制TCP流量,seq和ack第一行后都为偏移值)
报文段 | 方向 | 标志位情况 | seq值 | ack值 | length值 | 状态说明 |
---|---|---|---|---|---|---|
1 | A>>B | PSH | m:m+1 | n | 1 | client发送'l'字母 |
2 | A<<B | PSH | n+1:n+2 | m+1 | 1 | server确认1并回显字母'l' |
3 | A>>B | - | - | n+2 | 0 | client确认2 |
4 | A>>B | PSH | m+1:m+2 | n+2 | 1 | client发送's'字母 |
5 | A<<B | PSH | n+2:n+3 | m+2 | 1 | server确认4并回显字母's' |
6 | A>>B | - | - | n+3 | 0 | client确认5 |
7 | A>>B | PSH | m+2:m+4 | n+3 | 2 | client发送回车和EOF |
8 | A<<B | PSH | n+3:n+176 | m+4 | 173 | server返回ls命令的结果 |
9 | A>>B | - | - | n+176 | 0 | client确认8 |
10 | A<<B | PSH | n+176:n+228 | m+4 | 52 | server返回一个回车+换行符+PS1环境变量 |
11 | A>>B | - | - | n+228 | 0 | client确认10 |
因为客户端输入速度远远小于服务器处理速度,所以服务器通常使用延迟确认(报文2,5,8,10)以减少发包数。
Nagle算法了解下。
TCP 成块数据流
考虑用FTP传输一个大文件,启动vsftpd服务器程序,并执行ftp登录它,然后再ftp中输入get命令从服务器上下载一个几百m的大文件,同时进行抓包。
抓包过程可以看出,当传输大块数据时,发送方会连续发送多个TCP报文段,接受方可以一次性确认所有这些报文段。那么问题就是在确认之前能够连续发送多少包了。
能够连续发送的TCP报文段数量n是由确认的接受通告窗口(win值)决定的,n=win值 *64字节(窗口扩大因子为6时) / length值。
另外每4个TCP报文段会带一个PSH标志,以通知客户端应用尽快读取数据,不过好像没啥用,因为它知道win值不为0。
带外数据
Out of Band,OOB,比普通数据有更高的优先级,无视队列立刻发送。用的不多,了解即可。
TCP超时重传
TCP要保证可靠性就必须要处理超时或者丢包的情况。
设定iperf服务器程序,然后执行telnet登录改服务器程序,之后发送一些数据给服务器,然后断开服务器的网线并再发一些数据,同时进行tcpdump抓包。
在抓包时时保留时间戳以推测TCP的超时重传策略,因此重传的5次报文的时间间隔分别为0.2s、0.4s、0.8s、1.6s和3.2s,可见TCP一共进行5次重传,每次的重传超时时间都会增加一倍。5次失败之后有IP和ARP接管,直到telnet客户端放弃为止。在实例中TCP连接超时后坚持了15min。
拥塞控制
本质:一个闭环反馈控制,标准文档RFC 5681。一次写入的数据量称为SWND(Send Window,发送窗口),由RWND(Receive Window,即win值)和CWND(Congestion Window,发送方引入的拥塞窗口)的较小值决定。SWND太小导致网络延迟,太大导致网络阻塞。
慢启动和拥塞避免:CWND被初始化为IW(Intial Window,大小为2~4个SMSS,即发送者最大段大小),此后发送端没收到一个接受端的确认,CWND就按照以下公式增加:CWND+=(N,SMSS),N就是包含之前未被确认的字节数。这样一来没有其他条件的话,CWND会指数增加(看来慢启动其实并不慢)。
为了避免慢启动过快导致网络阻塞,需要定义一个慢启动门限(slow start threshold size, ssthresh)。拥塞避免算法使得CWND按照线性增加,从而减缓其扩大。RFC 5681中提到了两种实现方式,自行了解。最后一提,当传输超时或者TCP重传定时器溢出时使用慢启动和拥塞避免,但是当接收到重复的确认报文段时将使用快速重传和快速恢复,但是第二种情况发生在定时重传器溢出的话也当做第一种情况处理。
如果是第一种情况,即传输超时,就执行重传并做一下ssthresh的调整(式1):
ssthresh = max(FlightSize/2, 2*SMSS)
FlightSize为已经发送但是没被确认的字节数。这样调整后CWMD将小于SMSS,也小于调整后的ssthresh,从而再进入慢启动阶段。
快速重传和快速恢复:发送端收到3个重复的确认报文段后,就认为拥塞发生了,然后用快速重传和快速恢复算法来处理拥塞,过程如下:
1.ssthresh按照(式1)重新计算,然后立即重传丢失的报文段,并重新设置CWND=ssthresh+3*SMSS。
2.每再收到1个重复的确认时,设置CWND+=SMSS,此时发送端可以发送新的TCP报文段。
3.新数据被确认时,设置CWND=ssthresh
快速重传和快速恢复完成后将恢复到拥塞避免阶段。