Java基础系列-网络编程核心解析(一)
Java基础系列-网络编程核心解析
网络编程发展史
- http/0.9 1991年 只有get命令,服务端直接返回html格式字符,响应完毕既断开。
- http/1.0 1996 可以发送任何格式内容,包括文字图像视频二进制,也丰富了Get Post Head请求和响应格式加入头信息,每个TCP只能发送一个请求,新建TCP链接的成本很高,导致效率很差。
- http/1.1 1997 引入持久连接,TCP默认不关闭,可复用。
- http/2.0 2015 采用二进制而非文本格式,解析更高效,数据更紧凑,错误更少,服务器可以将响应主动推送到客户端。
- 自2017年起http3协议已发布了29个Draft,推出在即,Chrome、Nginx等软件都在跟进实现最新的草案。
Http协议
超文本传输协议(Hyper Text Transfer Protocol,HTTP)是一个简单的请求-响应协议,它通常运行在TCP之上,是应用层协议。
请求报文
请求报文由请求行,请求头,和请求数据组成。
- 请求行:抓包第一行,包括请求方法,url和http版本
- 请求报头:header的内容
- 请求数据:指post方式提交的表单数据
响应报文
响应报文由状态行,响应头,响应正文组成。
- 状态行:状态码
- 响应报头:同请求报头
- 响应正文:服务器返回的资源数据
网络分层
请求发起和断开
三次握手
第一次握手:客户端将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给服务端,客户端进入SYN_SENT状态,等待服务端确认。
第二次握手:服务端收到数据包后由标志位SYN=1知道客户端请求建立连接,服务器端将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给客户端以确认连接请求,服务端进入SYN_RCVD状态。
第三次握手:客户端收到确认后,检查ack是否为J+1,ACK是否为1,如果正确,则将ACK置为1,ack=K+1,并将该数据包发送给服务器,服务器检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,客户端和服务端进入ESTABLISHED状态,完成三次握手,随后可以开始传输数据了。
为什么握手需要3次?
为了实现可靠数据传输,TCP协议的通信双方,都必须维护一个序列号,以标识发送出去的数据包中,哪些是已经被对方收到的。
例
:发送方在发送数据包(假设是10byte),同时送上一个序号(假设为500),那么接收方收到这个数据包以后,就可以回复一个确认号(510 = 500 + 10)告诉发送方“我已经收到了你的数据包,你可以发送下一个数据包,序号从511开始”
三次握手的过程就是通信双方相互告知序列号起始值,并确认对方已经收到序列号起始值的必经步骤,如果只有两次握手,那么最多只有连接发起方的起始序列号能被确认。
通过wireshark观察三次握手
第一次握手:
第二次握手:
第三次握手:
3次握手漏洞-洪泛攻击:三次握手中的第二次握手,服务端向客户端应答请求,应答请求是需要客户端ip的,攻击者伪造这个ip,往服务端疯狂发送第一次握手的内容,因为第一次握手的客户端ip地址是伪造的,从而服务端忙于进行第二次握手,但是第二次握手没有响应,服务端被拖累至死机。
解决方案: 1.无效连接监视释放。监视所有连接,达到一定阈值后,拆除连接释放资源。这种方式不太推荐。 2.延缓TCB分配。第一次握手后,一般服务器会为该请求分配TCB(连接控制资源),这个资源通常会占用200多个字节,延迟TCB分配,当连接建立起来之后再分配TCB就可以有效减低服务器资源的消耗 3.使用防火墙。防火墙确认了连接的有效性后,再向内部服务器发起SYN请求。(推荐)
四次挥手
四次挥手终止TCP连接,当断开一个连接时,需要客户端和服务端总共发送四个包以确认连接的断开。在socket编程中,这一过程由客户端或服务端任意端来执行close触发。
由于TCP是全双工的,因此每个方向都需要单独进行关闭,这一原则是当甲方完成数据发送任务后,发送一个FIN给乙方来终止连接,乙方收到FIN只是意味着不会再收到甲方的数据了,但是乙方依然可以给甲方发送数据,直到乙方也发送了FIN给甲方,首先进行关闭的一方执行主动关闭,而另一方执行被动关闭。
由此可见,TCP建立连接需要三个分节,断开连接却需要四个分节
- 某个应用调用close(主动关闭),该端的TCP发送一个FIN分节,表示数据发送完毕,应用进入FIN-WAIT-1状态。
- 接收到这个FIN的对端执行被动关闭,发送确认报文,这个FIN由TCP确认。因为收到FIN意为着接受端应用进程在相应连接上再无数据可接收,接收端进入了CLOSE_WAIT状态,这时候处于半关闭状态,即此时主动关闭端已经没有数据需要发送了,但是被动关闭端要发送数据,主动关闭端依然可以接收,这个状态还要维持一段时间,也就是整个CLOSE_WAIT状态需要维持的时间。主动关闭端收到确认报文后进入FIN_WAIT_2状态。
- 过一段时间后,被动关闭的应用端通过调用close关闭他的套接字,这导致他的TCP也发送一个FIN。
- 接收这个FIN的原发端TCP确认这个FIN发出一个确认ACK报文,并进入TIME_WAIT状态,注意此时TCP还没有释放,必须经过2*MSL的时间后,当主动关闭端撤销相应的TCB(传输控制块)后,才进入CLOSED状态。
- 被动关闭端只要收到了主动关闭端发出的确认,立即进入CLOSED状态,同样撤销TCB后,就结束了这次TCP连接。可以看到被动关闭端结束TCP的时间要比主动关闭端早一些。
为什么挥手需要四次?
TCP是全双工模式,这就意味着,主机一发出一个FIN报文,只是意味着主机一已经没有数据要发送了,主机一告诉主机二,我数据发送完了。但是这个时候主机一还是可以接收来自主机二的数据,当主机二返回ACK报文段时,表示他已经知道主机一没有数据要发送了,但是主机二还是可以发送数据到主机一的。当主机二也发送了FIN报文段时,这个时候也表示主机二也没有数据要发送了,就会告诉主机一,我也没有数据要发送了,之后彼此就会愉快的中断这次TCP连接。这就是为什么全双工模式为了彻底中断,需要双方通信四次的原因。
为什么需要TIME_WAIT状态
第一,为了可靠的终止TCP连接,如果最后一个ACK报文因为网络原因被丢弃,此时server因为没有收到ACK超时报文而重发FIN报文,处于TIME_WAIT状态的client可以继续对FIN报文做回复,向server发送ACK报文。第二,让迟来的TCP报文段有足够的时间被识别和丢弃,连接结束了,网络中的延迟报文也应该被丢弃,以免影立刻建立的新连接。
TCP连接的基本特性
面向连接
TCP是面向连接的运输层协议
。应用程序在使用 TCP 协议之前,必须先建立 TCP 连接。在传送数据完毕后,必须释放已经建立的 TCP 连接。
可靠性
TCP提供可靠交付
的服务。通过 TCP 连接传送的数据,无差错、不丢失、不重复,并且按序到达。确认应答是TCP实现可靠传输的最核心机制,可靠传输不是说把消息100%发送给对方(由于网络物理等因素干扰,不可能实现),而是,当我们成功把数据发送给对方,我们知道发送成功了,当我们没有把数据发送给对方,我们能知道发送失败了。
RTT(Round-trip time)往返时延
RTO(Retransmission time out)重传超时
数据排序
TCP 发送数据时 , 会将数据拆分成不同的片段 , 并对这些片段进行排序 ; 顺序发送 : 将排序好的数据片段顺序发送 ; 顺序组装 : 在接收端按照顺序将数据片段组装成原数据 ;
全双工
TCP 允许通信双方的应用进程在任何时候都能发送数据。TCP 连接的两端都设有发送缓存和接受缓存,用 来临时存放双向通信的数据。
滑动窗口
在TCP头中有一个Window字段,这个字段代表了接收端告诉发送端自己缓冲区还有多少剩余空间可以接收数据。TCP 利用滑动窗口实现流量控制的机制, 而滑动窗口大小就是通过TCP头部的Window字段来通知发送方。
接收端会在确认应答发送ACK报文时,将自己的即时窗口大小填入,并跟随ACK报文一起发送过去。而发送方根据ACK报文里的窗口大小的值进而改变自己的发送速度。
假设我们窗口大小为20(32-51),发送方发送32-51序列包后,接收到ACK=36,表示 接收方只接收到了32-35,下一次接收方期望接收到的是36序列号的包。如果接收方给到的ACK中窗口大小仍为20,发送方窗口滑动,36-55则是发送方可以发送的包。
这就是滑动窗口的工作机制,发送方在发送过程中始终保持着一个发送窗口,只有落在发送窗口内的帧才允许被发送;发送方收到ACK 计算获得接收方窗口大小之后,便会调整自己的发送速率,也就是调整自己发送窗口的大小实现流量控制。但是当发送方收到接收窗口的大小为0时,发送方就会停止发送数据!
零窗口
当发送方停止发送数据后,该怎样才能知道自己可以继续发送数据?
我们可以采用这样的策略:当接收方处理好数据,接受窗口 win > 0 时,接收方发个通知报文去通知发送方,告诉他可以继续发送数据了。当发送方收到窗口大于0的报文时,就继续发送数据。
不过这时候可能会遇到一个问题,如果发送端在重发超时的时间内都没有收到窗口更新的通知或者窗口更新的包丢失了,这时候就会引发一个问题:接收方发了通知报文后,继续等待发送方发送数据,而发送方则在等待接收方的通知报文,此时双方会陷入一种僵局。
为了解决这种问题,TCP为每一个连接设有一个持续计时器 :当发送方收到接受窗口 win = 0 时,这时发送方停止发送报文,并且同时开启一个定时器,每隔一段时间就发个测试报文去询问接收方,打听是否可以继续发送数据了,如果可以,接收方就告诉他此时接受窗口的大小;如果接受窗口大小还是为0,则发送方再次刷新启动定时器。
流量控制
双方在通信的时候,发送方的速率与接收方的速率是不一定相等,如果发送方的发送速率太快,会导致接收方处理不过来,这时候接收方只能把处理不过来的数据存在缓存区里。
如果缓存区满了发送方还在疯狂着发送数据,接收方只能把收到的数据包丢掉,而流量控制就是控制发送者的发送速度从而使接收者来得及接收,防止丢失数据包的。被丢弃的数据无法收到确认,会触发发送端的重发机制,从而导致网络流量的无端浪费。
拥塞控制
流量控制是作用于接收者的,它是控制发送者的发送速度从而使接收者来得及接收,防止分组丢失的。 拥塞控制是作用于网络的,它是防止过多的数据注入到网络中,避免出现网络负载过大的情况。
拥塞控制就是防止过多的数据注入网络中,这样可以使网络中的路由器或链路不致过载。 拥塞是一个动态问题,我们没有办法用一个静态方案去解决,从这个意义上来说,拥塞是不可避免的。就好像上下班高峰期经常堵车,为了不让交通瘫痪,交警会去现场指挥,采用动态的方式对车辆进行限制,根据实际情况,慢慢放行。
比如主机A给主机B传输数据。
两台主机在传输数据包的时候,如果发送方迟迟没有收到接收方反馈的ACK,那么发送方就会认为它发送的数据包丢失了,进而会重新传输这个丢失的数据包。
然而实际情况有可能此时有太多主机正在使用信道资源,导致网络拥塞了,而A发送的数据包被堵在了半路,迟迟没有到达B。这个时候A误认为是发生了丢包情况,会重新传输这个数据包。
结果就是不仅浪费了信道资源,还会使网络更加拥塞。因此,我们需要进行拥塞控制。TCP的拥塞控制通过:慢启动、拥塞避免、快重传与快恢复完成。
慢启动
慢启动算法为TCP发送方新增的一个拥塞窗口 (cwnd )。对应到流量控制,发送方会根据拥塞窗口和滑动窗口的最小值作为发送上限。
当新建连接时,发送方不了解网络的情况,cwnd(拥塞窗口)初始化比较小的值,RFC建议2-4个MSS,具体视MSS的大小而定。
MSS:Maximum Segment Size,TCP一次传输发送的最大数据段长度。
假设初cwnd为1个MSS。发送端开始按照拥塞窗口大小发送数据,如果被ACK,下次就发送2个。如果还是收到了ACK,则发送4个,接着8个......。这样cwnd的值就随着网络往返时间(Round Trip Time,RTT)呈指数级增长,事实上,慢启动的速度一点也不慢,只是它的起点比较低一点而已。
拥塞避免
从慢启动可以看到,cwnd可以很快的增长上来,从而最大程度利用网络带宽资源,但是cwnd不能一直这样无限增长下去,一定需要某个限制。TCP使用了一个叫慢启动门限(ssthresh)的变量,当cwnd超过该值后,慢启动过程结束,进入拥塞避免阶段。拥塞避免的主要思想是加法增大,也就是cwnd的值不再指数级往上升,开始加法增加。此时当窗口中所有的报文段都被确认时,cwnd的大小加1(此处加1指的是加1个MSS,下文同样如此),cwnd的值就随着RTT开始线性增加,这样就可以避免增长过快导致网络拥塞,慢慢的增加调整到网络的最佳值。
快重传与快恢复
进入拥塞避免之后,最终还是会碰到拥塞点,发送方此时一直得不到接收端的确认。因此TCP在发送一个数据以后就开启一个计时器, 在一定时间内如果没有得到发送数据报的ACK报文,那么就重新发送数据,直到发送成功为止,这就是超时重传。
而快重传算法首先要求接收方每收到一个失序的报文段就立即发出重复确认。比如A给B发送M1, M2, M3, M4, M5,如果B收到了M1, M2, M4,M5;M3并没有接收到,那么在B接到M4时就会发送一次M2的ACK,接到M5,又会发送一次M2的ACK,这样重复确认M2意在告诉A,M3还没收到,可能是丢失了。
当A连续收到了三个确认M2的ACK,若M3超时事件还没发生,此时A也会假定M3丢失了,这个时候A就不必等待M3设置的计时器到期了,而是进行快速重传。
而快速恢复是在上述的快速重传后添加的,快速重传和快速恢复算法会同时使用,快恢复会:
1.当收到3个重复M2的ACK时,为了预防网络发生拥塞,把ssthresh设置为cwnd的一半,把cwnd设置为ssthresh的值加3,重传M3
快速恢复的思想是“数据包守恒”原则,即同一个时刻在网络中的数据包数量是恒定的,只有当“老”数据包离开了网络后,才能向网络中发送一个“新”的数据包,如果发送方收到一个重复的ACK,那么根据TCP的ACK机制就表明有一个数据包离开了网络,收到3个重复的ACK,表明有3个“老”的数据包离开了网络,因此此处加3。
2.再收到重复的ACK时,cwnd增加1。
3.当收到M4(新数据包)的ACK时,把cwnd设置为第一步中的ssthresh的值
通过新数据包的ACK(M4)确认了新的数据,说明从重复ACK时的数据(M3)已收到,该恢复过程结束,可以回到恢复之前的拥塞避免状态。
转载自:https://juejin.cn/post/7343895308932251698