发送方如何判定丢包了呢?
其实真正有没有丢包,发送方其实不知道。定的策略,超时了,就判定丢包了
但是,主机A未收到B发来的确认应答,也可能是因为ACK丢失了
因此主机B会收到很多重复数据(这也是不可靠的一种),那么TCP协议需要能够识别出那些包是重复的包,并且把重复的丢弃掉。这时候我们可以利用前面提到的序列号,就可以很容易做到去重的效果
思考点:发送端,发出去数据,被发出的数据,并不想我们想的那样立马移除,而是必须被维持一段时间,维持在发送窗口或发送缓冲区
如果超时的时间如何确定?
TCP为了保证无论在任何环境下都能比较高性能的通信,因此会动态计算这个最大超时时间。
在正常情况下,TCP要经过三次握手建立连接,四次挥手断开连接
为什么不四次握手?
三次握手是用最小成本验证全双工通信信道是通畅的,三次握手可以有效防止单机进行对服务器进行攻击(服务器收到攻击本身就不是tcp握手该解决的,但是如果握手有明显漏洞,那就是握手的问题了)
三次握手不一定非得成功,最担心的起始是最后一个ACK丢失,但是有配套的解决方案
链接是需要被管理起来的,被OS管理起来的,先描述,在组织。维护一个连接是有成本的[时空]
(三次握手也可称为四次握手,因为服务器在应答的SYN+ACK也可以分开进行,但是它们是不同的标志位,所以可以一次发送过去)
SYN 洪水(SYN Flood)是一种网络攻击方式,旨在通过发送大量的伪造 TCP 连接请求(SYN 包)来超载目标服务器的资源,使其无法正常处理合法的连接请求。
SYN 洪水攻击利用了 TCP 协议三次握手的过程中的漏洞。攻击者发送大量的带有伪造源 IP 地址的 SYN 包给目标服务器,使得服务器在接收到这些 SYN 包后会为每个连接请求分配一定的资源,包括内存和连接表项。然而,由于伪造的源 IP 地址,服务器在等待客户端发送后续的 ACK 包进行连接的建立时,无法得到响应,导致资源被浪费
为什么要连接?因为要保证可靠性
tcp连接本身并不能直接保证可靠性,实际上tcp建立连接的时候,你怎么知道哪些报文丢了?你怎么知道它是处于什么状态是通信状态还是断开状态?哪些报文需要重传?下一次重传的时间又是多长?当前服务端收到了多少报文,又发送了多少报文?这些上面的特征都是要维护在tcp连接结构体中的,正是有三次握手这样的机制,所以才建立了双方连接结构体这样的共识,正是有了连接结构体才能完成所谓的超时重传、流量控制等等策略。所以连接结构体是保证数据可靠性的数据结构基础,而三次握手是创建结构体的基础,所以tcp三次握手就间接保证了可靠性。
TIME_WAIT
状态CLOSE_WAIT
状态(这个与双方是服务器还是客户端无关,因为TCP是地位对等的协议)TIME_WAIT
如果我们的服务器出现了大量的CLOSE_WAIT
?
为什么主动断开连接的一方要维持一段时间的TIME_WAIT
?
一般维持2*MSL的时间,目的是:
服务器有时候可以立即重启,有时候无法立即重启原因是?bind_error
服务器主动断开,服务器要维持TIME_WAIT状态,维持该状态期间,该端口与连接依旧存在,所以你无法绑定该端口
解决TIME_WAIT状态引起的bind失败的方法
解决方法:使用setsockopt()设置socket描述符的 选项SO_REUSEADDR为1, 表示允许创建端口号相同但IP地址不同的多个socket描述符
我们确认应答策略,对每一个发送的数据段,都要给一个ACK确认应答,收到ACK后再发送下一个数据段。这样做有一个比较大的缺点,就是性能较差,尤其是数据往返的时间较长的时候
既然这样一发一收的方式性能较低,那么我们一次发送多条数据,就可以大大的提高性能(其实是将多个段的等待时间重叠在一起了)
发送方怎么在第一次就知道对方的接受能力呢?在通信之前,早就做过了三次握手,交换窗口大小
如果我们发送数据,没有收到应答之前,我们必须将自己的已经发送的数据暂时保存起来,为了支持超时重传
窗口的开始大小是怎么设定的?未来怎么变化?
目前:滑动窗口的大小,和对方的接受能力有关
win_start = 0; win_end = win_start + tcp_win
– 未来无论怎么滑动,都要保证对方能够进行正常接受
滑动窗口大小=对方通告给我的自己的接受能力大小【目前】
确认序号的定义:ACK seq X +1 ,标识X+1之前的所有数据全部都收到了。这也是支持我们滑动窗口的滑动规则设定
1000已经丢包了,那么我们肯定不能返回2000,因为返回2000就代表1000已经应答了。
当TCP客户端发送序号为1000和2000的数据包时,如果序号1000的数据包丢失了,服务端并没有收到该数据包。因此,它期望接收序号为1000的数据包。所以,服务端会返回确认序号(ACK)为1000的ACK包。这意味着服务端告诉客户端:“我已经成功接收了直到序号999的所有数据,现在期待接收序号为1000的数据”。
简单地说,服务端会返回序号为1000的ACK。
一直向后滑动?如果空间不够了怎么办?
其实发送缓冲区被内核组织成了环形结构,上面的线性结构只是方便一开始的理解
就如学校期末考试一样,全班就你没及格,你会觉得是你自己的问题,但是全班就一个人及格,可能就会认为是教学事故或其他原因。
这种发送10000条报文,丢失了9999条的情况,我们一般认为是网络出现了问题,因为我们与Server端有可靠性策略
那么这种网络出现问题我们还进行超时重传吗?不应该,我们以前的各种超时重传,互动窗口都是基于端对端的,网络已经出现问题了,我们如果继续发送,会让网络中又出现大量的报文,只会加重网络的故障问题。你可能觉得你的报文不多,发一下对网络不会有太大问题,但是如果TCP连接都这样,那么整体而言会造成重大问题。
TCP的可靠性不仅仅考虑了双方主机的问题,也考虑了路上网络的问题 – 拥塞控制背景
虽然TCP有了滑动窗口这个大杀器,能够高效可靠的发送大量的数据,但是如果在刚开始阶段就发送大量的数据,仍然可能引发问题。
因为网络上有很多的计算机,可能当前的网络状态就已经比较拥堵,在不清楚当前网络状态下,贸然发送大量的数据,是很有可能引起雪上加霜的。
TCP引入 慢启动 机制, 先发少量的数据, 探探路, 摸清当前的网络拥堵状态, 再决定按照多大的速度传输数据
滑动窗口大小 = min(拥塞窗口,窗口大小)
像上面这样的拥塞窗口增长速度,是指数级别的。“慢启动” 只是指初使时慢,但是增长速度非常快。
少量的丢包,我们仅仅是触发超时重传;大量的丢包,我们就认为网络拥塞;
当TCP通信开始后,网络吞吐量会逐渐上升;随着网络发生拥堵,吞吐量会立刻下降;
拥塞控制,归根结底是TCP协议想尽可能快的把数据传输给对方,但是又要避免给网络造成太大压力的折中方案
如果接收数据的主机立刻返回ACK应答,这时候返回的窗口可能比较小。
一定要记得,窗口越大,网络吞吐量就越大,传输效率就越高。我们的目标是在保证网络不拥塞的情况下尽量提高传输效率;
那么所有的包都可以延迟应答么?肯定也不是
具体的数量和超时时间,依操作系统不同也有差异;一般N取2,超时时间取200ms;
在延迟应答的基础上,我们发现,很多情况下,客户端服务器在应用层也是 “一发一收” 的。意味着客户端给服务器说了 “How are you”,服务器也会给客户端回一个 “Fine,thank you”;
那么这个时候ACK就可以搭顺风车,和服务器回应的 “Fine, thank you” 一起回给客户端
创建一个TCP的socket,同时在内核中创建一个发送缓冲区和一个接收缓冲区;
由于缓冲区的存在,TCP程序的读和写不需要——匹配,例如:
首先要明确,粘包问题中的"包",是指的应用层的数据包。比如你前一个序列的报文少读取一部分,同时也会影响下一个序列号的报文读取,这就是粘宝。
那么如何避免粘包问题呢?
归根结底就是一句话,明确两个包之间的边界
思考:对于UDP协议来说,是否也存在"粘包问题”呢?
进程终止:进程终止会释放文件描述符,仍然可以发送FIN和正常关闭没有什么区别(OS会正常自动四次挥手,跟close没有区别)
机器重启:和进程终止的情况相同
机器掉电/网线断开:接收端认为连接还在,一旦接收端有写入操作,接收端发现连接已经不在了,就会进行reset,即使没有写入操作,TCP自己也内置了一个保活定时器,会定期询问对方是否还在,如果对方不在,也会把连接释放。另外,应用层的某些协议,也有一些这样的检测机制。例如HTTP长连接中,也会定期检测对方的状态,例如QQ,在QQ断线之后,也会定期尝试重新连接。(OS没有时间反应,客户端识别到网络变化,但是服务器不知道,客户端想要告诉服务端也没有办法,因为先拔的网线。这个时候就会。服务端认为连接是好的,客户端认为连接已经关闭了。服务端可能采取某些策略,过一段时间询问一下客户端还在不在。)
为什么TCP这么复杂?因为要保证可靠性,同时又尽可能的提高性能
可靠性:
提高性能:
其他:
当然,也包括你自己写TCP程序时自定义的应用层协议;
客户端状态正常,但是服务器端出现了 SYN_RECV 状态,而不是 ESTABLISHED 状态
这是因为,Linux内核协议栈为一个tcp连接管理使用两个队列:
而全连接队列的长度会受到 listen 第二个参数的影响.
全连接队列满了的时候,就无法继续让当前连接的状态进入 established 状态了
这个队列的长度通过上述实验可知,是 listen 的第二个参数 + 1
这个队列本质上就是给服务器维护的一个短暂的缓冲区,用来随时填补服务器上层服务完毕的时候直接从队列拿取新连接。
如有错误或者不清楚的地方欢迎私信或者评论指出