目录
一.滑动窗口
1.1概念
1.2滑动窗口存在的意义
1.3 滑动窗口的大小变化
1.4丢包问题
二.拥塞控制
三.延迟应答
四.捎带应答
五.面向字节流
六.粘包问题
七.TIME_WAIT状态
八.listen第2个参数
九.TCP总结
一.滑动窗口
1.1概念
概念:双方在进行通信时,一方可以一次性向另一方发送多条消息,这样可以将等待多个响应的时间重叠起来,进而提高数据通信的效率。
窗口大小指的是无需等待确认应答而可以继续发送数据的最大值,操作系统内核为了维护这个滑动窗口, 还需要开辟发送缓冲区 ,只有确认应答过的数据, 才能从缓冲区删掉。
其实可以将发送缓冲区当中的数据分为三部分:
- 已经发送并且已经收到ACK的数据。
- 已经发送还但没有收到ACK的数据。
- 还没有发送的数据。
1.2滑动窗口存在的意义
可以提高传输数据的效率
滑动窗口的大小等于对方窗口大小与自身拥塞窗口大小的较小值,因为发送数据时不仅要考虑对方的接收能力,还要考虑当前网络的状况。可以一次性发送多条消息,将多个等待时间重叠起来,滑动窗口越大,则网络的吞吐率越高,同时也说明对方的接收能力很强。
1.3 滑动窗口的大小变化
滑动窗口不一定会整体右移,会根据对方的接受能力不断改变。一般情况下滑动窗口是向右移动,说明对方接受能力稳定。如果对方不接收或16位窗口大小变小了,说明对方接受能力变小,滑动窗口会变小,即:start_index 会向右走,end_index不动;如果对方返回的16位窗口大小变大了,说明对方接受能力变大,滑动窗口会变大,即:start_index不动 ,end_index向右走。
1.4丢包问题
如果一次性发送多条数据,出现丢包了,该如何解决呢?
情况一:数据被对方接受了,但自己未收到对方ACK应答
这种情况相对好点,即使前面几条ACK应答丢了,但只要自己收到了较大的ACK应答(确认序号更大),就说明在收到的较大ACK应答对应的那条数据以及之前的数据,对方都接受到了。
情况二:数据未被对方接受到。那么直接进行重新发送。
在上面的那个例子中如果发送端主机连续三次收到了同样一个 "1001" 这样的应答, 就会将对应的数据 1001 - 2000 重新发送;
这个时候接主机B收端收到了 1001 之后, 再次返回的ACK就是7001了,因为之前的数据都已经收到了, 被放到了接收端操作系统内核的接收缓冲区中,这种机制被称为 "高速重发控制"(也叫 "快重传").
二.拥塞控制
如果双方在进行通信过程中,发生了大量的丢包问题,那么可能就是网络出现了问题。
如何去解决呢?
能否用滑动窗口解决:因为网络上有很多的计算机, 可能当前的网络状态就已经比较拥堵. 在不清楚当前网络状态下, 贸然发送大量的数据,是很有可能会进一步导致问题更严重。能否进行重传:一样,再次向网络上输入数据,就可能造成更严重的问题。
解决方法:
TCP引入 慢启动 机制, 先发少量的数据, 探探路, 摸清当前的网络拥堵状态, 再决定按照多大的速度传输数据, 此处引入一个概念程为拥塞窗口,定义:在拥塞窗口发送数据量以内不会拥塞,超过可能会引发拥塞问题。
例如,当出现拥塞时,将拥塞窗口定义为一个较小的值,当能收到ACK应答时,在不断去增大这个拥塞窗口(以指数级别)。滑动窗口的大小= min(对方的窗口大小,拥塞窗口)
补充:
在慢启动时是以指数级别增加的,增加到一定程度时,会线性增长。
此处引入一个叫做慢启动的阈值,当拥塞窗口超过这个阈值的时候, 不再按照指数方式增长, 而是按照线性方式增长。当TCP刚开始启动的时候,慢启动阈值设置为对方窗口大小的最大值。在每次超时重发的时候,慢启动阈值会变成当前拥塞窗口的一半,同时拥塞窗口的值被重新置为1,如此循环下去。
三.延迟应答
当接受数据的一方收到数据后,不要立即进行ACK应答。(实际接收端处理数据的速度很快,可能在很短额时间内就将接受缓冲区内的数据读取了)接收端稍微等一会再进行ACK应答,比如等待200ms再应答,那么这时返回的ACK应答中窗口大小就能更大。
此外,不是所有的数据包都可以延迟应答。
- 数量限制:每个N个包就应答一次。
- 时间限制:超过最大延迟时间就应答一次(这个时间不会导致误超时重传)。
延迟应答具体的数量和超时时间,依操作系统不同也有差异,一般N取2,超时时间取200ms。
四.捎带应答
概念:当一方要给另一方发送响应时,同时也要给对方发送消息,那么二者可以一起发送,双方通信时就不是单独发送确认报文,单独发送ACK响应,这样也增大了传输效率。
五.面向字节流
在创建一个TCP的socket时,同时在内核中会创建一个发送缓冲区和一个接收缓冲区。由于缓冲区的存在,发送数据的方式和数据的格式完全无关系,即TCP程序的读和写不需要匹配。
1.调用write时, 数据会先写入发送缓冲区中, 如果发送的字节数太长, 会被拆分成多个TCP的数据包发出。 如果发送的字节数太短, 就会先在缓冲区里等待, 等到缓冲区数据有一定数量了, 或者其他合适的时机发送出去。(调用write可以认为是在拷贝数据到相关内核中,具体什么时候发送是由操作系统决定的)
2.在接收数据的时候, 数据也是从网卡驱动程序到达内核的接收缓冲区,然后应用程序可以调用read从接收缓冲区拿数据;
TCP既有发送缓冲区, 也有接收缓冲区, 那么对于这一个连接, 既可以读数据, 也可以写数据. 这个概念叫做 全双工
例如:
写100个字节数据时,可以调用一次write写100字节,也可以调用100次write,每次写一个字节。
读100个字节数据时,也完全不需要考虑写的时候是怎么写的,既可以一次read100个字节,也可以一次read一个字节,重复100次。
六.粘包问题
这个概念是在应用层上的。因为TCP通信是面向字节流的,
在TCP的协议头中, 没有如同UDP一样的 "报文长度" 这样的字段, 但是有一个序号这样的字段。站在传输层的角度,TCP的报文是按照序号排好序放在缓冲区中,站在应用层的角度, 看到的只是一串连续的字节数据。
如何解决呢?
要解决粘包问题,本质就是要明确报文和报文之间的边界。
对于定长的包,保证每次都按固定大小读取即可。
对于变长的包,可以在报头的位置,约定一个包总长度的字段,从而就知道了包的结束位置。比如HTTP报头当中就包含Content-Length属性,表示正文的长度。
对于变长的包,还可以在包和包之间使用明确的分隔符。因为应用层协议是程序员自己来定的,只要保证分隔符不和正文冲突即可。
UDP协议来说, 是否也存在 "粘包问题" 呢?
不存在,因为UDP协议是面向数据报的。
面向数据报:当应用层交给UDP多长的报文,UDP就原样发送,既不会拆分,也不会合并,这就叫做面向数据报 ,不能够灵活的控制读写数据的次数和数量。
因为UDP报头当中的16位UDP长度记录的是UDP报文的长度,因此UDP在底层的时候就把报文和报文之间的边界明确了,而TCP存在粘包问题就是因为TCP是面向字节流的,TCP报文之间没有明确的边界。站在应用层的角度,使用UDP的时候,要么收到完整的UDP报文,要么不收,不会出现“半个”的情况。
七.TIME_WAIT状态
前面已经介绍了,双方在通信过程中,主动断开连接的一方,在完成4次挥手后,不会立即进入CLOSED状态,而是进入短暂的TIME_WAIT状态等待若干时间,最终才会进入CLOSED状态。
若是服务器先断开连接,在TIME_WAIT的时间里,该连接处于TIME_WAIT期间,如果服务器想要再次重新启动,就会出现绑定失败的问题(绑定同一个端口号)。因为在TIME_WAIT期间,这个连接并没有被完全释放,也就意味着服务器绑定的端口号仍然是被占用的,此时服务器想要继续绑定该端口号启动,就只能等待TIME_WAIT结束。
解决办法:setsockopt,让服务器在调用socket函数创建套接字后,继续调用setsockopt函数设置端口复用。
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
参数解释:
sockfd:需要设置的套接字对应的文件描述符。
level:被设置选项的层次。比如在套接字层设置选项对应就是SOL_SOCKET。
optname:需要设置的选项。该选项的可取值与设置的level参数有关。
optval:指向存放选项待设置的新值的指针。
optlen:待设置的新值的长度
例如:
int opt = 1;
setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
八.listen第2个参数
创建TCP服务端流程:套接字创建、绑定、监听,调用accept函数。
TCP在进行连接管理时会用到两个连接队列:
- 全连接队列(accept队列)。全连接队列用于保存处于ESTABLISHED状态,但没有被上层调用accept取走的连接。
- 半连接队列。半连接队列用于保存处于SYN_SENT和SYN_RCVD状态的连接,也就是还未完成三次握手的连接。
一般全队列的长度是等于listen的第2个参数的值+1.
意义:服务器启动一般会启动多个线程(数量也是有限的),主线程从底层accept上来连接后就可以将其交给这些服务线程进行处理。当所有线程都在运行时,维护一个全队列,不会直接拒接客户端的连接请求,而是在一些线程空闲时在accept这些全队列中的请求。
九.TCP总结
可靠性:
- 检验和。
- 序列号。
- 确认应答。
- 超时重传。
- 连接管理。
- 流量控制。
- 拥塞控制。
提高性能:
- 滑动窗口。
- 快速重传。
- 延迟应答。
- 捎带应答。
TCP的各种机制实际都没有谈及数据真正的发送,这些都叫做传输数据的策略。TCP协议是在网络数据传输当中做决策的,它提供的是理论支持,比如TCP要求当发出的报文在一段时间内收不到ACK应答就应该进行超时重传,而数据真正的发送实际是由底层的IP和MAC帧完成的。