用户态协议栈TCP/IP 滑动窗口 拥塞慢启动
为什么普遍使用TCP?
1、数据可靠
2、传输效率不低
3、顺序
TCP协议头
每个TCP包,都会有这个协议头。
问:
sequence Number 的初始值是多少?
有没有可能有溢出的现象?
答:
sequence number 初始值是随机的。
如果超出的话,就从头开始
struct tcphdr {
unsigned short sport;
unsigned short dport;
unsigned int sequnm;
unsigned int acknum;
unsigned char hdrlen:4,
resv:4;
unsigned char cwr:1,
ece:1,
urg:1,
ack:1,
psh:1,
rst:1,
syn:1,
fin:1;
unsigned short cwnd;
unsigned short check;
unsigned short urg_point;
};
//TCP包
struct tcppkt {
struct ethhdr eh;
struct iphdr ip;
struct tcphdr tcp;
unsigned char body[0];
};
3次握手
服务器准备了队列
syn队列 也叫 半连接队列
accept队列 也叫 全连接队列
为啥需要三次握手呢?
因为两次的话,服务器没法知道,客户端是否收到。
四次的话,没有必要。
clientfd = accept(listenfd, addr, ```);
1、 从accept队列里取出一个节点
2、为了这个节点,分配了一个fd,与这个节点一一对应。
如果没有客户端连接的话,accept就会阻塞。
阻塞是怎么实现的,如果accept队列取不到节点,
就阻塞了。进入条件变量等待 这个条件变量满足。
当队列不为空时,则条件满足,则解除阻塞。
问: 这个节点是什么东西?
答:这个节点包含了五元组,即 源IP地址,目的IP地址,源端口号,目的端口号,协议等,还有fd。
一个fd对应一个这样的节点。fd是客户端唯一的标识。
当我们调用send这个函数的时候,传入的fd,就会通过节点找到对应的五元组。
-
问: syn队列、accept队列设置多长呢?
答:测一下,1秒中的接入量,在接入量的基础上除以20。 -
问:什么是DDOS攻击,syn floor?
答:如果有n个客户端只发送syn,不发ack应答。
它造成了 真正有用的连接进不来。 -
问:DDOS攻击要怎么防御呢?
答:只能通过硬件层面来防御,如增加壁垒及,物理防火墙的方式来保护。在软件层面上没法防御。 -
问:listen(fd, backlog); 这个backlog是啥?
答:1、在mac系统上,只代表syn队列的长度
2、在linux系统上,它打表着syn+accept队列的长度。
TCP 11种状态转化图
TCP怎么保证高效
需要引入滑动窗口来保证高效
kernel中有四种类型的数据包,如下图所示
问:滑动窗口要怎么计算?能发送多少?
对端回来的ack包中,有着确认序号 通过这个确认序号值,我们就可以知道,这个确认序号之前的包已经收到过了,确认序号开始以及之后的,都是下次需要待确认的包。
而且Ack包里带滑动窗口大小的window size。
通过这两个值,我们可以调整滑动窗口的大小。
左边的变化,是通ACK的值来变化。
右边的变化,是通过ACK+window size的大小来确定。
TCP 是怎么保证顺序的?
TCP协议有个确认机制。
这里引入了一个新的概念, “延迟确认”。
接收端收到一个包,就启动一个定时器,定时200毫秒,再收到一个包,再重置定时器的时间。
200毫秒是一个固定的值,TCP协议栈就是等待200毫秒。如果200毫秒超时的话,按顺序进行检查,
举个例子,如发现,5,6,7,9,10 序号包到了,8序号的包并没有到,那么就给发送方发送确认消息,确认序号填的8。那么发送端收到之后,就修改滑动窗口的开始确认下标和,并且修改窗口的大小值。
发送端重新传8号,9, 10 号也会重传。
为什么还有UDP传输?
UDP的应用场景,主要在下载,游戏。针对实时性要求较高的通信场合。
拥塞控制是怎么样的?
第一阶段,叫慢启动。
这时候,滑动窗口的大小 cwnd, 每经过一个RTT时间, cwnd = 2 * last cwnd, 呈指数增长。
当增长到一个值,叫门限值。
就进入了第二阶段, 叫拥塞避免。
这时候,就变成线性增长。每一个RTT时间,CWND增加一个报文段大小的.
如果遇到拥塞,滑动窗口怎么算?
网络上的拥塞分为两种:
一种情况是, 发送端没有收到确认应答,超时重传时。
一种情况是,发送端收到了三次重复的ACK应答。这种情况,一般是丢失了中间的某一序号的包。之后往后面发包的时候,都会重复发丢失某一序号包的ACK应答。
第一种的拥塞,采用了慢启动的方法。将门限值设置为当前窗口大小的一半,并把当前滑动窗口大小变为一个报文段的长度大小。这就是重新进入慢启动的过程。
第二种拥塞情况,是用拥塞避免的方法。
问一下,第二种情况为什么要选中这种拥塞避免的方法呢?
在这种情况下没有执行慢启动的原因是由于收到重复的ACK不仅仅告诉我们一个分组丢失了。由于接收方只有在收到另一个报文段时才会产生重复的ACK,而该报文段已经离开了网络并进入了接收方的缓存。也就是说,在收发两端之间仍然有流动的数据,而我们不想执行慢启动来突然减少数据流。
– 摘自《TCP/IP》卷1
它的做法就是:
将门限值 设置为当前 滑动窗口 cwnd的一半。重传丢失的报文段。设置 cwnd 为门限值 加上3倍的报文段大小。
RTT怎么计算?
如果RTT不稳定怎么办?
比如,我搭电梯的时候,RTT花了5s,走出电梯又为2s,下一次信号好又为0.1s。
抖动现象很大,RTT该怎么计算呢?
这里有一个公式:
rrt = 0.9 * last RRT + 0.1 * cur RRT
得到一个新的RRT。这个公式就是消抖的过程。
对当前的RRT的依赖比较小。
TCP四次挥手
tcp: socket\listen\accept\send\recv\close\connect\bind
这8个接口,哪个接口有发送包的功能。
只有这三个,connect, close, send.
tcp三次握手,必须由客户端先发送请求。
四次握手,没有要求谁先发出。
为什么TCP断开连接是四次的?
因为TCP是双工的,当A调用了close的时候,它是关闭的。
它也可以三次,把ACK和FIN合在一起发。
time_wait 状态有什么用?
1、假设发起主动关闭的一方,最后发送的ACK在网络中丢失,由于TCP协议的重传机制,执行被动关闭的一方将会重发FIN。在该FIN到达client之前,client必须维护这条连接状态的状态,也就是这条TCP连接所对应的资源,不能被立即释放或者重新分配,直到另一方重发FIN达到之后,client重发ACK,经过2MSL的时间周期没有收到另一方的FIN之后,该TCP连接才能恢复初始的closed状态。如果主动关闭方,不维护这样一个TIME_WAIT状态,那么当被动关闭的一方重发FIN到达之后,主动关闭一方的TCP传输层会用RST包响应对方,这样会被对方认为是错误发生,然而事实上只是正常的关闭连接过程,并非异常。
2、为使旧的数据包在网络因过期而消失。
每个具体的TCP实现必须选择一个报文段最大的生存时间MSL。这个任何报文段被丢弃前在网络内的最长时间。
用netstat anp | grep 端口 查看时, 发现close_wait 状态 过多。
由于被动关闭方的连接,没有及时调用close 导致的。
如果双方同时调用关闭?
也就是,相当于A调用了close(),发送了FIN之后,还没收到ACK,就收到了对端发来的FIN,
这时候A会由FIN_WAIT_1 状态 变为 closing状态。
我们在以前讨论过一方(通常但不总是客户方)发送第一个FIN执行主动关闭。双方都执行主动关闭也是可能的,TCP协议也允许这样的同时关闭(simultaneous close)。
当应用层发出关闭命令时,两端均从ESTABLISHED变为FIN_WAIT_1。这将导致双方各发送一个FIN,两个FIN经过网络传送后分别到达另一端。收到FIN后,状态由FIN_WAIT_1变迁到CLOSING,并发送最后的ACK。当收到最后的ACK时,状态变化为TIME_WAIT。图18-19总结了这些状态的变化。
摘自《TCP/IP》 卷1
FIN_WAIT_2 如果不收到FIN的话,这个状态会持续多久?
FIN_WAIT_2 的状态怎么消除?
没法消除,没办法直接终止,它变成了单工的通信。
socket端口会被一直占用,tcb控制块会一直存在。