目录
一.TCP协议介绍
1.1简介
1.2TCP协议格式
32位序号/32位确认号
标志位
1.3tcp的发送和接收缓冲区
1.3.1介绍
1.3.2窗口大小
1.4超时重传
二.连接管理
2.1三次握手
2.2三次握手的状态变化
2.3为什么是三次握手?
2.4套接字与三次握手关系
2.5四次挥手
2.6四次挥手的状态变化
一.TCP协议介绍
1.1简介
TCP全称为 " 传输控制协议 (Transmission Control Protocol"),其可靠性非常强,其应用非常广泛,例如:HTTPS,FTP、SSH。
1.2TCP协议格式
各个字段的简单解释:
源/目的端口号: 表示数据是从哪个进程来 , 到哪个进程去。
32位序号/32位确认号 :序号标定一个报文的编号;确认号标定 该确定号之前的报文全部收到 ,保证双向的全双工的确认应答机制,是TCP保证可靠性的重要字段。(后面详细介绍)
4位TCP报头长度(4位首部长度 ): 表示该 TCP头部有多少个32位bit(单位是4字节);此值是4bit位,则取值范围是0000~1111,即0~15,因为单位是4字节,所以TCP头部最大长度是15 * 4 = 60,而该报头标准长度有20,所以 4位首部长度 最少是 20 / 4字节 = 5=0101.即 4位首部长度 范围是0101~1111,20~60字节。(选项字段)则选项的大小的数据范围是0~40字节
16位校验和 :由发送端填充,采用CRC校验,用来验证数据在传输过程有没有问题。
16位窗口大小:应答本质就是要包含TCP报头,tcp报头可以有保存发送方接受能力的属性字段,叫做 16位窗口大小(后面详细解释)
16 位紧急指针 : 标识哪部分数据是紧急数据 ,标识紧急数据在报文中的偏移量,需要配合标志字段当中的URG字段统一使用。
TCP报头当中的6位标志位:
URG:紧急指针是否有效。
ACK:确认序号是否有效。
PSH:提示接收端应用程序立刻将TCP接收缓冲区当中的数据读走。
RST:表示要求对方重新建立连接。我们把携带RST标识的报文称为复位报文段。
SYN:表示请求与对方建立连接。我们把携带SYN标识的报文称为同步报文段。
FIN:通知对方,本端要关闭了。我们把携带FIN标识的报文称为结束报文段
TCP如何将报头与有效载荷进行分离,并向上交付
用先读取前20字节,读取其中的 4位首部长度, 4位首部长度*4 - 20 = 选项的大小 ,其余的就是有效载荷,再根据目的端口号,将数据上交给对应的(应用层)进程。
32位序号/32位确认号
数据在传输的过程中,要确保数据可以安全,有序的到达。当一方收到消息后,还需要向对方发送响应,对方才能知道数据成功发送了。但严格意义上来说,互联网通信当中是不存在百分之百的可靠性的,因为双方通信时总有最新的一条消息得不到响应,但只需保证重要的数据能成功发送即可。对端如果没有收到对应的响应数据,会判定上一次发送的报文丢失了,此时对端可以将上一次发送的数据进行重传。
32位序号:
当双发进行通信,每次发送一次数据都要等待对方应答的话,那么效率是十分低的。TCP是允许一方连续发送多次数据的,但在连续发送多个报文时,由于各个报文在进行网络传输时选择的路径可能是不一样的,因此这些报文到达对端主机的先后顺序也就可能和发送报文的顺序是不同的,32位序号可以解决这些问题。
TCP将发送出去的每个字节数据都进行了编号,这个编号叫做序列号。
比如:发送的一方连续发送3次报文,那么这三个TCP报文当中的32位序号填的就是发送数据中首个字节的序列,此时接收端收到了这三个TCP报文后,就可以根据TCP报头当中的32位序列号对这三个报文进行顺序重排(该动作在传输层进行),重排后将其放到TCP的接收缓冲区当中,此时接收端这里报文的顺序就和发送端发送报文的顺序是一样的了。
32位确认序号:
主要用来对另一方发送数据后,自己对其做出响应,表示自己已经收到了数据。
例如:当发送方连续发送了三个报文,该报文的大小都是1000字节的话,那么这三个报文所携带的32位序号可以认为是1,1001,2001,那么接受方发出的响应报文中的确认序号就是3001,表示下次向我发送数据时应该从序列号为3001的字节数据开始进行发送。若中间那个报文丢了,接受方发出的响应报文的确认序号就是1001,表示下次发送的数据要从1001开始,发送方就会从第二个报文开始重新传输。
为什么要用两套序号机制?
TCP是全双工的,双方可以同时给对方发送消息。
- 双方发出的报文当中,不仅需要填充32位序号来表明自己当前发送数据的序号。
- 还需要填充32位确认序号,对对方上一次发送的数据进行确认,告诉对方下一次应该从哪一字节序号开始进行发送。
标志位
SYN: 请求建立连接 ;,我们把携带SYN标识的称为同步报文段。
报文当中的SYN被设置为1,表明该报文是一个连接建立的请求报文。
只有在连接建立阶段,SYN才被设置,正常通信时SYN不会被设置。
ACK:
报文当中的ACK被设置为1,表明该报文可以对收到的报文进行确认。
多数情况下ACK都会被设置,因为都要进行应答。
FIN:
报文当中的FIN被设置为1,表明该报文是一个连接断开的请求报文。只有在断开连接阶段,FIN才被设置,正常通信时FIN不会被设置。
URG:
当URG标志位被设置为1时,需要通过TCP报头当中的16位紧急指针来找到紧急数据,紧急指针只有一个,它只能标识数据段中的一个位置,因此紧急数据只能发送一个字节。
PSH:
文当中的PSH被设置为1,是在告诉对方尽快将你的接收缓冲区当中的数据交付给上层。
补充:使用read/recv从缓冲区当中读取数据时,其实接收缓冲区和发送缓冲区都有一个水位线的概念,当数据接近这个水平线才会进行读取,减少不必要的消耗。
RST:
报文当中的RST被设置为1,表示需要让对方重新建立连接。
在通信双方在连接未建立好的情况下,一方向另一方发数据,此时另一方发送的响应报文当中的RST标志位就会被置1,表示要求对方重新建立连接。
在双方建立好连接进行正常通信时,如果通信中途发现之前建立好的连接出现了异常也会要求重新建立连接。
1.3tcp的发送和接收缓冲区
1.3.1介绍
TCP是具有发送与接受缓冲区的,我们用write/send函数把数据拷贝到内核缓冲区后,tcp会在合适时候进行发送。我们只是将数据拷贝到对应的缓冲区内。
内核缓冲区的数据什么时候发,发多少,出错了怎么办,要不要添加提高效率的策略——都是由OS内的TCP自主决定的,所以TCP叫做传输控制协议!
意义:
- 数据在传输过程中可能出现错误,那么就要进行重传,因此TCP必须提供一个发送缓冲区来暂时保存发送出去的数据,以免需要进行数据重传。只有当发出去的数据被对端可靠的收到后,发送缓冲区中的这部分数据才可以被覆盖掉。
- 接收端处理数据的速度是有限的,为了保证没来得及处理的数据不会被迫丢弃,因此TCP必须提供一个接收缓冲区来暂时保存未被处理的数据
1.3.2窗口大小
在数据进行传输的过程中,可能一方发送的数据过大,导致另一方不能接受那么多的数据。
16位窗口:16位窗口大小当中填的是自身接收缓冲区中剩余空间的大小,也就是当前主机接收数据的能力。接收端在对发送端发来的数据进行响应时,就可以通过16位窗口大小告知发送端自己当前接收缓冲区剩余空间的大小,此时发送端就可以根据这个窗口大小字段来调整自己发送数据的速度。
双方的发送缓冲区是在得知对方接收缓冲区接收能力的条件下进行通信的,根据对方每次应答中不断更新的窗口大小定期向对方发送合适的数据大小,这种策略就叫做流量控制策略。流量控制策略也是双向的。
在三次握手期间不仅仅是建立链接,还有就是交换信息 ,其中信息就包含了向对端告知自己的接收能力。
1.4超时重传
概念:当一方发送数据后,在一定时间内没有收到对方的响应,就要重新对该数据进行发送。
注意:TCP保证双方通信的可靠性,一部分是通过TCP的协议报头体现出来的,还有一部分是通过实现TCP的代码逻辑体现出来的。
这里可能是发送的数据丢了或者是对方发出的响应丢了,都要进行重传。
补充:
若是因为没有收到对方的响应而进行重传,那么接受方会收到两个相同的报文,但可以通过32位序列号来进行去重。
由于缓冲区的存在,当数据发送后,不会立即将缓冲区的数据清空,而是当收到响应后再清空。
超时的时间如何确定?
最理想的情况下, 找到一个最小的时间 , 保证 " 确认应答一定能在这个时间内返回 ", 但是这个时间的长短, 随着网络环境的不同 , 是有差异的 。TCP 为了保证无论在任何环境下都能比较高性能的通信 , 因此会动态计算这个最大超时时间 .
Linux中 (BSD Unix 和 Windows 也是如此 ), 超时以 500ms 为一个单位进行控制 , 每次判定超时重发的超时 时间都是500ms 的整数倍 .
如果重发一次之后, 仍然得不到应答 , 等待 2*500ms 后再进行重传 .
如果仍然得不到应答, 等待 4*500ms 进行重传 . 依次类推 , 以指数形式递增 。当重传超过一定次数,TCP 认为网络或者对端主机出现异常, 强制关闭连接。
二.连接管理
TCP是面向连接的,如何去理解?
我们在进行TCP通信之前需要先建立连接,就是因为TCP的各种可靠性保证都是基于连接的,要保证传输数据的可靠性的前提就是先建立好连接
建立连接:实际就是在操作系统中用该结构体定义一个结构体变量,然后填充连接的各种属性字段,最后将其插入到管理连接的数据结构当中即可。
断开连接:实际就是将某个连接从管理连接的数据结构当中删除,释放该连接曾经占用的各种资源。
2.1三次握手
二者在进行通信之前,要先建立连接,而建立连接的这个过程,叫做三次握手。
具体解释:
第一次握手:客户端向服务器发送报文当中的SYN位被设置为1,表示请求与服务器建立连接。
第二次握手:服务器在收到客户端发来的连接请求报文后,便向向客户端发起连接建立请求并且对客户端发来的连接请求进行响应,此时服务器向客户端发送的报文当中的SYN位和ACK位都被设置为1。
第三次握手:客户端收到服务器发来的报文后,得知服务器收到了自己发送的连接建立请求,并请求和自己建立连接,最后客户端再向服务器发来的报文进行响应。
2.2三次握手的状态变化
刚开始时,客户端与服务器都处于CLOSED状态。后服务器转化为LISTEN状态,客户端就可以向服务器发起三次握手了,当客户端发起第一次握手后,客户端状态变为SYN_SENT状态。
处于LISTEN状态的服务器收到客户端的连接请求后,会将该连接放入内核等待队列中,并向客户端发起第二次握手,此时服务器的状态变为SYN_RCVD。处于SYN_RCVD状态的客户端收到服务器的第二次握手请求后,客户端便建立好连接了,变为ESTABLISHED状态。
而服务器收到客户端发来的最后一次握手后,连接也建立成功,此时服务器的状态也变成ESTABLISHED。
2.3为什么是三次握手?
首先要明白的是,在建立连接的过程中,收到数据的一方都要向另一方做出响应,这也就意味着可能在某次握手的过程中出现丢包等情况,导致一方未收到数据或者响应等。在三次握手的过程中,最后一次握手的可靠性是不能保证的。
理由:
三次握手是验证双方通信信道的最小次数
在客户端一方来看,当它收到服务器发来第二次握手时(SYN+ACK),说明自己发出的第一次握手被对方可靠的收到了,并且证明自己发送,服务器可以接受。同时当自己收到服务器发来的第二次握手时,也就证明服务器能发以及自己能收,此时就证明自己和服务器都是能发能收的。
在服务器看来,当它收到客户端发来第一次握手时,证明客户端能发以及自己能收,而当它收到客户端发来的第三次握手时,说明自己发出的第二次握手被对方可靠的收到了,也就证明自己能发以及客户端能收,此时就证明自己和客户端都是能发能收的。
三次奇数次握手可转移风险
只有当服务器收到客户端发来的第三次握手后,服务器才知道双方通信信道是连通的,此时在服务器端才会建立对应的连接。若第三次握手服务器未收到,那么服务器端就不会建立对应的连接,而在客户端就需要短暂的维护一个异常的连接。而维护连接是需要时间成本和空间成本,这样可避免影响到服务器的资源。
2.4套接字与三次握手关系
在服务器处于LISTENED状态时,客户端就可以向服务器发送连接请求了,这里用到的是connect函数。
注意:connect函数不参与底层的三次握手,connect函数的作用只是发起三次握手。当connect函数返回时,要么是底层已经成功完成了三次握手连接建立成功,要么是底层三次握手失败。
如果服务器端与客户端成功完成了三次握手,此时在服务器端就会建立一个连接,但这个连接在内核的等待队列当中,此时服务器端只需要通过调用accept函数将这个建立好的连接获取上来。
后面二者就可以通过对应的套接字,通过read与write函数进行通信了。
2.5四次挥手
当二者通信结束后,要断开这种连接,这个过程叫做四次挥手。
具体解释:
第一次挥手:客户端向服务器发送报文当中的FIN位设置为1,表示请求与服务器断开连接。
第二次挥手:服务器收到客户端发来的断开连接请求后对其进行响应。
第三次挥手:服务器收到客户端断开连接的请求,且已经没有数据需要发送给客户端的时候,服务器就会向客户端发起断开连接请求。
第四次挥手:客户端收到服务器发来的断开连接请求后对其进行响应。
2.6四次挥手的状态变化
二者建立好连接请求时,它们的状态都是ESTABLISHED。
客户端为了与服务器断开连接,主动向服务器发起连接断开请求(FIN),此时客户端的状态变为FIN_WAIT_1。
服务器收到客户端发来的连接断开请求后对其进行响应,此时服务器的状态变为CLOSE_WAIT。
当服务器没有数据需要发送给客户端的时,那么此时服务器会向客户端发起断开连接请求,等待最后一个ACK到来,此时服务器的状态变为LASE_ACK。
客户端收到服务器发来的第三次挥手后,会向服务器发送最后一个响应报文,此时客户端进入TIME_WAIT状态。
当服务器收到客户端发来的最后一个响应报文时,服务器会关闭连接,转变变为CLOSED状态。
而客户端则会等待一个2MSL(Maximum Segment Lifetime,报文最大生存时间)才会进入CLOSED状态。
套接字与四次挥手关系:
这里也就是双方完成通信后,要调用close函数。一个close函数对应两次挥手。
CLOSE_WAIT:当客户端调用close函数,而服务端没有调用该函数的话,此时服务器就会进入CLOSE_WAIT状态,而客户端则会进入到FIN_WAIT_2状态。此时服务器依旧要花资源去维护这段连接,这其实也是一种内存泄漏的问题。
TIME_WAIT:说下这种状态的必要性,二者再断开连接的时候,发送的数据都可能出现丢包,对方数据未被收到等情况。但可以进行超时重传,但客户端最后发送ACK应答时,就马上进入CLOSED状态的话,若客户端未收到这条ACK应答,这对服务器便不好,可能会长间处于LAST_ACK状态。若客户端处于TIME_WAIT状态,服务器未收到最后一条ACK应答时,客户端还可以能收到服务器重发的报文然后进行响应。所以这个状态可以较大概率保证最后一个ACK被服务器收到。
这个时间不能太长也不能太短,
CP协议规定,主动关闭连接的一方在四次挥手后要处于TIME_WAIT状态,等待两个MSL(Maximum Segment Lifetime,报文最大生存时间)的时间才能进入CLOSED状态。
MSL在RFC1122中规定为两分钟,但是各个操作系统的实现不同,比如在Centos7上默认配置的值是60s。我们可以通过cat /proc/sys/net/ipv4/tcp_fin_timeout命令来查看MSL的值
TIME_WAIT的等待时长设置为两个MSL的原因:
- MSL是TCP报文的最大生存时间,因此TIME_WAIT状态持续存在2MSL的话,就能保证在两个传输方向上的尚未被接收或迟到的报文段都已经消失。
- 同时也是在理论上保证最后一个报文可靠到达的时间。