TCP :传输控制协议
1.协议格式:
-
16位源端端口-16位对端端口:描述通信两端
-
32位序号-32位确认序号:用于实现包序管理。
-
4位报头长度:描述tcp报头长度。4位表示最大数字15;以四字节为单位,所以TCp报头最小长度20字节,最大为15 * 4 = 60字节。
-
6位保留
-
6位标志位:
URG: 紧急指针是否有效 ACK: 确认应答 PSH: 提示接收端应用程序立刻从TCP缓冲区把数据读走 RST: 对方要求重新建立连接; 我们把携带RST标识的称为复位报文段 SYN: 请求建立连接; 我们把携带SYN标识的称为同步报文段 FIN: 通知对方, 本端要关闭了, 我们称携带FIN标识的为结束报文段
-
16位窗口大小:用于实现滑动窗口机制–进行发送数据的流量控制。防止缓冲区溢出丢包。
-
16为校验和:校验数据一致性。
-
16位紧急指针:发送的带外数据的位置
-
0-40字节选项数据:存储一些可能需要的额外的信息。Mss…
-
应用数据
2.协议特性:
2.1 面向链接
面向链接:通信双方建立连接之后才能进行通信,—>确保通信双方都据有数据收发能力
TCP的链接管理:三次握手建立链接,四次挥手断开链接。
主要是为了确认通信双方都在线有数据收发能力。
2.1.1三次握手建立连接
客户端向服务端请求建立连接:
2.1.1四次挥手断开连接
相关问题和知识点:
1. 握手为啥三次,挥手是四次?
三次握收:双发都需要确定对方是否具有数据收发能力。两次不安全—只能确定服务端在线,客户端可能发送完SYN请求后就下线了。四次没必要—SYN和ACK都是报文中的标志位,分开发送没太大意义,直接将这两个标志位都置为1同时发送即可。
四次挥手:双方上层只有在不会发送数据的情况下才会发送FIN(FIN表示上层不再发送数据,但下层还能ACK确认回复)。收到FIN请求只能表示对方上层不再send发送数据,不代表对方不能再收数据了。因此有可能接收FIN请求的一方还会继续给对方发送数据,只有在上层调用了close或者shutdown关闭写才会主动给关闭方发FIN(不再发数据了).所以被动关闭方的FIN和ACK默认不一起发送。
2. 三次握手失败两端是如何处理的?
握手失败情况:
1.SYN请求丢失,客户端没得到确认回复,隔一段时间会重新发送SYN请求。多次发送失败会导致请求超时,连接失败。
2. 服务端发送的ACK+SYN信息丢失。客户端没收到SYN导致服务器没收到相应的的ACK回复,新建的套接字会重新发送ACK+SYN请求。若服务端发送ACK+SYN请求超时(服务端可能会觉得这是恶意攻击请求,只发起连接不回复,占用资源),给客户端发送RST重置连接报文,释放新建套接字的资源。
3. 客户端最后发送的ACK丢了。服务端等待超时,服务端发送RST,释放资源,从新建立连接。
3. SYN泛洪攻击是怎么回事?
黑客伪造ip不断向服务端发送SYN,但是不进行ACK回复,服务端新创建套接字会不断占用资源,直至枯竭崩溃。所以listen(sockfd,backlog)接口中有backlog参数–新建连接队列–队列满了就不会再新建套接字。处理方法:防火墙–同一ip频繁发送数据则拉黑名单。
4. 一台主机上出现了大量CLOSE_WAIT状态链接是什么原因?
CLOSE_WAIT状态是被动关闭方收到FIN请求并进行ACK回复之后进入到的状态。一旦自己发送了FIN(close操作后就会发送FIN)则会进入下一个LAST_ACK状态,因此有大量CLOSE_WAIT,意味着代码中可能没有对连接断开的套接字进行close操作。解决方案就是检查代码。
5. TIME_WAIT状态有什么作用?
TIME_WAIT状态是主动关闭方在收到对方FIN后,进行最后一次ACK回复后进入的状态。如果最后一次ACK丢失了,被动关闭方等待没收到ACK,则会重新发送FIN(只重传一次),所以TIME_WAIT状态就是等待这次可能重传的ACK。
TIME_WAIT实际上是为了保护新建套接字不会使用刚被释放的套接字的地址和端口,防止原先通信的数据对新连接造成的影响(本来需要发送到原先套接字的重传FIN发送送到了新套接字的通信会话中)。
TIME_WAIT会等待两个MSL(最大报文生存周期)时间---->最后重传FIN和ACK的时间 X 2,确保本次通信的数据都消失在网络中,不会对新的连接造成影响。
TIME_WAIT实际上更多是为了保护客户端,客户端通常不主动绑定地址信息,交给系统分配;而服务端通信通常需要绑定相同的地址信息,重启之后地址信息不变。
6. 一台主机上出现大量TIME_WAIT链接,什么原因,怎么解决?
TIME_WAIT是主动关闭方最后一次发送ACK产生的。
出现大量TIME_WAIT则是因为大量主动关闭套接字,常见于爬虫主机。
解决方案:将TIME_WAIT等待时间设置更短一些。
或:设置套接字选项,开启地址复用,常见于服务端。
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
level:SOL_SOCKET
optname:SO_REUSEADDR
7. TCP的保活机制(心跳探测)
默认情况:通信双方7200s没有通信,则服务端每隔75s会向客户端发送一个保活探测数据包,要求客户端进行回复,连续9次没有进行回复则认为连接断开。
可以设置保活时间和次数。
连接断开在后程序在上层的表现:recv返回0;send会触发SIGPIPE异常—>会导致进程异常退出,可自定义SICPIPE信号的处理方式。
2.2 可靠传输
2.2.1 安全有序传输(保证数据可靠到达对端并且有序进行交付)
面向链接(前提):首先确保双方都具有数据收发能力
1. 确认应答机制
实现丢包检测功能,接收方针对接收的每一条数据都应该进行确认回复。
发送方收到确认回复认为传输成功,否则认为数据丢包。
2. 超时重传机制
发送方等待超时没有得到确认回复,则会对数据包重新传输。超时等待时长会随着网络环境的不同, 是有差异的.如果超时时间设的太长, 会影响整体的重传效率;如果超时时间设的太短, 有可能会频繁发送重复的包;
3. 协议字段中的序号(th_seq)和确认序号(th_ack)进行包序管理,实现有序交付
利用==协议字段中的序号(th_seq)和确认序号(th_ack)==进行包序管理,实现有序交付。
举个例子感受一下序号(th_seq)和确认序号(th_ack)的作用,下图中seq为其实序号,ack表示确认序号。
4. 协议字段中的校验和校验数据一致性。
数据不一致则丢弃,丢弃则无针对此数据的确认回复,对方收不到确认回复则重传此数据;也可直接发送重传请求。
2.2.2 避免没必要的丢包
丢包情况:
- 发送方发送数据过多,接收方来不及处理,缓冲区溢出之后所产生的丢包。
2.>传输起始或者过程中网络突然变差所导致的大量丢包。
1. 滑动窗口机制
依赖于协议字段中的窗口大小字段实现,避免发送方发送数据过多,接收方来不及处理,缓冲区溢出之后所产生的丢包。
原理:接收方接收数据之后就会进行确认应答,这时候会通过窗口大小字段告诉对方最多再给自己发送多少数据。窗口大小不能大于接收缓冲区剩余空间大小。
实现:通信双方都会维护一个发送窗口和接收窗口。
图画不动了,参考文章:网络 滑动窗口机制,这个里面有比较图解,过程较详细。
相关概念:
MSS:最大数据段大小,表示一个TCP报文中数据的最大大小。
相关协议:
停等协议:发送数据之后,收到确认回复才会发送下一条。适合网络环境差的场景。
回退n步协议:那一条数据丢失,则从丢失的数据包开始整体重传。
选择重传协议:哪条丢失重传哪条。适用于网络环境较好的场景。
2. 拥塞窗口机制
拥塞窗口机制:解决网络突然变差产生大量丢包的问题。
原理:发送方维护了一个拥塞窗口,用于限制当前所能发送的数据量。拥塞窗口大小是一种慢启动快增长(指数上涨,阈值为窗口大小)的形式进行传输控制。防止突然网络变差,导致大量丢包。每次丢包都会进行一次网络状况探测的过程。
2.2.3 提高一些传输性能
tcp为了实现可靠传输,牺牲了传输性能,而在传输过程中,有些性能上的损失是没有必要的。
1. 确认序号
确认序号标识序号之前的数据都已经收到了,为了避免因为确认应答丢失导致的重传。
2. 快速重传协议
在传输过程中,若接收方接收到的数据并非是接收窗口后沿数据,则有理由认为前边发送的数据丢失了,这时候每收到一条数据就会发送一条后沿数据的重传请求,一旦发送方连续收到三条(三条是为了避免数据延迟到达的情况)相同重传请求,则直接对这条数据(确认序号的数据)进行重传。丢包后不用完全等待超时重传,节省时间。
3. 捎带应答机制:
接收方对接收到的每一条数据都需要进行确认回复,然而确认回复最主要的信息就是确认序号,因此每一条确认回复都是一个tcp传输,至少是一个空报头(没有实际数据)的传输。如果收到数据后刚好要给对方发送数据,则将及即将要发送的数据和确认回复放到一起进行发送(在要发送的数据报头中加入确认序号)。
4. 延迟应答机制
接收方对接收到的每一条数据都需要进行确认回复,其中包含有当前窗口大小字段,如果立即进行回复,窗口大概率是不断减小的,因此延迟进行确认回复,有可能上层将数据取出,维持窗口大小。通过这种方式保证传输吞吐量不会降低。
5. 延迟发送机制
tcp传输过程中,如果对每次send的数据直接封装报头进行发送,若发送的数据比较小,但是次数较多,则io次数比较多,效率低。因此延迟发送,将数据在发送缓冲区中先堆积起来,再合适的时候一次性发送,减少了io次数,提高效率。
nagle算法–通过套接字选项设置(默认是开启的)
2.3 面向字节流传输
面向字节流传输:可靠的,有序的,双向的,基于连接的,以字节为单位的传输方式。传输时,并不限制上层(send \ recv)发送或者接收的数据大小。tcp延迟发送数据在缓冲区中积攒,基于mss取出合适大小数据进行发送。
优势:相对于面向数据报来说,传输更加灵活。
缺陷:产生粘包问题—将多条数据当作一条数据处理。
产生粘包的原因:tcp不会对数据进行边界处理。
解决方案:程序员需要在应用层进行数据的边界管理。可用特殊字符作为间隔(需要考虑特殊字符转译);使用TLV格式数据(在应用层头部加入数据长度);数据定长----提前约定会定长的数据,但是数据太短需要进行补全;
3 对应用层编程影响
- 连接断开:recv返回0,send触发异常。
recv一旦返回0,就要考虑文件描述符的回收关闭
Send因为会触发一次导致进程退出,若不想退出进程需要自定义信号处理。 - tcp传输存在粘包问题,需要在应用层进行数据边界管理。udp整条交付,无粘包问题。
问题:tcp和udp协议的区别?
实现上的区别:协议格式,协议字段不一样。
特性上的区别:TCp是面向连接的可靠的字节流传输方式。udp是无连接不可靠面向数据报的传输方式。udp支持广播,tcp不支持。
应用场景上的区别:UDP适用于实时性高于安全性的场景,tcp用于传输安全性高于实时性的场景。
tcp如何实现可靠传输
- 确认应答机制
- 超时重传机制
- 协议字段中的序号(th_seq)和确认序号(th_ack)进行包序管理,实现有序交付
- 协议字段中的校验和校验数据一致性。
udp如何实现可靠传输
ud协议本身没有实现可靠传输,需要在应用层手动实现类似tcp的可靠传输机制:确认应答机制,超时重传机制,包序管理。