计算机网络之传输层
# 引言
IP+MAC+port 标识一个全世界范围内独一无二的基于网络通信的软件..
参考链接:https://mp.weixin.qq.com/s/Uf42QEL6WUSHOwJ403FwOA
[传输层的由来]
网络层的IP地址帮我们区分子网,以太网层的MAC帮我们找到主机..
我们通过IP和MAC找到了一台特定的主机,那如何标识这台主机上特定的应用程序? 答案就是端口..
端口即应用程序与网卡关联的编号...
[传输层功能]
建立 端 口到 端 口的通信
端口范围0-65535, 0-1023为系统占用端口,端口占用的是操作系统的资源
端口打个比方就是操作系统开的一个个的小窗口,应用软件数据通过这些小窗口再到网卡.
[传输层的数据]
1> 依照tcp&udp协议在传输层的构建的数据,称为'数据段'
2> 数据段 = head + data
head 里放 <源端口> 和 <目标端口>
端口在本机唯一即可!
# UDP 协议
UDP协议 -- 无连接、不可靠的用户报文协议..
在发送数据时,不需要建立连接,拿到IP和端口直接发过去了..发完后数据就删掉了.应用场景: 查询类的操作(eg dhcp查询) 集群的时间同步 QQ聊天
Q: 前三层协议只能把数据包从一个主机搬到另外一台主机,但是,到了目的地以后,
数据包具体交给哪个程序(进程)呢?
A: 需要把通信的进程区分开来,于是就给每个进程分配一个数字编号: 端口号
So,现目前,经过前四层(传输层~数据链路层 从上到下),应用数据被封装成了这样:
数据链路层头部 网络层头部 传输层头部 数据部分
源MAC 目的MAC 源IP 目的IP 源端口号 目的端口号 数据部分
这样子,我们将主机到主机的通信,升级成了进程和进程之间的通信..
经过上述的过程,我们在不知不觉中就已经实现了UDP 协议!
(当然 UDP 协议中不光有源端口和目标端口,还有数据包长度和校验值,我们暂且略过)
[引出TCP协议]
用UDP协议A同B能实现通信,但这个通信过程中存在很多问题!!!没有那么简单!!
丢包问题、效率问题、顺序问题、流量问题、拥塞问题、连接问题... 这些问题TCP协议才能解决!!!
# TCP协议
TCP协议 -- 面向连接的、可靠的、基于字节流的传输控制协议 netstat -an
应用场景: 与用户打交道的应用程序
# 可靠交互
# ☆ [丢包问题]
由于网络的不可靠,A传给B的数据包可能在半路丢失..
不难想到,对于丢包问题,B应该告诉A数据包没收到,A再重;或者B告诉A一声数据包收到了.
停止等待协议
A每发一个包,都必须收到来自B的确认(回一个ACK包)后,再发下一个;
否则在一定时间内没有收到确认,就重传这个包.
这样以来,TCP协议的通信过程有了一个新特征 -- 可靠交互.
# ☆ [效率问题]
停止等待解决了丢包问题,可靠是可靠,但效率太慢了..
于是乎,我们提出假设,用 流水线 的形式,A连续发几个数据包给B,B再按收到的顺序依次ACK确认..
(前提,A发出去的包按照发送的先后依次到达B,才能保证B依次回应的ACK对应的上..)
看似成立,但实则傻白甜了.网络是复杂的,不可靠的.
A发出去的数据包,可是会走 不同的路径 (耗时不同) 到达B.
这可能会导致出现这种情况,第2个发出去的包会在第5个发出去的包到达B后才到达B..
也就是说,在流水线中有多个数据包和ACK包在乱序流动,他们之间对应关系乱掉啦!!
# ☆ [顺序问题]
A在发送的数据包中增加一个序号(seq), 同时B要在ACK包 上增加一个 确认号(ack).
这样不但解决了停止等待协议的效率问题,也通过标序号的方式解决了顺序问题.
现实中,为了减少网络IO,B会采用 累计确认/累计应答 的方式给A回复确认号(ack).
举个栗子:
A连续发送了带有1,2,3序号(seq)的数据包给B,
B收到了这三个数据包,将回应了一个带有确认号(ack=4)的ACK包给A;
代表A发送的序号小于4的数据包都收到了..也同时告诉A下一个数据包应该发的序号是4.
Ps: ack号是收到的最后一个数据包的序号seq+1. So,纠正下,下图中B回复A的ACK包的确认号应该为4..
# 滑动窗口机制
TCP可靠传输的实现依托于 -- 以字节为单位的滑动窗口
视频链接:https://www.bilibili.com/video/BV194411h71z
!!!!!!
Ps: 图中一个个小框代表一个个字节, 收到的最后一个确认号是3.. 窗口上边界指的是滑动窗口的前沿.
# 三次握手
三次握手 -- 准确点应该是三报文握手,即一次握手过程中交换了三个报文,并不是进行了三次握手!
TCP通信之前必须建立双向通路, 这个连接是虚拟的, 是由 A 和 B 共同维护的, 在网络中的设备根本就不知道连接这回事儿!
TCP协议在 "通信之前" 必须建立双向连接 ,通常都是客户端主动连接服务端, 所以必须先启动服务端.
Ps:UDP协议不需要先启动服务端.
客户端: 主动发起连接建立的应用进程
服务端: 被动等待连接建立的应用进程
Ps: 两条线是时间线! 代表客户端和服务端建好连接需要花费的时间..
# ☆ [说明]
结合前面的TCP协议的组成图.
SYN -- 同步位,表明发送的是连接请求报文段
ACK -- 确认位,表明发送的是连接确认报文段
seq -- 序号. TCP是面向字节流的,在TCP连接中传送的字节流的每一个字节都按顺序编号
ack -- 确认号,是期望收到对方下一个报文段的第一个数据字节的序号
# ☆ [连接步骤]
1> 一开始,B会处于LISTEN(监听)状态,等待某些客户的连接请求.
2> A发送完SYN连接报文段后,进入SYN-SENT(同步已发送)状态.
3> B收到A发送的SYN后,若同意建立连接,会向A发送ACK确认报文段.进入SYN_RCVD(同步收到)状态.
只不过在这个确认报文段中,不仅将ACK置为1,还会将SYN置为1,相当于把向B发送连接请求的活一起干了..
也可以简说发送的是ACK+SYN报文段..
注意: ack = x+1
表示B已经接收到A发送过来的序号为x的报文.
4> A收到B的确认后,还要向B给出确认.A进入ESTAB_LISHED(已经建立连接)状态.
注意: 这里的seq = x+1
,是因为A收到的B的确认报文中 ack = x+1
-- 期望收到的下一个报文的序号是以x+1开始的.
5> B收到A的确认后,B进入ESTAB_LISHED状态. 此刻A与B的双向道路才真正的打通!
注意哦! SYN_RCVD 也叫做 半连接 状态
敲黑板! B向A发送的报文实际上可以拆成两个报文段,可以先发送一个确认报文段(ACK=1 ack=x+1),再发送一个同步报文段(SYN=1,seq=y). 这样四报文握手的效果跟三报文握手的效果是一样的!
Q:讲道理,A向B发送连接请求后,B同意了,那么AB之间的通路就建立了.那为什么A最后还要向B发送一次确认?
A: 假设A不发送最后那次请求.这是前提.A向B发送的连接请求因为网络原因未能及时到达B.
于是乎,A重新发送了一次连接请求,而后A收到了B的确认,连接建立.
AB之间数据传输完毕后,连接释放..好巧不巧,A向B发送的第一次连接请求并没有丢失,在连接释放后到达了B.
B就会认为A发送了一次新的连接请求.并一直等待A发来数据..B的许多资源就这样浪费掉了..
A最后向B发送一次确认就能有效的避免这种情况!! client不会向server的确认发出确认. server由于收不到确认,就知道client并没有要求建立连接!!
# ☆ [半连接池]
半连接池占用服务器固定内存的大小
多个客户端同时向一个服务端发送SYN请求连接,这些连接到了服务端会先依次放到半连接池里面..
在不断放的同时, 服务器的操作系统会根据队列先进先出的原则 不断的取 半连接池里的请求..
在现实当中,打开某一个网页,若原地转圈,表示请求已经在半连接池里了,要等一小会;
若直接拒绝了请求,访问不上,则证明池子已经满了,请求在池子外面..
所以在高并发的场景下,我们会进行一个优化: 将半连接池增大.
调多大没个标准,因为半连接池占用的内存空间变大了,相应的其他进程存取数据可用的空间就少了..
敲黑板! 最本质的优化, 加内存条 是软件优化的N倍.
# ☆ [syn洪水攻击]
服务器处于LISTEN和ESTABLISHED状态都是正常的,前者表示正在监听,后者表示服务器正在服务多个客户端.
若发现服务器有大量客户端发来的请求连接处于SYN_RCVD状态,就不正常啦...表明操作系统忙不过来了.
why?因为连接建立是很快很快... 客户端的SYN_SENT状态和服务器的SYN_RCVD状态是很难捕捉的到.
syn洪水攻击就会造成服务器大量SYN_RCVD状态出现..
它会模拟大量的假的客户端朝服务端发SYN连接请求,发完后客户端就消失了; 服务端收到后, TCP是好人协议嘛 ,不会验证客户端的身份,会直接回复确认信息,但客户端已经消失了,是不会回复最后的确认的,但服务端不知道啊,服务端会每隔一段时间重复回一次,尝试几次后发现客户端还是没有回复最后的确认信息,那么服务端就会认为这是个客户端出现了什么异常...连接就取消啦. 但在这期间,要占用服务端的资源嘛...
也许你会说,TCP为啥不能验证身份后再开始建立连接呢?因为TCP是一个底层的协议,设计复杂一点,建立连接的速度就会降下来,整个互联网通信效率都会被拉下来.. 所以安全性的问题往往会在应用层,应用程序里解决..
再唠唠嗑,服务器通常存放在机房里,服务器外面是交换机,交换机外面是路由,再往外是各种网络设备..
syn洪水攻击往往是外网攻击,攻击的流量若很大很大,还没到服务器就把外面的交换机给干趴下了..那么不管增大服务器的半连接池到多大,服务器的性能有多牛也没有用..
用正规的机房,使用阿里云腾讯云的服务器,它们会帮忙解决这个问题..
# 四次挥手
四报文握手.
C与S谁先发完数据,谁就先主动发起断开连接.这里假设客户端A先发完数据
一台机器既是某个软件的客户端也运行着某个软件的服务端.所以在一台机器上可以同时看到C与S的状态.
1> A会向服务端B发送FIN连接释放报文段.A进入FIN_WAIT_1(终止等待1)状态,等待B的确认.
2> B收到A发送来的FIN=1报文后,立即发送ACK确认报文.B进入CLOSE_WAIT(关闭等待)状态.
3> A收到来自B的确认后,A进入FIN_WAIT_2(终止等待2)状态.
4> 等待一会,服务端B也发完数据后,B会向A发送FIN连接释放报文段.B进入LAST_ACK(最后确认)状态.
5> A收到B发送来的FIN=1报文后,A进入TIME_WAIT(时间等待)状态.
6> A向B发送ACK=1的确认报文后,必须处于TIME_WAIT等待2MSL的时间后,A才会进入CLOSE状态.
具体来说是为了避免两种情况.
第一种,B长时间没有收到最后一次确认,B会再次向A发送FIN报文,A会重传最后一次的ACK报文
为了B能准确无误的收到最后一次的ACK确认报文.
第二种,防止"已经失效的连接请求报文段"出现在本连接中.A在发送完最后一个ACK报文段后,
再经过2MSL的时间,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失.
这样就可以使下一个新的连接中不会出现这种旧的连接请求报文段..
7> B只要收到来自A的确认后,服务端B直接关掉了这个连接..B进入CLOSE状态..
可以发现,B进入CLOSE状态会比A早一些!!
Ps: MSL通常为2min;
敲黑板! 针对断连接,中间的两次为啥不能合二为一? S要等数据全部发给C后,才会发送FIN报文段.
qTCP协议的重点:
[可靠性]
不是说建立了双向通路就可靠,而是A发送的数据是拷贝一份沿着网络发送给B,待B接收到后,会回个ACK给A,意味着B告诉A收到数据啦,A才会将这份数据在内存中删除掉.. 若等一段时候,A没有得到ACK确定,会重传.. 尝试几次后都没得到,A就会认为B坏了..不会再发了. TCP这种保证数据可靠性的方式,对传输速率没有影响,但对资源是浪费了..因为A在等待B回复ACK的期间,数据一直在内存里放着..牺牲空间来换取可靠性.
[三连四挥 各种状态]
建立连接是三次,断连接是四次,因为前者没有数据在传,后者有数据在传!!
建连接通常是s先启动后,C对S发起连接;断连接就没准了,C与S谁先发完谁先断.
为啥需要三次握手建立连接,四次挥手断开连接.
https://blog.csdn.net/LO_YUN/article/details/115541563
2
3
4
5
6
7
8
9
10
11
12