一 传输层概述
二 传输层和应用层之间的关系
端口复用。
网络层用协议号NSAP去区分上一层,传输层用端口号区分上一层
2.1 端口
-
登记端口:开发程序的人注册端口,不让其他人用
端口号:0-65535
http—TCP+80端口
https–TCP+443端口
ftp–TCP+21端口
SMTP–TCP+25端口
POP3–TCP+110端口
RDP–TCP+3389端口
SQL–TCP+1433端口
DNS–UDP+53端口 OR TCP+53端口
共享文件夹–TCP+445端口
应用层协议和服务之间的关系:对外服务运行之后在TCP或UDP的某个端口监听客户端的请求。用端口来区分服务,用ip地址来区分计算机。让服务器只开提供服务的那些端口别的端口不开可以增强服务器的安全,其他主机就不能用其他端口来偷用他的服务器(TCP/IP筛选)
2.2 windows防火墙的作用
拦截外来的请求但是自己可以访问外部的网络,别人进不来我可以出去。但防火墙拦不了木马程序
三 UDP
3.1 UDP与TCP
UDP:一个数据包就能完成数据通信,不建立会话,不可靠传输;用于多播
QQ聊天:不建立会话,不需要双方“喂”一下,不可靠传输(通知用户发送失败)–>UDP
QQ传文件:TCP
访问网站:不可能一个数据包传过来–>TCP
3.2 UDP协议的报文格式
UDP报文对应用层报文既不合并和不拆分,直接用
首部放端口信息、长度信息和差错检测信息,8个字节。
伪首部(IP数据报首部的部分信息)在计算校验和时会加上,传输时会去掉
UDP的特点
四 TCP
4.1 TCP概述
TCP:分段、编号、建立会话 netstat -n
从这三个角度学习:可靠传输、流量控制(接收方让发送方慢一点)、拥塞控制
面向连接:在传输之前要建立连接
点到点:不多播不广播
全双工通信:A到B且B到A,A说话B要给A反馈,证明B在收。平时打电话老师给你安排任务你会说“嗯嗯”
4.2 TCP报文格式
与UDP一样需要端口信息和校验和信息;除此之外还需要分组所需的序号、以及可靠传输使用的确认、窗口
-
序号:是该分组的第一个字节在整个文件中是第几个字节(TCP面向字节流)
-
确认号:接受方通知发送方此次发送我已收到,下一次发送方应该给我发文件中以第几个字节开始的分组(用发送方发的序号和分组长度可以计算出来)
-
数据偏移:TCP报文段从第多少个数据开始就是数据了(因为首部除了固定的20个字节外还有可选部分)
-
可选部分(选项+填充)最多40个字节,这是因为数据偏移可以表示60个字节
-
标志位URG:1在发送方优先发送
-
标志位ACK:0什么也没收到但是我要发信息(客户端建立会话时)/1确认收到
-
标志位SYN:同步:1建立会话
-
标志位PSH:1在接收端先读,直接给应用层
-
标志位RST:1断开连接
-
标志位FIN:1释放连接
-
校验和:与UDP一样
-
紧急指针:指明哪段数据是紧急处理的,在URG=1时才有效
-
窗口:该主机的接收窗口大小。因为要根据接收窗口确定发送窗口,保证发送的数据小于接收窗口
4.3 TCP如何实现可靠传输
可靠传输–发啥收啥,不丢包
4.3.1 停止等待机制
a) 过去的路上出了问题:
等B回我跟我确认了我才继续发下一个包,长时间收不到确认就重传,只要不确认我就一直重传
b) 回去的路上出了问题:
核心是:
对于发送方只要你没告诉我收到我就认为你没收到一直超时重传,一旦收到了你的确认我就发下一个,之后再收到你相同的确认我就无视他;
对于接受方只要我收到了我就确认并收下,哪怕收到了相同的我也再次确认,但是我会丢弃相同的。
4.3.2 滑动窗口
发送条件:只要在窗口内的数据包就能发;
窗口滑动条件:从窗口左边起连续N个数据报都收到了ACK,则窗口可以移动N个包的位移
(如果只收到窗口左边第一个包的ACK,那窗口往右移一格;
如果收到窗口左起三个数据包,则窗口往右移三格;
如收到窗口左起第一个和第三个ACK,此时从窗口左起连续1个数据包收到ACK,第二个ACK没收到,收到第三个ACK也没用,窗口右移一格;
如收到窗口左起第二个第三个第四个ACK但是没收到第一个ACK,那是从窗口左起连续0个数据报的ACK,第一个ACK收不到就是不能后面的收到了也没用,还是不能右移)
1 累计确认
B说我收到了第三个证明第一二三个我都收到了。如果一二四都收到了但三丢了,此时B会回复前两个我都收到了。这种方法一次ACK说明了这个ACK之前的数据包我也收到了。
2 举例
a 无丢失情况
-
接收窗口决定发送窗口大小
-
只要在发送窗口内的都可以在未接到B确认前就发送
- B进行累计确认(从第一个到连续的第N个,不连续的不算)
- A的发送窗口右移,并将已收到确认的数据删除
- 接收窗口右移,并将已累计确认的数据交付上层应用
b 有丢失的情况:
4.3.3 超时重传时间选择
网络堵塞情况是变化的,所以要加权平均
4.4 TCP如何实现流量控制
解决通讯两端处理速度不一致的问题
在接收端处理接收数据时就将接收窗口的大小减小,由于接收窗口决定发送窗口,因此发送窗口也减小了,于是实现流量控制。
4.5 TCP如何实现拥塞控制
4.5.1 网络拥塞的概念
拥塞是网络太拥堵了,跟网络的所有主机都有关。而流量控制只跟接收两个主机有关。
4.5.2 慢开始拥塞避免算法概述
只有发送端检测网络拥塞状态
拥塞窗口大小变化:指数增长–>加法增大–>慢开始门限乘法减小+重新慢开始
刚开始不知道网络堵不堵,所以刚开始窗口小一点,如果网络情况好,就不断指数增长
4.5.3 慢开始快重传快恢复拥塞避免算法概述
接收方和发送方配合检测拥塞状态
五 TCP连接
5.1 三次握手
建立连接(前两次握手):同步,SYN=1
第三次握手开始SYN=0
SYN–建立连接
ACK–确认收到
seq–我发的是文件中的第几个字节
ack–我下次要收到第几个字节
第三次握手的作用是让B知道这个连接已经建立。如果没有第三次握手可能A发了两次连接,一个先到B,B建立了连接,一个后到B,B也建立了连接。都等待A发数据。但是A看到之后的确认后就无视了,因为之前已经建立了一个连接了,这就造成第二个连接中B一直在等待A发数据但其实没有数据,但B也不知道这个连接A不用。加上第三次握手B就有了一个确认机制,如果超时未收到A的确认那这个连接就释放了。最常见的情况就是我用一个假地址访问服务器,服务器向一个不存在的主机第二次握手,没有第三次握手服务器就一直要等待。
5.2 四次挥手
FIN–我没有要说的了,我都发完了,申请主动关闭连接(我还有话没话)
TIME-WAIT是防止最后一次挥手在网络中丢失
1. 三次握手
三次握手(Three-way Handshake)其实就是指建立一个TCP连接时,需要客户端和服务器总共发送3个包。进行三次握手的主要作用就是为了确认双方的接收能力和发送能力是否正常、指定自己的初始化序列号为后面的可靠性传送做准备。实质上其实就是连接服务器指定端口,建立TCP连接,并同步连接双方的序列号和确认号,交换TCP窗口大小信息。
刚开始客户端处于 Closed 的状态,服务端处于 Listen 状态。
进行三次握手:
第一次握手:客户端给服务端发一个 SYN 报文,并指明客户端的初始化序列号 ISN©。此时客户端处于 SYN_SEND 状态。
首部的同步位SYN=1,初始序号seq=x,SYN=1的报文段不能携带数据,但要消耗掉一个序号。
第二次握手:服务器收到客户端的 SYN 报文之后,会以自己的 SYN 报文作为应答,并且也是指定了自己的初始化序列号 ISN(s)。同时会把客户端的 ISN + 1 作为ACK 的值,表示自己已经收到了客户端的 SYN,此时服务器处于 SYN_REVD 的状态。
在确认报文段中SYN=1,ACK=1,确认号ack=x+1,初始序号seq=y。
第三次握手:客户端收到 SYN 报文之后,会发送一个 ACK 报文,当然,也是一样把服务器的 ISN + 1 作为 ACK 的值,表示已经收到了服务端的 SYN 报文,此时客户端处于 ESTABLISHED 状态。服务器收到 ACK 报文之后,也处于 ESTABLISHED 状态,此时,双方已建立起了连接。
确认报文段ACK=1,确认号ack=y+1,序号seq=x+1(初始为seq=x,第二个报文段所以要+1),ACK报文段可以携带数据,不携带数据则不消耗序号。
发送第一个SYN的一端将执行主动打开(active open),接收这个SYN并发回下一个SYN的另一端执行被动打开(passive open)。
在socket编程中,客户端执行connect()时,将触发三次握手。
1.1 为什么需要三次握手,两次不行吗?
弄清这个问题,我们需要先弄明白三次握手的目的是什么,能不能只用两次握手来达到同样的目的。
第一次握手:客户端发送网络包,服务端收到了。
这样服务端就能得出结论:客户端的发送能力、服务端的接收能力是正常的。
第二次握手:服务端发包,客户端收到了。
这样客户端就能得出结论:服务端的接收、发送能力,客户端的接收、发送能力是正常的。不过此时服务器并不能确认客户端的接收能力是否正常。
第三次握手:客户端发包,服务端收到了。
这样服务端就能得出结论:客户端的接收、发送能力正常,服务器自己的发送、接收能力也正常。
因此,需要三次握手才能确认双方的接收与发送能力是否正常。
试想如果是用两次握手,则会出现下面这种情况:
如客户端发出连接请求,但因连接请求报文丢失而未收到确认,于是客户端再重传一次连接请求。后来收到了确认,建立了连接。数据传输完毕后,就释放了连接,客户端共发出了两个连接请求报文段,其中第一个丢失,第二个到达了服务端,但是第一个丢失的报文段只是在某些网络结点长时间滞留了,延误到连接释放以后的某个时间才到达服务端,此时服务端误认为客户端又发出一次新的连接请求,于是就向客户端发出确认报文段,同意建立连接,不采用三次握手,只要服务端发出确认,就建立新的连接了,此时客户端忽略服务端发来的确认,也不发送数据,则服务端一致等待客户端发送数据,浪费资源。
1.2 什么是半连接队列?
服务器第一次收到客户端的 SYN 之后,就会处于 SYN_RCVD 状态,此时双方还没有完全建立其连接,服务器会把此种状态下请求连接放在一个队列里,我们把这种队列称之为半连接队列。
当然还有一个全连接队列,就是已经完成三次握手,建立起连接的就会放在全连接队列中。如果队列满了就有可能会出现丢包现象。
这里在补充一点关于SYN-ACK 重传次数的问题:
服务器发送完SYN-ACK包,如果未收到客户确认包,服务器进行首次重传,等待一段时间仍未收到客户确认包,进行第二次重传。如果重传次数超过系统规定的最大重传次数,系统将该连接信息从半连接队列中删除。
注意,每次重传等待的时间不一定相同,一般会是指数增长,例如间隔时间为 1s,2s,4s,8s…
1.3 ISN(Initial Sequence Number)是固定的吗?
当一端为建立连接而发送它的SYN时,它为连接选择一个初始序号。ISN随时间而变化,因此每个连接都将具有不同的ISN。ISN可以看作是一个32比特的计数器,每4ms加1 。这样选择序号的目的在于防止在网络中被延迟的分组在以后又被传送,而导致某个连接的一方对它做错误的解释。
三次握手的其中一个重要功能是客户端和服务端交换 ISN(Initial Sequence Number),以便让对方知道接下来接收数据的时候如何按序列号组装数据。如果 ISN 是固定的,攻击者很容易猜出后续的确认号,因此 ISN 是动态生成的。
1.4 三次握手过程中可以携带数据吗?
其实第三次握手的时候,是可以携带数据的。但是,第一次、第二次握手不可以携带数据
为什么这样呢?大家可以想一个问题,假如第一次握手可以携带数据的话,如果有人要恶意攻击服务器,那他每次都在第一次握手中的 SYN 报文中放入大量的数据。因为攻击者根本就不理服务器的接收、发送能力是否正常,然后疯狂着重复发 SYN 报文的话,这会让服务器花费很多时间、内存空间来接收这些报文。
也就是说,第一次握手不可以放数据,其中一个简单的原因就是会让服务器更加容易受到攻击了。而对于第三次的话,此时客户端已经处于 ESTABLISHED 状态。对于客户端来说,他已经建立起连接了,并且也已经知道服务器的接收、发送能力是正常的了,所以能携带数据也没啥毛病。
1.5 SYN攻击是什么?
服务器端的资源分配是在二次握手时分配的,而客户端的资源是在完成三次握手时分配的,所以服务器容易受到SYN洪泛攻击。SYN攻击就是Client在短时间内伪造大量不存在的IP地址,并向Server不断地发送SYN包,Server则回复确认包,并等待Client确认,由于源地址不存在,因此Server需要不断重发直至超时,这些伪造的SYN包将长时间占用未连接队列,导致正常的SYN请求因为队列满而被丢弃,从而引起网络拥塞甚至系统瘫痪。SYN 攻击是一种典型的 DoS/DDoS 攻击。
检测 SYN 攻击非常的方便,当你在服务器上看到大量的半连接状态时,特别是源IP地址是随机的,基本上可以断定这是一次SYN攻击。在 Linux/Unix 上可以使用系统自带的 netstats 命令来检测 SYN 攻击。
netstat -n -p TCP | grep SYN_RECV
常见的防御 SYN 攻击的方法有如下几种:
- 缩短超时(SYN Timeout)时间
- 增加最大半连接数
- 过滤网关防护
- SYN cookies技术
2. 四次挥手
建立一个连接需要三次握手,而终止一个连接要经过四次挥手(也有将四次挥手叫做四次握手的)。这由TCP的半关闭(half-close)造成的。所谓的半关闭,其实就是TCP提供了连接的一端在结束它的发送后还能接收来自另一端数据的能力。
TCP 的连接的拆除需要发送四个包,因此称为四次挥手(Four-way handshake),客户端或服务器均可主动发起挥手动作。
刚开始双方都处于 ESTABLISHED 状态,假如是客户端先发起关闭请求。四次挥手的过程如下:
第一次挥手:客户端发送一个 FIN 报文,报文中会指定一个序列号。此时客户端处于 FIN_WAIT1 状态。
即发出连接释放报文段(FIN=1,序号seq=u),并停止再发送数据,主动关闭TCP连接,进入FIN_WAIT1(终止等待1)状态,等待服务端的确认。
第二次挥手:服务端收到 FIN 之后,会发送 ACK 报文,且把客户端的序列号值 +1 作为 ACK 报文的序列号值,表明已经收到客户端的报文了,此时服务端处于 CLOSE_WAIT 状态。
即服务端收到连接释放报文段后即发出确认报文段(ACK=1,确认号ack=u+1,序号seq=v),服务端进入CLOSE_WAIT(关闭等待)状态,此时的TCP处于半关闭状态,客户端到服务端的连接释放。客户端收到服务端的确认后,进入FIN_WAIT2(终止等待2)状态,等待服务端发出的连接释放报文段。
第三次挥手:如果服务端也想断开连接了,和客户端的第一次挥手一样,发给 FIN 报文,且指定一个序列号。此时服务端处于 LAST_ACK 的状态。
即服务端没有要向客户端发出的数据,服务端发出连接释放报文段(FIN=1,ACK=1,序号seq=w,确认号ack=u+1),服务端进入LAST_ACK(最后确认)状态,等待客户端的确认。
第四次挥手:客户端收到 FIN 之后,一样发送一个 ACK 报文作为应答,且把服务端的序列号值 +1 作为自己 ACK 报文的序列号值,此时客户端处于 TIME_WAIT 状态。需要过一阵子以确保服务端收到自己的 ACK 报文之后才会进入 CLOSED 状态,服务端收到 ACK 报文之后,就处于关闭连接了,处于 CLOSED 状态。
即客户端收到服务端的连接释放报文段后,对此发出确认报文段(ACK=1,seq=u+1,ack=w+1),客户端进入TIME_WAIT(时间等待)状态。此时TCP未释放掉,需要经过时间等待计时器设置的时间2MSL后,客户端才进入CLOSED状态。
收到一个FIN只意味着在这一方向上没有数据流动。客户端执行主动关闭并进入TIME_WAIT是正常的,服务端通常执行被动关闭,不会进入TIME_WAIT状态。
在socket编程中,任何一方执行close()操作即可产生挥手操作。
2.1 挥手为什么需要四次?
因为当服务端收到客户端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当服务端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉客户端,“你发的FIN报文我收到了”。只有等到我服务端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四次挥手。
2.2 2MSL等待状态
TIME_WAIT状态也成为2MSL等待状态。每个具体TCP实现必须选择一个报文段最大生存时间MSL(Maximum Segment Lifetime),它是任何报文段被丢弃前在网络内的最长时间。这个时间是有限的,因为TCP报文段以IP数据报在网络内传输,而IP数据报则有限制其生存时间的TTL字段。
对一个具体实现所给定的MSL值,处理的原则是:当TCP执行一个主动关闭,并发回最后一个ACK,该连接必须在TIME_WAIT状态停留的时间为2倍的MSL。这样可让TCP再次发送最后的ACK以防这个ACK丢失(另一端超时并重发最后的FIN)。
这种2MSL等待的另一个结果是这个TCP连接在2MSL等待期间,定义这个连接的插口(客户的IP地址和端口号,服务器的IP地址和端口号)不能再被使用。这个连接只能在2MSL结束后才能再被使用。
2.3 四次挥手释放连接时,等待2MSL的意义?
MSL是Maximum Segment Lifetime的英文缩写,可译为“最长报文段寿命”,它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。
为了保证客户端发送的最后一个ACK报文段能够到达服务器。因为这个ACK有可能丢失,从而导致处在LAST-ACK状态的服务器收不到对FIN-ACK的确认报文。服务器会超时重传这个FIN-ACK,接着客户端再重传一次确认,重新启动时间等待计时器。最后客户端和服务器都能正常的关闭。假设客户端不等待2MSL,而是在发送完ACK之后直接释放关闭,一但这个ACK丢失的话,服务器就无法正常的进入关闭连接状态。
两个理由:
- 保证客户端发送的最后一个ACK报文段能够到达服务端。
这个ACK报文段有可能丢失,使得处于LAST-ACK状态的B收不到对已发送的FIN+ACK报文段的确认,服务端超时重传FIN+ACK报文段,而客户端能在2MSL时间内收到这个重传的FIN+ACK报文段,接着客户端重传一次确认,重新启动2MSL计时器,最后客户端和服务端都进入到CLOSED状态,若客户端在TIME-WAIT状态不等待一段时间,而是发送完ACK报文段后立即释放连接,则无法收到服务端重传的FIN+ACK报文段,所以不会再发送一次确认报文段,则服务端无法正常进入到CLOSED状态。
- 防止“已失效的连接请求报文段”出现在本连接中。
客户端在发送完最后一个ACK报文段后,再经过2MSL,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失,使下一个新的连接中不会出现这种旧的连接请求报文段。
2.4 为什么TIME_WAIT状态需要经过2MSL才能返回到CLOSE状态?
理论上,四个报文都发送完毕,就可以直接进入CLOSE状态了,但是可能网络是不可靠的,有可能最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。
3. 总结
《TCP/IP详解 卷1:协议》有一张TCP状态变迁图,很具有代表性,有助于大家理解三次握手和四次挥手的状态变化。如下图所示,粗的实线箭头表示正常的客户端状态变迁,粗的虚线箭头表示正常的服务器状态变迁。