之前提到网络层的ip协议,是无连接的协议,它不会占用主机之间的通信线路,ip只是保证了数据在互联网中的流动。
而数据传输的控制就交给了上层协议UDP和TCP
UDP协议适合网络直播这种应用,这样减少了数据的延迟,即使数据丢失一点也能接受。
源端口号是可有可无的(如果不需要收到回复,就可以不用添加),但一定要有目的端口号。
UDP长度存储了UDP用户数据报的整个长度。
UDP检验和则是判断数据是否有误,有误就丢弃。
也称为TCP的首部字段。
在网络模型的文章中,我们知道,传输层得到应用层传输下来的数据后,会添加一个头部,整合为一个新的数据,发送给网络层。
下面就是TCP报文头
前面五个部分是固定的,每个部分占32位,即4字节。五个部分固定为20字节。TCP报文头要求总大小为4字节的整数倍,所以添加选项后,会进行对齐填充。Java对象头也用到了这种方式。
源端口和目的端口
我们知道TCP协议是端对端的,如果两个进程之间需要通信,在本机上通过pid进程标识符来确定唯一标识的进程,可以实现本机的进程间通信。
但是如果两个进程分别运行在不同的主机上,pid就失效了,TCP报文头部就解决了这样的问题。
在传输层协议中,使用协议端口号,这样通过TCP协议来唯一标识主机中的一个进程,再通过网络层ip协议找到唯一标识的主机,就实现了不同主机的进程之间的通信。
因此,我们利用三元组(ip地址+协议+端口号)就可以标识网络中的进程,实现网络间进程的通信。
在计算机通信领域,这种唯一标识的模式也称为“套接字”,即“Socket”
虽然需要通信的重点是进程,但是我们只需要把报文传送到目的主机的端口,剩下的工作就交给TCP来完成
序号
序号位占用4个字节,在TCP连接中,传输的字节流中每一个字节都按照顺序进行编号,序号字段就记录了本报文段发送的数据的第一个字节的序号
比如这样,将字节流都进行编号后,将要发送的数据放入TCP缓存中,第一次可能先传输两个字节,加上TCP的头部组成一个报文段进行传输。
这次传输的第一个字节的编号为“1”,那么TCP的报文头的序号字段就会存储“1”
如果下次发送【4】【3】的时候,TCP报文头的序号字段就会存储“3”。
确认号
确认号记录的是:希望收到对方下一个报文段的第一个字节的序号。如果确认号为N,表示到N-1的所有数据都已经正确收到了。
比如B收到了A发送过来的报文,序列号字段为1,而传过来了20个字节,表明B收到了序号1-20的所有字节,所以B期望收到A的下一个字节的序列号是21,那么B发送给A的确认报文段中,会把确认号置为21。
数据偏移
该字段记录了数据起始位置到报文段起始位置有多远,即报文头长度
由于头部是有可选字段的,长度无法固定,所以用数据偏移来记录报文头的长度。
六个常见控制位
窗口
窗口字段占4字节,用来表示接受方的接口窗口,也就是允许下次发送方可以发送的数据量,以此控制发送方的发送速率,达到流量控制。
比如B发给A的报文段头部,确认号为501,窗口为800。
表明B已经收到前面的500字节了,下次能接受800个字节,希望收到501-1300的字节。
检验和
此检验和指的是对TCP报文的数据和头部进行奇偶校验,由发送方进行计算和存储,接受方进行验证。
紧急指针
当URG=1时,该字段才起作用,该字段表示本报文段中紧急数据的字节数。
选项
有一些可选字段,比如最大报文段长度字段、时间戳字段等。选项的字段不一定是4字节整数倍,所以需要填充字段。
TCP属于面向连接,所以TCP连接也是三个步骤
TCP连接的建立采用的是客户服务器模式,主动发起连接建立请求的应用进程称为客户,被动响应连接建立请求的应用进程是服务器。
当应用程序需要通过TCP和其他应用程序通信时,会发送一个请求到确认位置,双方握手之后,TCP会占用两台主机的通信线路来建立一个全双工的通信,直到被一方或双方关闭为止。
上面说得握手,就是我们所说的三次握手
下面就详细剖析下三次握手的流程
这里假设客户端和服务器端是首次进行通信,客户端主动打开连接,服务端被动打开连接。
服务器进入LISTEN状态,时刻准备接受来自客户端的请求
第一次握手,客户端发送建立连接的请求报文段:
SYN(同步位)=1,seq(序号)=x(随机产生)
因为之前提到的建立连接和接受连接时,都会将同步位设为1,初始序号字段则是主机中随机产生的。此时是没有传输应用层数据的,同时ACK=0,因为并不知道自己需要传输什么数据。
客户端就进入SYN_SENT状态
第二次握手,服务器接受到请求报文段,回复一个确认报文段:
SYN=1,ACK(确认位)=1,seq=y(随机产生),ack(确认号)=x+1
服务器会为TCP连接分配缓存和变量,此时响应连接,则SYN=1,由于收到了客户端发来的seq=x,所以需要回复表示已收到,期望收到序号x+1开始的数据了,所以ack=x+1,同样ACK就起作用了,于是ACK=1,同时主机随机分配一个初始序号seq=y。
服务器就进入了SYN_RCVD状态
第三次握手,客户端收到了确认报文段,返回一个“确认的确认”报文段:
ACK=1,seq=x+1,ack=y+1
客户端知道了服务器已经确认连接了,于是会为TCP连接分配缓存和变量。此时不需要同步位了,同样也可以传输数据了,收到服务器的seq=y,ack=x+1,所以下一步传输的序号seq=x+1,确认号ack=y+1,报文段中存放序号x+1开始的数据字节。
客户端进入ESTABLISHED状态,当服务器端收到这份数据时,也进入了ESTABLISHED状态
连接建立完成
为什么需要三次握手?不能是两次,四次?
三次握手主要是为了初始化Sequence Number,也就是序号seq,这样保证了数据传输的有序。第一次握手,客户端初始化seq;第二次握手,服务器收到客户端的seq,发送自己的seq;第三次握手,客户端表示收到了seq。
如果两次握手就建立的话,假如客户端先发送的第一次握手卡住了,过了一会儿,客户端重发请求,然后正常建立连接,双方正常传输数据完毕,释放了连接。
然后之前卡住的第一次握手到了,服务器端返回第二次握手,如果两处握手就能建立连接,那么此时服务器就进入ESTABLISHED状态,但是客户端并没有数据需要传输了,就会导致此时服务器没有收到任何响应而一直挂起,使得资源浪费。
如果是四次握手,即第三次只是传输的响应报文段,第四次才传输数据。本来第三次握手就能传输数据了,这属于画蛇添足,也浪费了资源。
三次握手中的隐患——SYN洪范攻击
SYN洪范攻击的方式就利用了三次握手的性质,攻击者客户端发送第一次握手后,不再响应服务器的第二次握手,服务器会认为客户端没有收到这次响应,这个TCP连接就一直处于挂起状态,服务器会不停地尝试发送第二次握手。比如在Linux,服务器端会分别等待1s、2s、4s、8s、16s这五次分别发送一次第二次握手,第五次发送后等待32s也没有收到回应,才会判定超时释放连接。在这63秒的等待时间内,攻击者客户端可以不停地发送这样的请求,产生了这种大量的TCP连接,这样导致服务器的SYN连接的队列耗尽,无法响应正常的TCP连接,最终导致服务器死机。
针对这种攻击,Linux提供了tcp_syncookies参数来进行防护。
当SYN队列满了之后,TCP会通过源地址端口、目标地址端口和时间戳,来建立一个特殊的seq(SYN Cookie)发回,如果是攻击者,就不会响应SYN Cookie的,而如果是正常客户端,就会将SYN Cookie回发给服务器,服务器端通过SYN Cookie建立连接。这样即使SYN队列满了,本次请求不在队列中,也能正常建立连接。
连接建立后,客户端故障了怎么办?
TCP设置了保活机制,在保活时间内,如果连接处于非活动状态,开启保活机制的一方,会向对方发送一个保活探测报文,如果没有收到响应就根据设置的保活时间间隔继续发送,直到发送探测报文的次数达到上限,此时仍没收到响应,就判断对方主机不可达,会中断连接。
和三次握手类似,四次挥手是说在TCP连接释放时,客户端和服务器端需要交换四个报文段,才能释放掉连接。
参与连接的任意一方都可以来终止该连接,连接结束后,主机内的资源(缓存和变量)将会释放。
下面假设客户端主动停止连接。
第一次挥手,客户端释放连接,发送释放连接报文段,停止传输数据,主动关闭TCP连接:
FIN=1,seq=u
终止位=1,同时消耗一个序号,是最后一次发送的数据的最后一个字节的序号+1。此时客户端就进入FIN-WAIT-1状态。
第二次挥手,服务器端收到客户端发来的报文,回复一个确认报文:
ACK=1,seq=v,ack=u+1
表示自己已经收到了客户端的报文段,作为回应。服务器进入CLOSE-WAIT状态(客户端收到该报文时,客户端进入FIN-WAIT-2状态),服务器端会通知高层的应用进程,客户端要释放连接了,此时处于半关闭状态,服务器还能发送数据,但是客户端不再发送数据了。
第三次挥手,服务器发送完最后的数据后,也需要发送释放连接报文段:
FIN=1,ACK=1,seq=w,ack=u+1
因为服务器没有收到数据,所以确认号不会变。服务器发送完该报文段后,就进入LAST-ACK状态。
第四次挥手,客户端收到了服务器发送的释放连接报文段,回复确认报文段:
ACK=1,seq=u+1,ack=w+1
作为响应,表示自己收到了服务器传来的报文段,然后进入TIME-WAIT状态,服务器收到确认报文段后就彻底关闭连接。而客户端在等待状态下,会在等待计时器设置的2MSL(两个最长报文段寿命)后,如果没有收到新的报文,连接就彻底关闭。
为什么需要四次挥手?
因为TCP是全双工的,所以客户端和服务器端都需要发送FIN报文和ACK报文,一来一去总共需要4次挥手。
为什么客户端需要等待2MSL?
如果客户端最后回复的确认报文段(第四次挥手)丢失的话,服务器会认为客户端没有收到自己的报文,于是会重传释放连接报文段(第三次挥手),客户端收到会再次发送确认报文段,重置计时器。2MSL则是为了保证在两个最长报文段的寿命之内,能够收到重传的释放链接报文段。
TCP是通过滑动窗口机制来实现动态流量控制的。
什么是滑动窗口?
在上面TCP报头中的窗口字段,就是在这里使用的。在通信时,接收方会根据自己接受缓存的大小,来调整发送方的发送缓存的大小。
接收方的将自己接收窗口rwnd的大小,存入窗口字段中,发送给发送方,发送方收到报文后,根据接受窗口rwnd和拥塞窗口cwnd的最小值来设置发送窗口的大小
窗口滑动模拟
假设发送报文段,序号39之前的字节都是已经发送并收到确认的,现在等待新的确认。
接收方发送报文过来,ACK=1,ack=41,rwnd=14。
说明收到了41之前的所有报文,窗口向右滑动,窗口大小改为14,但是原窗口已经发送到55了,所以现在是在等待新的确认。等收到新的ack后,窗口会继续向右滑动,发送新的数据。
如果超时还没有收到确认报文,就会进行重传。
TCP在发送数据包后,会启动重传计时器。如果在计时器时间内,收到ACK,计时器就失效。否则到了计时器时间,就会进行数据重传。