tcp协议段格式
- 源/目的端口号: 表示数据是从哪个进程来, 到哪个进程去;
- 4 位 TCP 报头长度: 表示该 TCP 头部有多少个 32 位 bit(有多少个 4 字节); 所以TCP 头部最大长度是 15 * 4 = 60
- 16 位校验和: 发送端填充, CRC 校验. 接收端校验不通过, 则认为数据有问题. 此处的检验和不光包含 TCP 首部, 也包含 TCP 数据部分.
- 16 位紧急指针: 标识哪部分数据是紧急数据;
- 6 位标志位:
- URG: 紧急指针是否有效
- ACK: 确认号是否有效
- PSH: 提示接收端应用程序立刻从 TCP 缓冲区把数据读走
- RST: 对方要求重新建立连接; 我们把携带 RST 标识的称为复位报文
- SYN请求建立连接; 我们把携带 SYN 标识的称为同步报文段
- FIN: 通知对方, 本端要关闭了, 我们称携带 FIN 标识的为结束报文段
tcp策略
确认应答机制(ACK)
每一个 ACK 都带有对应的确认序列号, 意思是告诉发送者, 我已经收到了哪些数据; 下
一次你从哪里开始发.
上图,既然发送的是一个序号,为什么在协议段中要设置两个序号(序号,确认序号)?
协议段中标记位有什么用?
客户端向服务端发送数据,或者服务端向客户端发送数据,有各种各样的请求,以客户端向服务端发送数据为例:建立连接请求,端口连接请求,确认报文......,不同类型的报文有不同的标记位;
超时重传机制
对方收到数据,就是我收到了ACK;但是,我没有收到ACK,对方没有收到数据(规定);
对于丢包,有两种情况:
(1)主机 A 发送数据给 B 之后, 可能因为网络拥堵等原因, 数据无法到达主机 B;如果主机 A 在一个特定时间间隔内没有收到 B 发来的确认应答, 就会进行重发;
(2)主机 A 未收到 B 发来的确认应答, 也可能是因为 ACK 丢失了;
因此主机 B 会收到很多重复数据. 那么 TCP 协议需要能够识别出那些包是重复的包, 并且把重复的丢弃掉;
那么特点的时间间隔到达是多长呢?
网络状态是会变化的,时间间隔是和网络状态相关联的--------->时间间隔是浮点的!
超时以 500ms 为一个单位进行控制, 每次判定超时重发的超时时间都是 500ms 的整数倍.
累计到一定的重传次数, TCP 认为网络或者对端主机出现异常, 强制关闭连接
连接管理机制
在正常情况下, TCP 要经过三次握手建立连接, 四次挥手断开连接;
建立连接
SYN置1:请求建立连接;
三次握手:
三次握手,只是一方主动发起,三次握手的过程是双方自动的完成的;
建立连接的备注就是在赌,赌最后一个ACK对方一定收到了!
如果连接不成功?最后一个ACK没有收到怎么办?
连接重置
RST置1:reset连接重置标志位;收到该标志位的主机,要对异常连接重新释放,重新建立;
断开连接
FIN置1:通知对方, 本端要关闭了;
两次握手:
客户端给服务器断开:Client要给服务器发送的数据已经发完,没有数据可发了;
但是服务器给客户端发消息,客户端还是会给服务器ACK;
那既然客户端关闭了文件描述符,为什么还可以读取消息呢?
用shutdown可以关闭读端或者写端;
为什么是四次挥手?
用最小的通信成本,建立了断开连接的共识;
双方都不通信了,并且也知道对方也不和我通信了!
四次挥手的过程也是双方自动完成的;
详谈
建立连接,为什么要三次握手?为什么不是一次握手,两次握手?
握手就是发报文;
一次握手:客户端给服务器发送SYN,这样双方默认连接建立好了;
如果一个客户端给服务器发送很多SYN,就会造成SYN洪水,浪费资源;两次握手也是如此;就是造成客户端挂掉;
虽然三次握手也会产生SYN洪水问题,但是相对于一次两次好点;
回到题目,原因:
- 验证全双工 ----验证网络的连通性
- 建立双方通信共识意愿(最小次数)
CLOSE_WAIT
如果服务器端不关闭文件描述符,就会出现CLOSE_WAIT;
我们编译运行服务器. 启动客户端链接, 查看 TCP 状态, 客户端服务器都为ESTABLELISHED 状态, 没有问题,然后我们关闭客户端程序, 观察 TCP 状态;
此时服务器进入了 CLOSE_WAIT 状态, 结合我们四次挥手的流程图, 可以认为四次挥手没有正确完成;
对于服务器上出现大量的 CLOSE_WAIT 状态, 原因就是服务器没有正确的关闭socket, 导致四次挥手没有正确完成. 这是一个 BUG. 只需要加上对应的 close 即可解决问题;
TMIE_WAIT
主动开始断开连接,自己要处于TIME_WAIT状态,等待需要一定的时长,2*MSL;
打开服务器,用telnet连接服务器,查询:
将服务器关掉,此时服务器是主动断开连接的一方,我们可以看到客户端处于CLOSE_WAIT,
再把客户端关掉,我们就可以看到服务器处于TIME_WAIT状态;
- TCP 协议规定,主动关闭连接的一方要处于 TIME_ WAIT 状态,等待两个MSL(maximum segment lifetime)的时间后才能回到 CLOSED 状态
- 我们使用 Ctrl-C 终止了 server, 所以 server 是主动关闭连接的一方, 在TIME_WAIT 期间仍然不能再次监听同样的 server 端口;
- MSL 在 RFC1122 中规定为两分钟,但是各操作系统的实现不同, 在 Centos7 上默认配置的值是 60s
- 可以通过 cat /proc/sys/net/ipv4/tcp_fin_timeout 查看 msl 的值;
MSL
MSL的TCP报文的最大生存时间;
为什么是2*MSL?
就能保证在两个传输方向上的尚未被接收或迟到的报文段都已经消失(否则服务器立刻重启, 可能会收到来自上一个进程的迟到的数据, 但是这种数据很可能是错误的);
bind error问题
之前的TCP/UDP bind error:
进程已经退了,但是连接还在,即连接对于的端口号还在这里;
怎么解决这个问题呢?
setsockopt可以解决端口号复用问题:
流量控制
看下图:
主机A给主机B发送数据,本质就是主机A的发送缓冲区将数据发送到主机B的接收缓存区,如果主机B的接受缓冲区满了,而主机A并不知道还在发送数据,这就会造成错误;怎么办呢?
只需要在发送报文是在窗口记录自己的剩余空间发送给对方即可;
16 位数字最大表示 65535, 那么 TCP 窗口最大就是 65535 字节么?
实际上, TCP 首部 40 字节选项中还包含了一个窗口扩大因子 M, 实际窗口大小是 窗口
字段的值左移 M 位;
其实在tcp三次握手的时候,就已经协商交换过了双方的接受能力!!!
如果接受缓冲区的剩余空间大小为0,怎么办?
这时发送方就会等待;
那主机A怎么知道主机B什么时候有空间呢?(两种方法)
1、在等待期间主机A会主动进行窗口更新(tcp报文)查看主机B是否有空间;定期发送一个窗口探测数据段, 使接收端把窗口大小告诉发送端.
2、主机B有空间后,会向主机A进行窗口更新通知;
那如果主机B的窗口大小一直不更新呢?主机A有没有什么办法尽快的让主机B向上交互?
如果想让对方尽快的处理数据,都可以设置PSH标准位:PSH:PUSH
滑动窗口
目前:滑动窗口的大小=对方同步给我的窗口大小,即对方的接受能力(暂时)
问题列表:
1、滑动窗口只能向右滑动?能不能向左滑动?
只能向右滑动,不能向左
2、滑动是一直不变的吗?可以变得吗?可以变小吗?
滑动窗口可以变大,可以变小
3、可以为0吗?
可以为0
理解滑动窗口
我们知道TCP是面向字节流,每个字节的数据都进行了编号. 即为序列号;
滑动窗口本质就是下标:
在滑动窗口中如果丢包了呢?
丢包分为三种情况:
1、最左侧报文丢失
- 确认序号规定的约束,滑动窗口左侧不动
- 快重传&&超时重传,对最左侧报文进行补发
如果2000号报文丢失了,但是3000,4000,5000都收到了;但是发送的ack只能是1001;
- 当某一段报文段丢失之后, 发送端会一直收到 1001 这样的 ACK, 就像是在提醒发送端 "我想要的是 1001" 一样;
- 如果发送端主机连续三次收到了同样一个 "1001" 这样的应答, 就会将对应的数据 1001 - 2000 重新发送;
- 这个时候接收端收到了 1001 之后, 再次返回的 ACK 就是 7001 了(因为 2001 - 7000)接收端其实之前就已经收到了, 被放到了接收端操作系统内核的接收缓冲区中;
- 这种机制被称为 "高速重发控制"(也叫 "快重传")
2、中间报文丢失
如果3000号报文丢失了,但是2000,4000,5000都收到了;但是发送的确认序号ack只能是2001;
窗口左侧会更新到2001;就回归到最左侧报文丢失问题;
3、最右侧报文丢失
窗口左侧更新,转换成最左侧丢失问题;