一、协议格式
16位源端端口&16位对端端口:
描述通信两端。
32位序号:实现包序管理,完成数据有序交付
告诉接收端,这条数据在整体数据中的排序;接收端根据序号进行排序,
32位确认序号:确认应答&快速重传
向发送端进行回复确认,代表确认序号以前的数据都已经收到。
4位头部长度:
以4字节为单位描述tcp报文头部长度(最大60字节,最小20字节)。
6位保留:
暂未使用。
6位标志位:FIN, SYN, RST, PUSH, ACK, URG
1)FIN断开标志:带有该标志位的数据包,用于结束TCP会话,但对应端口仍处于开放状态,准备接收后续数据。
2)SYN同步标志:仅在三次握手建立连接时有效。
3)RST复位标志:表示连接复位请求,用于复位那些产生错误的连接,也被用来拒绝错误和非法的数据包。
4)PUSH标志:在数据包到达接收端后,立即传送给应用程序,而不是在缓冲区中排队。
5)ACK确认标志:用于确认数据包的成功接收。
6)URG紧急指针标志:用于将输入数据标识为“紧急”。
16位窗口大小:用于实现滑动窗口机制,进行流量控制
16位校验和:二进制反码求和算法,校验数据一致性
16位紧急指针:指向紧急优先带外数据的结束位置
0~40字节选项数据(可无):通常是一些额外的协商信息
二、协议特性
1.面向连接
面向连接:通信前,需要先建立连接,以确保双方都是在线、具有数据收发能力。
连接管理&状态管理:三次握手建立连接,四次挥手断开连接
1.1三次握手建立连接
建立连接:通信前,先建立连接,确保双方具有数据收发能力。
连接管理:
第一次握手:
客户端向服务端发起连接请求,SYN置1,并告诉服务端起始序号seq(这里假设是1)
第二次握手:
服务端回复客户端ACK&SYN,并告诉自己的起始序号seq,确认应答ack为客户端起始序号seq加一。
第三次握手:
客户端向服务端发送确认回复ACK,并告诉自己的起始序号seq和确认应答ack,ack为服务端的起始序号seq加一。
状态管理:
①客户端初始处于CLOSED状态,在发送连接请求后,处于SYN_SENT状态。
②服务端初始处于CLOSED状态(什么都不能做),进入一直处于LISTEN状态进行监听。
③服务端收到连接请求后,会创建新建连接,新建连接此时处于SYN_RCVD状态。
④客户端收到服务端的确认应答后,处于ESTABLISHED状态,表示进入就绪状态,接下来可以进行数据通信。
⑤服务端收到客户端的确认应答后,处于ESTABLISHED状态,表示进入就绪状态,接下来可以进行数据通信。
1.2四次挥手断开连接
断开连接:通信结束,需要一个断开连接的过程,以避免发生意外。
注意:FIN请求,只能表示不再给对方发送数据,不代表不再接收数据。
连接管理:
第一次挥手:主动关闭方向被动关闭方发送FINISH请求,即FIN置1。
第二次挥手:被动关闭方收到FINISH请求后,首先会进行一个确认回复ACK,(第三次挥手:)随后也会向主动关闭方发送FINISH请求。
第四次挥手:主动关闭方收到被动关闭方的确认应答ACK和FIN请求后,向被动关闭方发送确认应答ACK。
状态管理:
①主动关闭方(调用close()或shutdown(SHUT_WR)后)发送FIN请求后,处于FIN_WAIT1状态。
②被动关闭方收到主动关闭方的FIN请求后,进行确认应答发送ACK,处于CLOSE_WAIT状态,上层调用close()或shutdown(SHUT_WR)后,则会向主动关闭方发送FIN请求,处于LAST_ACK状态,等待最后一次请求回复。
③主动关闭方收到ACK应答后,处于FIN_WAIT2状态,表示自己发送的FIN包已经被收到,等待对方的FIN包。
④主动关闭方收到被动关闭方的FIN包后,向被动关闭方发送ACK应答,随后处于TIME_WAIT状态。
⑤被动关闭方收到最后的ACK应答后,处于CLOSED状态,释放资源。
解释:
CLOSE_WAIT状态:等待关闭状态。对方发送了FIN包,表示已经不再给自己发送数据,上层如果此时还在继续recv,则会读完缓冲区的数据后,不再阻塞,而是直接返回0。这种情况下,就是等待上层针对这种情况的处理。
TIME_WAIT状态:等待两个MSL之后,进入CLOSED状态,释放资源。
为什么握手是三次,挥手是四次?
(1)为什么是三次握手?
因为两次不安全,四次没必要:
1.建立连接,是双方都要确保对方具有数据收发能力,因此都需要进行SYN。
2.两次不安全:
①SYN有可能会延迟到达,与重发的SYN形成冲突(三次握手有状态要求)。
②防止恶意攻击,比如客户端发送SYN后直接退出,浪费服务端资源。
3.四次没必要:
没必要发送两次报文,在一次回复中将对应比特位置1即可
(2)为什么是四次挥手?
1.FIN请求只能表示主动关闭方不再发送数据,不代表不再接收数据,因此被动关闭方收到FIN请求并进行确认后,有可能还会继续发送数据,等待上层不再发送数据了,也要关闭套接字了才会向主动关闭方发送FIN包,因此挥手需要四次,没有合并为三次。
三次握手失败后,两端如何处理?
①第一次握手失败:客户端发送的第一次握手请求丢失,客户端会重发SYN请求。
②第二次握手失败:服务端回复的ACK+SYN丢失,客户端会重传SYN;服务端在等待对方的ACK回复超时后,会给客户端发送一个RST报文,然后释放资源。
③第三次握手失败:服务器等待超时,则发送RST,然后释放资源。
一台主机上出现了大量CLOSE_WAIT状态连接的原因?
1.首先要知道,只有收到了FIN请求,并且进行了ACK确认回复的连接才会进入CLOSE_WAIT状态。
2.一直处于CLOSE_WAIT状态而没有进入下一步状态是因为,上层没有进行关闭套接字操作,也就是没有发送FIN包,所以无法进入下一步状态。
3.因此原因就是,代码中没有针对断开连接的套接字进行关闭处理。
TIME_WAIT状态的作用?为什么不直接关闭套接字?
1.首先要知道,TIME_WAIT状态是主动关闭方在发送最后一次ACK后进入的状态。
2.如果没有TIME_WAIT状态,主动关闭方发送最后一次ACK后直接关闭套接字,有可能新启动的套接字会使用与之前相同的地址信息。
3.而上一次连接最后一次ACK应答有可能会丢失,一旦丢失,被动关闭方就会重传FIN包。
4.就会导致上一次通信因为最后一次ACK丢失,而遗留的问题(重传的FIN包)对新连接造成影响。
5.因此,不能直接释放资源,需要等待两个MSL时间,针对有可能存在的FIN重传进行处理,保证上一次通信的所有数据都消失在网络中。
MSL:报文最大生命周期,即一个报文在网络中最大能存在的时间,默认60s(大致推算)。
一台主机上出现了大量TIME_WAIT状态,什么原因?怎么解决?
原因:
1.首先要知道,TIME_WAIT状态是主动关闭方,发送最后一次ACK应答后进入的状态,等待一段时间,是为了处理有可能因为FIN丢失导致的FIN重传的处理。
2.因此,一台主机出现大量TIME_WAIT状态连接,是因为主机上大量的主动关闭连接,常见于爬虫服务器。
解决方法:
1.TIME_WAIT等待时间是可配置的,可以将时间设置的更短。
2.利用套接字选项,setsockopt()设置支持地址重用。这样一个套接字在进入TIME_WAIT状态后,其他新建连接也可以使用该套接字的地址信息。
1.3TCP连接管理中的保活机制
问题引入:
tcp通信中,若客户端和服务端通信频率不高,中间突然网断开,没有进行四次挥手的机会,可能需要很久才能发现。
保活机制:
在通信中,客户端与服务端若长时间无通信(默认7200s),则tcp服务器会自动向客户端发送保活探测心跳包,要求对方进行响应(默认每隔75s),若连续多次都没有收到响应(默认9次),则认为连接断开。
注意:以上时间都是可配置的,甚至可以使用套接字选项设置。(根据需求,配置自己认为合适的时间)
连接断开在程序上层的体现:
1.recv接收完数据后继续接收时,不再阻塞,而是直接返回0.
2.send在发送数据的时候,若连接断开会触发异常(SIGPIPE信号),导致程序退出。
2.可靠传输
可靠传输:确保数据安全有序的到达对端
TCP可靠传输所包含:
可靠传输:面向连接,确认应答,超时重传,序号,校验和。
避免丢包重传:滑动窗口机制(用于实现流量控制),拥塞控制。
性能提升:延迟发送,延迟应答,捎带应答,快速重传。
2.1可靠传输的实现
通过很多特殊机制实现:
1.面向连接:确保通信双方都具有数据收发能力。
2.丢包检测机制(确认应答机制):接收方需要针对收到的每一条数据进行确认回复。
3.丢包重传机制(超时重传机制):等待确认回复超时则重传。
4.协议字段中的序号字段:进行包序管理,保证有序交付。
5.校验和字段:校验数据一致性,不一致则丢弃,要求重传。
2.2没必要的性能损失
1.比如确认应答丢失导致的重传,没必要。
1.比如发送方发送数据过多,超过了接收方的上限,导致数据溢出丢包。
3.比如因为网络状态拥塞时,发送的数据过多而导致的丢包。
2.3没必要的性能损失处理机制
1.滑动窗口机制:通过协议字段中的窗口大小字段实现,进行流量控制
接收方每次收到数据后都会进行确认回复,在确认回复的时候就会计算自己的接收缓冲区中剩余空间大小,将这个数字放入窗口大小字段中回复给对方。
发送方根据确认回复的窗口大小字段,确定自己最多能发送多少数据。
这就是tcp的流量控制:避免因为发送方发送的数据过多,导致接收方因为接收缓冲区溢出而导致丢包。
注意:当接收方回复的窗口大小为0时,表示接收方的数据缓冲区已满,发送方并不是就不发数据了,而是不发送实际数据,每隔一段时间发送一个探测包,去确认是否有空间了,直到回复有空间了后,再继续发送实际数据。
2.4滑动窗口机制实现
发送窗口:
发送方维护了一个发送窗口(不大于对方回复的窗口大小字段值),在窗口内的数据是可发送数据,通过两个序号进行管理实现:
1.窗口后沿:数据发送的起始序号,收到确认回复后向后移动。
2.窗口前沿:数据发送的结束序号,随对方回复的窗口大小而定。
接收窗口:
接收方维护了一个接收窗口(不大于接收缓冲区剩余大小):
1.窗口后沿:窗口区域的起始位置,要接收的数据的起始序号,收到了对应起始序号的数据后,向后移动。
2.窗口前沿:窗口区域结束位置。
可能导致窗口前沿变大的原因:缓冲区取出数据,剩余空间变大,或人为设置。
导致窗口前沿变小的原因:人为设置。
滑动窗口机制中的三种特殊协议:
1.停等协议:发送一条数据后,必须等到确认回复后,才会发送下一条数据。
2.回退n步协议:发送一条数据后,若出现丢包,则将丢包的数据及其往后的数据都进行重传。(一般针对网络较差的情况)
3.选择重传协议:管线化传输,哪条数据出现丢包,就只重传哪条数据。(一般针对网络较好的情况)
扩展了解:
MSS:最大数据段大小,表示在tcp传输中,应用层数据最大大小。
在三次握手阶段,tcp通信双方就会进行协商MSS大小,选择双方较小的一个值作为实际传输的MSS。
MTU:最大传输单元大小,是链路层限制的最大数据帧大小。
tcp在使用send发送数据的时候,是把数据放到了发送缓冲区中,系统选择合适的时候,从发送缓冲区中取出合适长度大小(不大于MSS)的数据,封装报头进行发送。
2.5快速重传协议
引入:
在连续传输过程中,接收方会按序进行响应,如果某个应答丢失,后面数据的确认应答也能表示前面的数据收到了(基于确认序号的作用:告诉对方这个确认序号之前的所有数据都已经收到了),避免因为确认应答丢失而导致重传。
在连续传输过程中,如果某个数据包丢失,也就是接收窗口起始序号位置的数据没有收到,而是收到了后面的数据,这时候就可以认为前面的数据有可能丢失了,这时候就会启动快速重传协议。
快速重传:
收到了后面的数据,而没有收到前面的数据,则每收到一条数据就会确认回复前面没有收到的哪条数据的起始序号,表示让对方对这条数据进行重传。
但是发送方,收到前面数据的确认序号的应答,并不会立即对这条数据进行重传,而是连续收到三次后,才会重传。(避免前面的数据只是因为网络延迟到达,而不是丢失的情况)
2.6拥塞机制
避免因为网络拥塞而导致大量的丢包
1. 发送方会维护一个拥塞窗口,起始为1,阈值初始为16。
2. 拥塞窗口以指数级进行增长1,2,4,8……16;达到阈值后,以线性增长17,18,19……
1)增长过程中,一旦出现超时重传:
阈值变为当前窗口的一半大小,窗口从1重置。
2)增长过程中,一旦出现快速重传请求:
拥塞窗口变为当前窗口的一半。
然后阈值变为拥塞窗口+3。
3. 拥塞窗口在达到阈值前,以指数级增长;到达阈值后,以线性增长(每次加一)。
总结:
拥塞控制就是以慢启动,快速增长的形式实现网络状况探测,避免因为网络拥塞而导致大量丢包情况。
2.7其他的一些提升性能的方式
1.延迟发送机制:数据先放到缓冲区,延迟一段时间再发送(减少IO次数)。
2.延迟应答机制:收到数据后,延迟一段时间在确认回复(维持吞吐量)。
3.捎带应答机制:将确认回复与即将要发送的数据一起合并发送。
4.快速重传协议:见上。(减少超时等待时间)
3.提供字节流传输服务
3.1流式传输
是一种有序的、安全的、可靠的,基于连接的字节流传输。
3.2字节流传输
字节流传输:发送或接收最小单位为字节的传输方式。
(1)发送方将要发送的数据放到发送缓冲区中,系统截取合适大小的数据进行传输;
(2)接收方将接收到的数据,放到接收缓冲区中,上层recv需要多少,给多少。
优点:比较灵活,没有发送或接收长度的限制。
缺点:存在粘包问题。
3.3编程影响——粘包问题
粘包问题:
将多条数据当做一条数据进行处理(两个或多个包粘在一起当做一个处理)。
产生原因:
tcp对上层数据的边界不做特殊处理,没有数据之间的边界管理。
解决方法:
程序员在应用层进行数据之间的边界管理:三种方式
(1)数据之间以特殊字符作为间隔
优点:思想简单;
缺点:数据中不能含有特殊字符,需要编码。
(2)数据定长
优点:思想简单;
缺点:数据小了,浪费性能;数据大了不够。
(3)采用TLV数据格式:type, length, value
发送的每条数据都有一个固定头部:type,length
思想就是先获取固定头部,然后根据头部中的length确定数据长度。
注意:http就是采用的TLV数据格式。
什么是TCP粘包?怎么产生的?怎么解决?
(1)粘包就是将多条数据当做了一条数据进行处理;
(2) 原因是tcp对上层数据的边界没有特殊处理,也没有边界管理;
(3) 解决方法就是程序员自己实现在应用层进行数据之间的边界管理,其中有三种方式:(详见粘包问题的解决方法)
udp粘包怎么解决?
首先明确:udp不会粘包
udp有固定的8个字节头部长度,在头部里面描述了数据报长度,因此能够明确的描述数据的边界,在传输的时候都是整条交付,所有不会产生粘包问题。
tcp和udp有什么区别?
udp:无连接,不可靠,面向数据报。
tcp:面向连接,可靠传输,面向字节流传输。
可扩充详谈
tcp怎么实现可靠传输?
见上,可靠传输的实现。
udp怎么实现可靠传输?
通过程序员自己在应用层实现确认应答机制、超时重传机制,来保证数据安全到达对端,并且对传输的数据包进行包序管理,实现数据的有序交付,确保数据能够有序到达对端。同理,还可以自己实现滑动窗口机制、拥塞控制,避免丢包重传,以及一些提升性能的机制。