目录
前言
在上一篇文章中我们介绍了关于UDP是如何实现的,今天我们要介绍的是TCP,关于TCP协议,前面说了是可靠的,今天我们就一起来看看TCP协议为什么是可靠的,关于可靠的实现采用了什么策略。
1.TCP协议
TCP全称为 "传输控制协议(Transmission Control Protocol"). 人如其名, 要对数据的传输进行一个详细的控制;
当数据拷贝到传输层的时候,继续向下传输数据的时候操作系统有自己的传输策略,所以TCP协议被称为传输控制协议,TCP协议既有发送缓冲区又有接受缓冲区,所以TCP是全双工的
如图所示:
2.TCP协议段格式
3.如何解包如何分用
4.网络协议栈和文件的关系
如图所示:
5.如何理解TCP报头
报头在语言层面就是一种结构化的数据:
struct tcp_ hdr
{
uint32_t src_port:16;
uint32_t dst_port:16;
uint32_t seq;
uint32_t ack_seq;
uint32_t header_length:4;
....
};
6.TCP的特点
可靠性,传输效率高
a.理解可靠性:
为什么会存在不可靠性:网络传输存在不可靠性的本质原因是因为传输的距离变长了
不可靠性的场景:丢包,乱序,校验错误,重复......
b.tcp如何保证可靠性:
感性理解可靠性
A和B通信:
距离变长了,不存在绝对的可靠性,但是存在相对的可靠性。
如何保证相对的可靠性呢?
一个报文只要收到了应答。就能保证该报文的可靠性!
TCP采用确认应答机制保证可靠性,双方通信一定存在最新消息,如果没有应答,最新消息一般无法保证可靠性
理解TCP的工作模式:
保证可靠性,无论是客户端到服务端还是服务端到客户端都需要有应答,双方在通信的时候,可能除了正常的数据段,还可能包含确认数据段。
TCP的真实工作模式:
tcp协议数据传输的时候达到对面的顺序和发送时的顺序不一定一样。
为了解决上述问题:需要有方式标识数据段本身,标识数据段本身采用序号和确认序号的方式
理解序号和确认序号:
确认序号 = 序号 + 1 并且是连续的
为什么序号有两组:因为TCP是全双工的,客户端向服务端发送消息,服务端向客户端发送消息
真实序号: 因为tcp协议传输数据是面向字节流的,所以将每个字节的数据都进行了编号,即序列号
每一个ACK都带有对应的确认序列号, 意思是告诉发送者, 我已经收到了哪些数据; 下一次你从哪里开始发.
7.TCP字段
7.1 16位窗口大小
tcp在发送数据的时候,快了不行,慢了也不行,如何保证发送合适的数据呢?
解决方式: 16位窗口大小一本质上是一块缓冲区,当一方向对方发送数据的时候,在自己的报头中填写缓冲区剩余空间的大小! 通过16位窗口的方式实现数据传输时的流量控制
7.2标志位
服务器会受到各种各样的报文,接受方需要根据不同的报文,做不同的动作,tcp报文也是有类型的,有的是正常的数据段,有的是ack确认数据段,按照不同的标志位做不同的动作。
tcp报文类型的划分: URG. ACK. PSH, RST, SYN, FIN
SYN:连接建立的时候,将该标志位设置为1
FIN:连接断开的时候,将该标志位设置为1
ACK:报文为确认类型的时候,将该标志位设置为1
PSH:当接收方的缓冲区满了之后,发送方催促接受方尽快将数据拿走,告知这个信息,将该标志设置为1
URG:
RST:
8.超时重传
9.连接管理机制
在正常情况下, TCP要经过三次握手建立连接, 四次挥手断开连接
如何理解三次握手:
三次握手是tcp协议建立连接定制的策略,三次握手不一定保证连接一定会成功!
一次握手和两次握手可以吗?
答案是一定不行的,因为当客户端和服务端建立连接的时候,连接是需要被os管理起来的,管理的方式先描述,再组织。
管理一定会有时间和空间的成本,如果一次握手和两次握手就能够让连接成功,服务端就有可能会受到客户端的攻击,一般将这种攻击称为是SYN洪水
三次握手的特点:
a.用最小的成本验证全双工通信信道是通畅的
b.三次握手可以有效防止单机对服务器进行攻击.
如何理解tcp要建立连接?
tcp建立连接是因为要保证可靠性。
如何保证可靠性?
结构体字段保证了可靠性的数据结构基础,三次握手是创建连接结构体的基础,通过这样的方式间接保证了tcp的可靠性!
如何理解三次握手以上的建立连接:三次握手以上也能建立连接,但是造成了不需要的资源浪费!
如何理解四次挥手:断开连接是双方的事情,需要征得双方的同意
这里所谓的不发数据是指不发用户数据,并不代表底层没有管理报文的交互
tcp是如何知道数据已经发送完了,需要断开连接了呢?
答案是tcp并不知道,但是上层会调用close (sock),关闭文件描述符标识着数据已经传输完毕了,需要断开连接了。
如果服务器大量出现close_wait:
1.服务器有bug,没有做close关闭文件描述符的动作
2.服务器有压力,可能一直在推送消息给client,导致来不及close
如何解决这个问题:
使用setsockopt()设置socket描述符的 选项SO_REUSEADDR为1, 表示允许创建端口号相同但IP地址不同的多个socket描述符
10.滑动窗口
如何理解滑动窗口:
首先数据可能存在丢失的情况,因此有超时重传机制,决定了数据由发送端发送数据之后不会被清除掉,而是保存在发送缓冲区中,发送缓冲区又被细化分为:
如图所示
如何看待滑动窗口:
窗口大小起始如何设定的,未来如何改变?
win_ start = 0; win_ end = win_ start + tcp_ win ->未来无论怎么移动,都要保证对方能够正常接受! 所以起始滑动窗口大小=对方告知我的自己接受能力的大小
窗口会向左滑动吗?一定 会向右滑动吗?
答案是不会向左滑动
可能会向右滑动,也可能保持不变
滑动窗口会一直不变吗?会变大吗?会变小吗?变的依据是什么?
答案是滑动窗口可能会一直不变, 也可能会变大,也可能会变小,变的依据是根据对方可接受缓冲区的大小!
收到应答确认的时候,如果不是最左侧发送报文的确认,而是中间的,结尾的,怎么办?
如果说没有收到应答确认就说明是丢包了:
丢报包含两种情况:
1.数据没丢,只是应答丢了
2.数据真的丢了
针对第一种情况: 因为确认序号的定义是ACK seq X + 1, 表示X + 1之前的数据全部都收到了win_ start+X+1
针对第二种情况:数据真的丢了返回的确认序号依旧是前面的确认序号,规定ack序号连续三个相同的序号,会触发重传机制
滑动一直向后滑动,空间不够了怎么办?
发送缓冲区在内核中被设置为环形结构
11.拥塞控制
如何理解拥塞控制:
拥塞控制的机制:
TCP引入慢启动机制, 先发少量的数据, 探探路, 摸清当前的网络拥堵状态, 再决定按照多大的速度传输数据;
采用指数增长的模式进行传输
拥塞窗口的增长速度是指数级别的:
慢启动是指初始是慢,但是增长速度是非常快的,所以为了控制,不能使拥塞窗口单纯的按照指数形式增长
采用的方法:使用一个阈值
当拥塞窗口超过这个阈值的时候,不再按照指数方式增长,而是按照线性方式增长!
当TCP开始启动的时候, 慢启动阈值等于窗口最大值;
在每次超时重发的时候, 慢启动阈值会变成原来的一半, 同时拥塞窗口置回1;
总结:
12.延迟应答
延迟应答的目的:在不考虑网络拥塞的情况下,尽量提高传输效率
如何理解延迟应答:
举例说明:
假设接收端缓冲区为1M. 一次收到了500K的数据; 如果立刻应答, 返回的窗口就是500K;
但实际上可能处理端处理的速度很快, 10ms之内就把500K数据从缓冲区消费掉了;
在这种情况下, 接收端处理还远没有达到自己的极限, 即使窗口再放大一些, 也能处理过来;
如果接收端稍微等一会再应答, 比如等待200ms再应答, 那么这个时候返回的窗口大小就是1M;
13.捎带应答
client向server端发送数据,server端接受到数据之后也会向client发送数据,在发送数据的时候会将上一次c1ient发送的数据的应答一起发送过去,此时,这种应答方式被称为是捎带应答,本质上也是提高数据传输效率!
14.理解TCP的面向字节流
15.粘包问题
16.TCP异常情况
17.TCP小结
18.TCP/UDP对比
我们说了TCP是可靠连接, 那么是不是TCP一定就优于UDP呢? TCP和UDP之间的优点和缺点, 不能简单, 绝对的进行比较
归根结底, TCP和UDP都是程序员的工具, 什么时机用, 具体怎么用, 还是要根据具体的需求场景去判定.
19.如何使用UDP实现可靠传输
参考TCP的可靠性机制, 在应用层实现类似的逻辑;
例如:
20.理解listen的第二个参数
举一个生活中的例子:
在吃海底捞的时候,如果里面人已经满了,当继续有客户来的时候,一般会在外边有桌子让来的客户坐着排队等候。
排队的本质是:当有人吃完离席的时候,等候的客户可以马上吃海底捞,对于老板来说,提高了桌子的利用率,进而提升收入,但是排队等候的也不利于过长
如图所示:
客户端状态正常, 但是服务器端出现了 SYN_RECV 状态, 而不是 ESTABLISHED 状态
这是因为, Linux内核协议栈为一个tcp连接管理使用两个队列:
总结
以上就是TCP协议实现的全部内容,相信看完UDP协议的实现和TCP协议的实现对比而言,TCP为了实现可靠性,采取了相当多的策略,感谢大家的阅读,今天的介绍就到这里了,我们下次再见!