TCP协议详解——初识
目录
TCP协议段格式
确认应答(ACK)机制
超时重传机制
连接管理机制
TCP协议段格式
这篇文章是初识,先理解部分字段的作用
源和目标端口号意义明确。
序列号和确认应答号后面再展开
数据偏移字段是4位,意义是表明TCP首部有几个4字节,图上可以看出来一行是32bit也就是4字节,这个4位数据偏移字段相当于表明了有几行,由此可以算出TCP首部最大时4 * 15 = 60个字节
控制位有8位,但是很多时候把前两位与保留位合并,之后的6位每个位表达不同的含义,从左到右依次是
URG: 紧急指针是否有效
ACK: 确认号是否有效
PSH: 提⽰接收端应⽤程序⽴刻从TCP缓冲区把数据读⾛
RST: 对⽅要求重新建⽴连接; 我们把携带RST标识的称为复位报⽂段
SYN: 请求建⽴连接; 我们把携带SYN标识的称为同步报⽂段
FIN: 通知对⽅, 本端要关闭了, 我们称携带FIN标识的为结束报⽂段
16位窗口大小后面再展开
16位校验和: 发送端填充, CRC校验. 接收端校验不通过, 则认为数据有问题. 此处的检验和不光包含
TCP⾸部, 也包含TCP数据部分
16位紧急指针: 标识哪部分数据是紧急数据;
确认应答(ACK)机制
在TCP中,当接收端收到发送端的数据时,接收端会返回一个已经收到消息的通知,这个消息就叫做ACK
通过ACK,TCP可以实现可靠传输,如果收到了确认应答,说明数据发送成功,否咋很可能数据丢失。
主机A发出数据后一段时间仍未收到确认应答,这时可判定为数据丢失,主机A再次发送数据,这样通过ACK,即使发生了数据丢失,仍然可以实现可靠的数据传输
未收到应答也可能不是数据丢失,而是应答丢失,如下图
主机B发送的确认应答可能由于网络拥堵等原因丢失,主机A会等待一段时间,若超出特定时间就会重发数据。此时,主机B将第二次发送的确认应答。由于主机B已经收到过1~1000的数据,再次收到就会丢弃。
这时会出现一个严重的问题,假设主机B发送的确认应答一直未能成功到达主机A,A只需要不断重发数据就可以,而B就会反复收到同样的数据,就会反复丢弃同样的数据,为此需要一种机制来判断是否已经接收过数据,又能判断是否需要接收。
序列号就可以实现这些目的。TCP将每个字节的数据都进⾏了编号. 即为序列号.
接收端会查询收到的数据的TCP首部中的序列号和数据的长度,将自己下一步应该接收的序列号作为确认应答发送出去,上面的图中类似1~1000就是序列号的意思,但是需要注意序列号并不从0或1开始,图中只是示意
超时重传机制
如何确定超时时间具体的值呢
最理想的情况下, 找到⼀个最⼩的时间, 保证 "确认应答⼀定能在这个时间内返回"
但是这个时间的⻓短, 随着⽹络环境的不同, 是有差异的
如果超时时间设的太⻓, 会影响整体的重传效率
如果超时时间设的太短, 有可能会频繁发送重复的包
TCP为了保证⽆论在任何环境下都能⽐较⾼性能的通信, 因此会动态计算这个最⼤超时时间
具体为在每次发包时计算往返时间和偏差,将两者相加,偏差的存在是考虑到了网络波动的情况。
BSD的Unix和Windows,重发超时都是0.5s的整数倍,偏差最小是0.5s,最初的数据包不知道往返时间,一般重发隔的时间是6s左右。
数据并不会无限次的重发,达到一定次数后,如果仍没收到应答,则判定对端出现了异常,强制关闭连接。
连接管理机制
连接可以认为是TCP协议在进行通信发送有效数据之前必要的步骤,是为了检测对端是否能正常通信。
TCP通过三次握手建立连接,四次挥手断开连接
通信前发送发送一个SYN包,若对方发来确认应答,则认为可以进行数据通信,若未收到确认应答则不会进行数据通信。通信结束时会进行断开连接的处理(FIN包)
为什么握手是三次而挥手是四次呢,看图可以发现,客户端发送SYN包后,服务器发送的是ACK+SYN,可以认为把两个包合在一起了,而挥手断开连接就是四次,一次发送一个包
CLOSE_WAIT状态
在编写tcp网络服务器时,若服务器端在客户端调用close(fd)后没有调用关闭文件描述符,这时用
netstat -nltp
可查看到服务器处于CLOSE_WAIT状态。
在服务器接收到客户端的FIN后,服务器进入CLOSE_WAIT状态,并且发出对FIN的ACK,然后进入LAST_ACK状态,接下来如果收到了客户端的应答,就会关闭连接,但是,如果服务器没发出FIN, 服务器就会一直停在CLOSE_WAIT状态,会一直占用服务器的资源,所以,服务器一定要记得调用close关闭已经断开连接的客户端
TIME_WAIT
在编写tcp网络服务器时,若将服务器启动后,在客户端连接后ctrl+c取消再立即绑定同样的端口,会绑定失败,可使用
netstat -nltp
查看到服务器处于TIME_WAIT状态,这是由于server的应用程序停止了,但是连接并没有通过四次挥手正常断开,因此不能监听同样的端口
TCP协议规定,主动关闭连接的⼀⽅要处于TIME_ WAIT状态,等待两个MSL(maximum segment
lifetime)的时间后才能回到CLOSED状态
我们使⽤Ctrl-C终⽌了server, 所以server是主动关闭连接的⼀⽅, 在TIME_WAIT期间仍然不能再次监听同样的server端⼝
MSL在RFC1122中规定为两分钟,但是各操作系统的实现不同, 在Centos7/Ubuntu上默认配置的值
是60s
可以通过
cat /proc/sys/net/ipv4/tcp_fin_timeout
查看msl的值
为什么TIME_WAIT的时间是两个msl?
MSL 是 TCP 报⽂的最⼤⽣存时间, 因此 TIME_WAIT 持续存在 2MSL 的话
就能保证在两个传输⽅向上的尚未被接收或迟到的报⽂段都已经消失(否则服务器⽴刻重启, 可能
会收到来⾃上⼀个进程的迟到的数据, 但是这种数据很可能是错误的)
同时也是在理论上保证最后⼀个报⽂可靠到达(假设最后⼀个ACK丢失, 那么服务器会再重发⼀个
FIN. 这时虽然客⼾端的进程不在了, 但是TCP连接还在, 仍然可以重发LAST_ACK,从而让服务器不至于一直停在CLOSE_WAIT状态)
解决因TIME_WAIT状态导致的bind失败问题
场景:服务器挂掉,这时需要立马重启,但无法立即绑定同一个端口,TIME_WAIT时长可能持续到1~2分钟
为了解决这种问题,需要开启地址端口重用,使用
#include <sys/types.h>
#include <sys/socket.h>
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
来解决
int opt = 1;
setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))
这个系统调用可以对socket的属性进行设置,如上的代码,就将SO_REUSEADDR属性设置为真,使得服务器可以绑定TIME_WAIT的端口