【计网】从零开始理解TCP协议 --- 熟悉TCP报头结构并理解三次握手与四次挥手
CSDN 2024-10-21 09:37:02 阅读 59
我依旧敢和生活顶撞,
敢在逆境里撒野,
直面生活的污水,
永远乐意为新一轮的月亮和日落欢呼。
--- 央视文案 ---
从零开始理解TCP协议
1 前情提要2 TCP报头的基础结构3 确认应答机制3.1 什么是确认应答3.2 确认序号和ACK标志位
4 超时重传机制5 连接管理机制5.1 三次握手5.2 四次挥手5.3 为什么要三次握手
1 前情提要
前一篇文章我们讲解了UDP协议,UDP协议的结构很简单,维护链表结构的指针,报头指针和数据指针。与之对应UDP协议是不可靠的,没有重传机制,没有传送缓冲区。UDP协议是面向数据报的,接受者会一次性收到一份完整的数据报!
TCP协议就要更加的复杂一些,与之对应的就变得可靠!我们在应用层使用TCP协议进行通信时,不仅要创建套接字,还需要进行客户端connect
建立连接,服务端accept
获取连接;TCP是面向数据流的,还需要进行数据流的解析,判断是否读取到完整的报文结构!再来看下图:
TCP具有两个缓冲区:发送缓冲区和接收缓冲区,因此TCP协议也是支持全双工的!读写的策略本质是拷贝数据到缓冲区中,什么时候发送由操作系统进行考虑!接下来我们就来学习Tcp协议!
2 TCP报头的基础结构
TCP报头结构是这样的:
源/目的端口号:这些字段指示了数据的发送进程与接收进程,即数据包的来源与去向。32位序列号/32位确认号:这两个字段用于管理TCP连接中的数据传输顺序,确保数据的有序交付,具体细节将在后文详述。4位TCP头部长度:表示TCP头部的长度,以32位字(4字节)为单位计算。因此,TCP头部的最大长度为15 * 4 = 60
字节。6位标志位:
URG:指示紧急指针字段是否有效,用于处理紧急数据。ACK:确认号字段是否有效,用于确认已成功接收的数据。PSH:提示接收端的应用程序应立即从TCP缓冲区中读取数据。RST:要求对方重新建立连接,携带RST标志的报文段被称为复位报文段。SYN:用于发起连接请求,携带SYN标志的报文段被称为同步报文段。FIN:通知对方本端即将关闭连接,携带FIN标志的报文段被称为结束报文段。 16位窗口大小:该字段用于流量控制,将在后续部分进行详细说明。16位校验和:由发送端填充,采用CRC校验。接收端对校验和进行验证,若校验失败,则认为数据在传输过程中受损。该校验和覆盖了TCP头部及数据部分。16位紧急指针:该字段标识紧急数据的结束位置,以便接收方能够识别并优先处理紧急数据。40字节头部选项:对于基础理解,这部分可以暂时忽略,但它提供了对TCP协议的扩展功能。
应用层处理好数据放入缓冲区中,TCP协议会对数据进行封装。当TCP协议的接收缓冲区获取到报文时,会进行解包,将数据从报文中分离出来!为了能够将数据进行解包,就需要 4位TCP头部长度。当缓冲区有了完整报文时,会先读取前面的20字节,读取到一系列字段。其中的 4位TCP头部长度【0 ,15】就是代表整个报文的的长度。 4位TCP头部长度是有基本单位的(4字节),0-15代表0-60字节!总长度减去前20字节即可!
其余的字段我们接下来通过对TCP协议的可靠性的分析进行理解!
3 确认应答机制
3.1 什么是确认应答
日常生活中当我们面对面聊天时,我们可以通过对方的表情来判断对方是否听见了自己的表达。当距离拉远时,就很通过表情进行判断,那么你们决定使用听见就大喊“听见了”。通过对方喊出“听见了”,我们就能够确定对方接收到了我们的信息。类似这样机制就确认应答机制。
TCP协议中具有这样的机制!发送方向接收方发送数据时,接收方收到消息就发送一个应答,发送方接收到了就知道自己之前发送的消息对方收到了。而接收方此时还不知道对方有没有接收到,所以发送方这时也向接收方发送一个应答告诉接收方。那么这样就会不断不断的发送应答告诉对方我收到了!这样是不切实际的,生活中总要有一个人说最后一句话!也就是总要一条消息没有被应答!
收到了应答就知道历史数据100%被对方收到了!这就是可靠性!如果应答没有成功传给对方呢?客户端就会认为数报文丢失了,就会重新再发送一次报文(超时重传机制)。
在正常的TCP通信过程中,客户端给服务器一个消息,服务端就给客户端一个应答。客户端不需要对这个应答做处理!这种应答就能说明历史消息已经传达!如果应答在发送过程中出现问题,客户端会自己进行处理,重新发送报文,这是另外一个机制了!
客户端和服务端都采用确认应答机制,来保证两个朝向的通信的可靠性!这个机制是由操作系统完成的,所以使用TCP的代码中并不需要编写。
TCP有两种通信模式:
客户端每次发送数据,都会立刻接收到应答客户端发送多个数据时,一次性返回多个应答。
很明显方案二的模式更加高效!但是这样有一个问题,如果多个应答中有一个应答丢失,如何知道是哪一个应答丢失呢?所以一定要有标识应答的字段 — 32位序列号/32位确认号。确认序号是报文序号+1
。当收到一个应答时,可以保证这个应答之前所以的数据都收到了!应答中只有报头结构,没有数据,确认序号被设置!
有个问题?在这种场景中一个序号不就可以了吗?为什么需要报文序号和确认喜欢?应答中直接返回报文序号不就好了!
这个我们后面讲解
3.2 确认序号和ACK标志位
如果只使用一个序号,那么就是忽略了服务器也可以给客户端发送消息,忽略了TCP通信的全双工性质!服务器发送应答时如果也想给客户端发送信息呢?此时肯定需要两个序号,分别代表发送的数据和发送的应答 ,这叫做捎带应答。这时应答和发送同时使用一个报头。
作为服务器来说,会收到客户端发送的各种各样的报文:建立连接的请求,端口连接的请求,确认报文,正常数据,确认+数据…。所以TCP协议要有处理不同类型报文的能力,即TCP的报文是有不同类别的。如何知道TCP协议的类型呢? 通过6位的标志位为来确定!
只要是确认报文,就要设置ACK
位置为1,填好32位确认序号!
如何理解序号?
首先对于发送缓冲区和接受缓冲区可以理解为一个char类型的大数组。数据从应用层拷贝到缓冲区中,缓冲区内数组的下标就自然可以代表序号了。发送时就可以根据数组下标当做序号进行发送消息!实际情况会复杂一点,可以先这样简单理解。
所以通信的双方,可以互相将数据带上序号来进行发送数据了!
4 超时重传机制
上面谈的都是报文成功发送过去,服务端收到了报文,并做出了应答。但是网络通信并不是百分之百会做到传输的稳定性的,所以可能会发生丢包的情况:报文丢包或应答丢包!此时客户端都不会收到应答,就不会知道历史消息是否成功的被接收了!超时重传机制就是为了解决这种问题。
超时重传机制很好理解:如果主机 A 在一个特定时间间隔内没有收到 B 发来的确认应答, 就会进行重发;
不管我们有没有发送成功数据 ,只要我没有收到来自对方的ACK,我就认为我的发送失败了!需要重新发送!
当应答丢包多次,那么主机 B 会收到很多重复数据。那么 TCP 协议需要能够识别出那些包是重复的包,并且把重复的丢弃掉。这时候我们可以利用前面提到的序列号,就可以很容易做到去重的效果。
注意,发送端发送报文的顺序不一定是服务端接收数据的顺序。这个很好理解。那么服务端接受到消息就很有可能是乱序的,但是数据是有顺序的,所以为了服务的正常运用,需要对数据进行按序到达!所以对多个报文可以根据序号进行排序就可以,如果中间少一部分:比如1000,2000 , 4000 的数据到了,但是3000的没到,那么就先将1000, 2000的传给上层,等待3000再将4000一切交给上层。
超时重传机制的等待间隔是动态的,因为网络状态是不稳定的,不能保证每次的传输速度是一致的,如果采取较小的固定时间间隔,那么再网络较卡的情况下就有可能会造成大量的发送数据!反而降低的传输效率。采取较大的固定时间间隔,那么就更会降低传输效率了!所以采取动态的调节!根据网络情况关联!
为了保证无论在任何环境下都能比较高性能的通信, 因此会动态计算这个最大超时时间:
Linux 中(BSD Unix 和 Windows 也是如此), 超时以 500ms 为一个单位进行控制,每次判定超时重发的超时时间都是 500ms 的整数倍.如果重发一次之后, 仍然得不到应答, 等待 2*500ms 后再进行重传。如果仍然得不到应答, 等待 4*500ms 进行重传. 依次类推, 以指数形式递增.。累计到一定的重传次数, TCP认为网络或者对端主机出现异常, 强制关闭连接
5 连接管理机制
5.1 三次握手
TCP协议是面向连接流的,通信的基础就建立连接!所以建立连接的请求就十分的关键!在正常情况下TCP要经过三次握手建立连接!同样的,建立连接的请求是独特的类型,当标志位SYN设置为1时说明是要建立连接!
之前使用TCP进行编程的时候,我们会将服务端的sockefd设置为listen状态,然后客户端进行Connect创建连接,服务端进行accept接受连接。这其实就是三次握手的过程
初始化与套接字建立:客户端和服务端均完成初始化操作,并建立套接字文件。服务端将套接字设置为监听模式。客户端发起连接:客户端执行Connect函数,向服务端发送SYN请求,并等待服务端的响应。发起三次握手服务端响应请求:服务端通过accept函数监听,接收到客户端的SYN请求后,返回一个SYN应答。客户端处理应答:客户端接收到服务端的SYN应答后,Connect函数执行完毕并返回,同时发送一个应答给服务端。服务端确认连接:服务端接收到客户端的应答后,accept函数执行完毕。完成三次握手连接建立成功:至此,客户端与服务端之间的连接成功建立。
这个过程中发生了三次传输,也就是三次握手!注意:Connect只是发起三次握手;accept不参与三次握手的过程,他只是将OS建立好的连接fd返回给服务端!
三次握手的过程可以使用一个非常幽默的例子进行理解:
小明今天在前往教室的路上,看到了一个十分美丽动人温柔优雅的女生小美,小明一见倾心,马上就去小美面前说:“很高兴认识你,我很喜欢你,可以做我女朋友吗?”
小美见到小明帅气的脸庞,倒三角的身材,个性的穿搭。一下子也迷上了小明,就说:“好啊好啊,什么时候开始?”
小明激动地说:“就现在!”
双方使用了最简洁的语言,完成了男女朋友关系的确定!客户端和服务端三次握手就是这样一个过程!
最后一次的ACK不一定会被服务端接收到,但是只要发出了最后的ACK,就认为三次握手完成了!建立连接的本质就是在堵:堵最后一个ACK对方一定收到了!那不是太草率了?其实不草率,三次握手中就已经验证客户端可以收发消息,服务端可以收发消息,这就足够了!
当最后一次ACK丢包时,客户端并不知道丢包了,于是会正常向服务端发送数据,服务端收到数据,就知道了之前的ACK丢包了, 没有收到!此时会使用RST标志位发送应答,客户端就知道了需要重新进行建立连接!所以进行三次握手时不需要考虑最后一次的ACK是否成功,后续发送数据就会得到验证!RST现实中就是:
5.2 四次挥手
当连接断开时,客户端进行了close时,就会进行四次挥手:
四次挥手的过程使用一个简单例子进行理解
小明与小美相处的不是很愉快,小美看这个男生天天打游戏,不上进,于是想要分手,那么一定是要征求双方同意。需要我向跟你分手,你想和我分手,那么最终两个人就再无瓜葛!
所以双方都会发送一次FIN请求!进行四次挥手
客户端发送FIN请求本质:是告诉服务端客户端给你的数据已经发完了,没有数据再进行传输了(注意正常的ACK还是会发送的)!我断开连接了!客户端可以调用shutdown
接口关闭写端,那么还可以继续读取服务端发送的数据!服务端发送FIN请求:是告诉客户端给你的数据我都发完了,没有数据再写了!
四次挥手使用最小的通信成本,建立了断开连接的共识!双方都不和对方通信了!并且也知道对方不再和我进行通信了!
这里可以解决之前使用http的一个问题:当我们使用ctrl+c
退出客户端程序时,再次启动相同端口号时会出现bind error,因为客户端退出来并不代表连接退出了,处理连接进行四次挥手是需要一定时间的,没有完成之前,连接是一直存在的!
5.3 为什么要三次握手
在内核数据结构中,是需要一个管理连接的数据结构的,所以维护管理连接时是由成本的!那么在三次握手时会建立连接结构体,四次挥手时会进行delete!
如果一次挥手就能建立连接,当客户端发生SYN洪水时(伪造大量SYN请求进行创建连接),服务端就会创建出一堆的连接数据结构,这样就挂满了大量无用的连接,会浪费服务器的资源,最终可能会造成很大影响!两次回收同理!
可是三次挥手这样看不是也怕SYN洪水吗,其实这是一个机制的问题,三次挥手要求客户端发回ACK才会建立连接!那么客户端以后占用一定的资源!所以客户端就无法伪造大量的SYN请求进行SYN洪水了!当然三次挥手也有其他的机制来避免SYN洪水。一次两次挥手则是存在明显的BUG,会被利用!
三次挥手有以下优点:
验证全双工:三次挥手可以验证网络的连通性,因为三次回收中客户端和服务端都进行了收发数据!确认双方意愿:三次挥手可以保证双方都想要建立连接!可以达成一个共识!成本低:四次挥手和五次挥手等不过也是为了完成前两个目的!而三次握手时成本最低的!
本文标签
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。