面向连接
三次握手
重重点:
SYN代表发送者可以收发数据
FIN代表对方不再发送数据
为什么是三次握手
握手是双方都要确保对方有收发数据的能力,因此都要发送SYN请求
两次不行:两次是不安全
状态要求:SYN延迟到达,与重发的SYN冲突,通过三次握手的不同状态,让两端做不同的事情,遇见同样的SYN则丢弃防止恶意攻击,客户端发送SYN就退出,占用服务器资源
四次没必要
握手失败两端的处理
- 第一次连接丢了,客户端重发
- 第二次连接丢了,客户端重传,服务端等不到ACK就会发送RST报文,服务端断开连接
- 第三次连接丢了,服务器没收到ACk,释放资源
1.从双方连接状态的角度
- 两次握手时,服务端认为该连接不是建立的
该连接还在内核的未完成队列中;- 即使程序员调用accept函数也不会将连接从内核中获取
2.从客户端接收能力角度
- 客户端发送SYN,服务端收到则认为客户端可以发送;
- 服务端发送ACK+SYN,客户端收到则认为服务端可以收发数据;
这个ACK说明服务端可以收发数据- 客户端发送ACK,服务端收到则认为客户端可以接收
这个ACK说明客户端可以收发数据
不发,则服务端不确定客户端是否能收发数据
- 对于客户端,状态是 ESTEBALISHED则连接建立;
- 此时服务端状态是 SYN_RCVD,认为连接未建立;
- 当前连接还在内核未完连接成队列中,即使服务端调用accept也不会将连接从内核中拿回;
- 只有客户端回复ACK,状态变成 ESTEBALISHED,连接才建立,
服务端调用accept函数才能从已完成队列中取回连接
四次挥手
四次挥手,双方都有可作为主动断开方
- 问题1:为什么是四次挥手
FIN的功能,只代表对方不再发送数据,不代表对方不接收数据
被动关闭方收到FIN确认后,还有可能继续发数据,上层没数据了,才关闭套接字
因此没有合并成三次
- 问题2:出现大量CLOSE_WAIT状态的原因
该状态的下一步是关闭连接
连接断开后,上层没有关闭套接字,是代码的问题
- 问题3:TIME_WAIT状态的作用,为什么不直接关闭套接字资源
需知:MSL(默认60秒)是报文最大生命周期
分析:最后一次ACK可能丢失,未收到ACK会重传FIN包
主动方直接关闭套接字,此时主动方别的程序启用该端口,就可能和当前服务器进行通信,直接收到一个FIN包
等待2MSL后,保证上一次通信的所有数据包都丢失在网络中,不影响后续的通信
问题4:出现大量TIME_WAIT状态的原因
- TIME_WAIT的作用:
TIME_WAIT状态是主动方发送最后一次ACK后进入的状态,等待是为了处理因FIN包丢失重传
等待2MSL时间,防止最后一次ACK丢失重传FIN包影响新连接
- 产生原因:
主机上存在大量主动关闭的连接
一台主机最多有65536个端口,就只能创建这么多客户端- 如何解决
可以将TIME_WAIT时间缩短
如果服务器是主动关闭方,大量程序处于wait状态,是非常危险的(服务器崩溃无法重启),应采用地址重用
地址重用:当前连接虽然使用了该端口,但是其它连接仍可使用,setsocketopt函数
问题5:
保活机制
两端通信频率低,但是网断了(没有四次挥手),两端如何知道连接断开
连接断开的信息:recv返回0,send会发送异常
若长时间没通信(默认7200秒),tcp服务器会自动发送保活心跳包(每75秒发送一次包,连发9次),若对方没响应就断开
包序管理
网络抓包
要了解包序管理,就要先进行网络抓包:
1.win环境,使用wireshark针对网卡进行抓包
2.linux环境,使用tcpdump,可以抓任意协议的数据包
- 命令:tcpdump -i any port [要抓取的数据端口] -s 0 -w 123.dat
any表示所有网卡都需抓取- 123.dat抓到的数据所写入的文件
- 将这些文件在wireshark分析
三次握手阶段
三次握手建立连接后,双方并不能正常发送数据
序号:tcp将发送的每个字节数据进行了编号,即序列号
- 双方都有各自的序号,谁发送数据的时候,就使谁的序号标识数据;
- 接收方收到数据后,会进行确认,确认方式是 “期望收到对方发送的下一个序号数据包” ;
- ack是确认收到上一序号消息 + 期望下次收到的信息序号
- seq标识本端是从哪个序号发送信息的
- tcp双方在发送数据时,数据的每个字节都有序号进行标识
纯ACK数据包(上图中第二次连接是ACK+SYN包,第三次连接是纯ACK包)不消耗序号,也就不需要对方确认,
例如:
对方确认序号是多少,我下次的发送起始序号是多少
结论:
- 在三次握手中,双方协商各自维护序号的起始位置
- 双方在发送数据时是消耗自己的序号,确认对方序号
- 纯ACK数据包不消耗序号
- tcp针对发送的数据,每一个字节都会消耗一个序号
四次挥手阶段
MSL:报文最大生存时间(自发送方发送数据后,发送方认为该报文最大生存时间是MSL);
主动方收到后重置MSL,产生新的TIME_WAIT状态继续等待2MSL;
正常到达状态:
- 主动断开方等待了2MSL的时间,还未等到重传的FIN报文,说明ACK数据一定到达了被动方;
- 多等一个MSL是保证被动断开方重传FIN能到达主动方
只有主动断开连接方才存在TIME_WAIT状态;
处于TIME_WAIT状态的程序,还需要占用端口,等待2MSL后释放端口,这是要防止被动断开方重传FIN报文
端口重用:
- 主动断开方处于TIME_WAIT状态会等待2MSL时间变成CLOSED,但是这个时间内,这个应用程序虽然退出,但是操作系统还依旧使用着端口(程序中的端口是写死的);
- 在这个时间内再次快启动该程序就会出现问题,报错:bind:Address already in ues
需要在该代码中使用setsockopt函数提前将端口设置为重用状态
CLOSED_WAIT状态
- 被动断开方才有的状态;
- 处于CLOSED_WAIT状态的被动断开方,需要调用close函数关闭新连接套接字,只有调用后才能变成LAST_ACK状态;
- 在这个状态变化过程中,被动断开方还可以给对端发送一些数据