目录
1.认识Tcp的报头
2.确认应答机制(ACK)
序号与确认序号
捎带应答
3.超时重传机制
4.Tcp连接管理
三次握手
为什是三次握手
四次挥手
理解TIMEWAIT
1.认识Tcp的报头
源端口和目的端口号没什么说的
32位的序号和确认序号,之后会介绍。
4位首部长度表示的是Tcp报头的长度,但是4位长度最多也就能表示15个字节,即使不算选项,也有20个字节,这是因为首部长度的单位是4字节,所以四位首部长度可以表示的长度是 4 * 15 = 60字节。
URG ACK PSH 都是tcp协议的选项,代表着不同的Tcp协议类型。
剩下的字段后面介绍。
2.确认应答机制(ACK)
Tcp是保证可靠性的,确认应答机制就是保证可靠性的一种机制。
其实这就是在日常生活中很常见的方法。
例:小明:小红我喜欢你,你愿意做我女朋友吗?
小红:就你这样长的太丑了,还是算了吧。
这个例子就是确认应答机制,怎么理解呢,
小红对小明的请求进行了回复(ACK),就说明小明的请求小红收到了。
到此故事结束。
序号与确认序号
Tcp是传输控制协议,发送数据需要先将数据交给tcp的发送缓冲区,可能会将数据拆分一个一个包。交给操作系统,系统在发送,在到达对端的时候要保证包的顺序要和发送前一致,所以必须要有序号来标识每个包。
Tcp是面向字节流的,他会对每个字节进行编号,ACK返回时,确认序号 = 序号 + 1,为了告诉发送者,下次该从哪个字节开始发。
这里可能就有一个问题了,可能有人觉得确认序号就是一个摆设,明明一个序号就能搞定的事,还整一个确认序号。
但是在捎带应答的场景下一个序号就不够用。
捎带应答
捎带应答就是,在ACK向发送者返回时,碰巧接受者也想向发送者发送信息,那么这两条报文就会合二为一,因为ACK就是一个报头,他没有数据,碰巧我正想发送数据,在ACK加上数据不就是两全其美了吗。
在这种情况下,序号表示发server送包的顺序,确认序号表示clint下次发送开始的位置。
3.超时重传机制
这也是tcp保证可靠性的一种方式,在一段时间内,如果没收到ACK就会,重新发送一次数据。
没有收到应答是有两种可能1.真的丢包了 2.包收到但ACK丢了,这种情况就需要tcp把收到序号相同的包直接丢弃。
这个特殊的时间间隔是根据网络状况动态计算的。
4.Tcp连接管理
三次握手
服务端状态转化:[CLOSED -> LISTEN] 服务器端调用listen后进入LISTEN状态, 等待客户端连接;[LISTEN -> SYN_RCVD] 一旦监听到连接请求(同步报文段), 就将该连接放入内核等待队列中, 并向客户端发送SYN确认报文.[SYN_RCVD -> ESTABLISHED] 服务端一旦收到客户端的确认报文, 就进入ESTABLISHED状态, 可以进行 读写数据了
客户端状态转化:[CLOSED -> SYN_SENT] 客户端调用connect, 发送同步报文段;[SYN_SENT -> ESTABLISHED] connect调用成功, 则进入ESTABLISHED状态, 开始读写数据;
三次握手是保证可靠性的不是三次握手连接100%能建立成功
前两次握手报文丢失,客户端和服务端的服务器状态都不是ESTABUSHED,超时重传即可。
但是最后一次握手ACK发出去的时候客户端就认为自己连接已经建立成功了,如果这个ACK丢失,服务器连接没建立成功,客户端认为自己连接建立成功,客户端直接向服务器发送数据了,这时候服务器会向客户端发送RST报文(reset)重新进行三次握手。
为什是三次握手
。
1.三次握手双方都有一次完整的收发,保证了网络是健康的 而且 双方都是全双工的。
2.保证了双方的Tcp都是愿意通信的,SYN就相当于客户端对服务器说你愿意和我通信吗? ACK就是我愿意
四次挥手
在这里补充一下,三次握手和四次挥手都是通讯细节,这些都是操作系统的工作。
服务端状态转化:[ESTABLISHED -> CLOSE_WAIT] 当客户端主动关闭连接(调用close), 服务器会收到结束报文段, 服务器 返回确认报文段并进入CLOSE_WAIT;[CLOSE_WAIT -> LAST_ACK] 进入CLOSE_WAIT后说明服务器准备关闭连接(需要处理完之前的数据); 当服务器真正调用close关闭连接时, 会向客户端发送FIN, 此时服务器进入LAST_ACK状态, 等待最后一个 ACK到来(这个ACK是客户端确认收到了FIN)[LAST_ACK -> CLOSED] 服务器收到了对FIN的ACK, 彻底关闭连接.
客户端状态转化:[ESTABLISHED -> FIN_WAIT_1] 客户端主动调用close时, 向服务器发送结束报文段, 同时进入FIN_WAIT_1;[FIN_WAIT_1 -> FIN_WAIT_2] 客户端收到服务器对结束报文段的确认, 则进入FIN_WAIT_2, 开始等待服务器的结束报文段;[FIN_WAIT_2 -> TIME_WAIT] 客户端收到服务器发来的结束报文段, 进入TIME_WAIT, 并发出LAST_ACK;[TIME_WAIT -> CLOSED] 客户端要等待一个2MSL(Max Segment Life, 报文最大生存时间)的时间, 才会 进入CLOSED状态
问题:客户端关闭连接,服务器还能给客户端发消息吗?
答案是可以的 ,tcp是有接受和发送两个缓冲区的,一般情况下关闭连接直接close(fd)两个缓冲区会直接关闭,但shutdown接口是可以只关闭一个缓冲区的。
参数how,设置为SHUT_RD关闭接受缓冲区,SHUT_WR关闭发送缓冲区,SHUT_RDWR关闭接受和发送两个缓冲区。
理解TIMEWAIT
先启动一个服务,然后ctrl + c将服务终止,在将服务启动,我们会发现,bind竟然失败了。
这是因为Tcp规定,主动关闭连接的一方要等待两个MSL(maximum segment lifetime)时间在从TIMEWAIT状态变为closed。
可以使用cat /proc/sys/net/ipv4/tcp_fin_timeout 查看MSL时间,默认60秒。
这么做的目的是为了防止,服务器关闭了,却还有报文在网络中传输。
如果服务器立马重启,收到了这种报文不就出问题了吗,这种报文已经过期了,丢掉都一点都不可惜。
在server的TCP连接没有完全断开之前不允许重新监听, 某些情况下可能是不合理的。
使用setsocketopt接口可以解决这种问题。
int opt = 1;
::setsockopt(_listenfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));
理解CLOSE_WAIT状态
服务器启动,客户端与服务器建立连接,客户端退出。
查看服务器连接的状态,发现有很多的CLOSE_WAIT连接,出现这种情况就是服务器忘记关闭文件描述符了,导致四次挥手不能完成,连接的状态就卡在CLOSE_WAIT这里了。