【网络】传输层协议TCP
目录
四位首部长度
序号
捎带应答
标记位
超时重传机制
连接管理机制(RST标记位)
三次握手及四次挥手的原因
TCP的全称是传输控制协议(Transmission Control Protocol),也就是说,对于放到TCP发送缓冲区中的数据,如何发,什么时候发,出错了怎么办,这些都是有TCP协议控制的
下面是TCP报文的基本格式
标准报头是20个字节,并且在传输层,并不会像我们在应用层一样还需要序列化,传输层直接发送的就是结构化的数据,因为这样发送成本是最低的,发送带宽最小。那它不是会有跨平台,大小端的问题吗?Linux内核中对它进行了处理:
不同的客户端都同时可以基于TCP协议向服务器发送信息,那么OS中就会存在很多收到的报文,这些报文可能正在网络层或数据链路层中,还没有拷贝到传输层的接收缓冲区,可能就在缓冲区中,还没来的及处理,总之各种情况
那么OS就要对大量的已经收到的,但是暂未处理的报文进行管理,如何管理呢?先描述再组织
而描述这种报文的结构体就叫做struct sk_buffer,它里面存有指针,就指向存着报文的内存空间,对于每层协议来说,报头在前面,正文在后面,我们如果要剥离报头,只需要指针向后移动一定的字节数即可,添加报头就是向前移动。所以封装与解包本质就是指针移动,然后进行一定操作
四位首部长度
那下面来解释一下TCP报文中的4位首部长度:
这个长度指的是报头的20个字节加上选项的长度,并且基本单位是为4字节
也就是说4位能表示的最大数字是15,即报头+选项的最大长度是15*4=60字节,选项的范围是[0,40]
我们都说TCP协议是可靠的,它保证可靠性,具体体现是什么呢?
因为我们发到网络中的消息,我们根本不知道它去了哪里,我们如果想知道它有没有被对方收到,就只能看有没有收到应答
所以我们可以确定的是如果我收到应答,那么我发的消息对方肯定收到了。这依靠的就是确认应答机制:我收到了消息就要应答
所以可靠性是指:我能知道对方收到了我发的消息,同时我也得知道对方没收到我发的消息
发送数据和应答这些细节其实都是双方OS完成的,这也就是“传输控制协议”中“控制”的一个体现
序号
我们发送数据可以串行发,也就是发送一条等应答,然后再发下一条等应答……,但是这样效率太慢了
我们可以一次发多条,但是问题就来了,对方也给我很多应答,那如果有一条消息对方没收到,那我怎么知道是那一条消息呢?并且对方怎么判断那条消息在前,那条消息在后呢?(因为我的发送顺序并不一定是对方的接收顺序)
这个问题的解决方法就是报头中的序号,我们可以给消息带序号,对方收到了一堆消息也可以通过排序序号来知道我发的消息的顺序,然后再按顺寻处理数据即可,所以应答就要把确认序号带上,这个确认序号一般是发来消息的序号+1
确认序号的含义就是该确认序号之前的数据,对方已经全部收到了,下次发送请从确认序号开始
我们知道TCP是有发送缓冲区,并且TCP是面向字节流的,其实我们可以把这个缓冲区看成一个一定长度的char数组,那么缓冲区中每个字节其实都有自己的序号,这个序号就是下标,确认序号我们前面说就是发来的报头中的序号+1,那么新的序号其实就是确认序号+我要发送的报文的长度
捎带应答
TCP是支持全双工的,既要向对方发送消息,也要向对方发送应答,所以需要序号和确认序号两个
如果只发应答,只需要发一个报头(报头中添加确认序号)即可,那么我可不可以发送一个报文,其中既有应答(确认序号),又有我想给对方发的数据(序号和正文)呢?当然可以,我们叫做捎带应答
标记位
下面我们来解释TCP报头中的标志位:
为什么要有标志位呢?其实就是区分不同的TCP报文类型
就比如我们知道基于TCP通信时,客户端要connect服务器,这是发的报文就是建立连接的报文,同时还有正常通信的报文,还有close时断开连接的报文等等,这些报文类型都不同,那么为了区分这些报文类型,就有了标志位
下面我们简单通过三次握手这个过程来介绍三个标记位:ACK(确认标记位),SYN(同步标记位),FIN(断开连接标记位)
这个过程是我们在客户端调用connect后OS自动完成的,首先客户端向服务器发送带SYN标记位(即将此位 置为1)的报头,服务器收到后向客户端发送SYN+ACK的报头,客户端收到后向服务器发送ACK的报头,至此三次握手成功后服务器accept获取连接。我们将服务器置为监听状态是因为只有listen状态的服务器才能受理SYN的请求
双方建立好连接之后是要对这个连接进行管理的,用内核数据结构去管理,,这样就会消耗内存空间和时间。所以说维护连接是有成本的
下面是四次挥手的大致过程:这个过程也是OS自动完成的
先调用close的一方(A)要向对方发送FIN请求,对方(B)收到后会发送ACK,之后B会发送FIN请求,A收到后发送ACK
超时重传机制
这个机制其实在报头中是没有体现的,当我们向对方发送请求时,如果我们迟迟都收不到应答,我们只能主观的认为对方没收到数据(也有可能收到了,只不过对方的ACK可能没发过来,我们肯定要按最差情况处理),此时,我们就认为超时了,我们需要重传。
也就是说,如果我们发送的请求在一个时间段内都没有收到应答,就要触发超时重传机制。
那万一对方收到了呢?只不过是ACK没发过来,因为报头中是有序号的,对方可以根据这个序号进行去重
上面说一个时间段内,那这是多少时间呢?
因为网络的状态是动态的,TCP为了保证无论在任何环境下都能比较高性能的通信,因此会动态计算这个超时时间
Linux,超时以500ms为一个单位进行控制,等待时间以此是1*500,2*500,4*500,这样以指数形式递增,累积到一定的重传次数,TCP就会认为网络或对端主机出现异常,强制关闭连接
连接管理机制(RST标记位)
有这样一种情况,就是三次握手的第三次ACK发送之后丢掉了,这时客户端已经认为建立好连接了,但是服务器却没有建立好连接。
此时客户端都可以向服务器发送数据了,此时服务器因为没有建立好连接但是收到了数据,此时就会向客户端发送带有RST标记位的报头来表示reset(重置),即重新进行三次握手
所以RST这个标记位就是用来解决建立连接出现异常的问题的
三次握手及四次挥手的原因
三次握手:
如果一次就可以握手,那太扯了,因为客户端连应答都没有收到,连最基本的网络连通性都无法确认
两次的话服务器是比客户端先建立连接的,维护连接是有成本的,如果客户端是恶意连接的,疯狂发送SYN请求(SYN洪水攻击),那么服务器资源就会被浪费,从而导致真正想连接的用户无法连接
所以三次握手就是因为:
1.需要保证网络(信道)是健康的,三次握手,客户端服务器双方都会有一次确定的收发,他们就可以确认是全双工
2.确保双方OS是健康且愿意通信的
四次挥手:
它的原因其实和三次握手是一样的,关键就是四次挥手没有进行捎带应答,因为虽然比如客户端关闭连接,但是服务器还有消息要发。那么此时用户如何拿取数据呢?
首先一般应用层代码是有逻辑的,一般按照协议收完消息后才会close;其次,其实我们可以仅关闭写端,不关闭读端,是有这样的接口的
如果选项是SHUT_RDWR,就相当于close