TCP的三次握手和四次挥手

TCP(传输控制协议)是一种面向连接的协议,网络程序使用这个协议的时候,网络可以保证客户端和服务端的连接是可靠的,安全的。

首先,建立网络通信连接至少需要一对socket,一对socket可以唯一确定互联网络中每个TCP连接的双方。socket可以简单理解为IP+端口号,俗称“套接字”,是编程接口(API)。源IP可以让目标主机知道源主机的IP地址,以发送数据包,而端口号表示主机上应该接收和处理数据包内容的进程,目标IP表明数据包要送往的目标机。

其次,源主机在把数据包发送到网线上之前,会将数据(称有效载荷)进行封包操作,目标机在接收到数据包后,会进行解包操作,从而得到有效载荷。封包的过程:在有效载荷之前加上TCP包头(最重要的就是目标端口和源端口),然后再在前面加上IP包头(最重要的就是目标IP和源IP)。解包则是封包过程的逆操作。

TCP数据被封装在一个IP数据报中。
在这里插入图片描述
标志号解释
URG:紧急指针
ACK:确认序号有效
PSH:接收方应该尽快将报文交给应用层
RST:重置连接
SYN:发起一个新连接
FIN:释放一个连接

seq和ack序号都是32位

再次,就是TCP连接的建立过程,俗称三次握手
1、第一次握手:客户端发送位码SYN=1,seq=J的数据包到服务器。服务器由SYN=1知道,客户端要建立联机。
2、第二次握手:服务器收到请求后要确认联机信息,向客户端发送ack=J+1(表示J+1之前的数据都接收到了,下一个期待的字节是J+1),SYN=1,ACK=1,seq=k的数据包。
3、第三次握手:客户端收到后检查ack是否正确,即ack=seq+1=J+1,以及ACK=1,若正确,客户端再对服务器发送的SYN和seq进行回应,即发送ack=seq+1=k+1,ACK=1,seq=J+1 的确认包。服务器收到后确认ack=k+1,ACK=1。若是,则连接建立成功。

然后就可以快乐的传输数据啦~

最后,就是TCP的连接终止过程,俗称四次挥手
TCP连接是全双工的,全双工意思就是双方都可以作为数据的传输者和接受者,因此,当A没有数据要传输给B时,可以先关闭A到B的连接,但是,B可能还有数据要传输到A,所以每个方向都必须单独进行关闭。
1,客户端发送一个FIN,seq=z包,用来关闭客户端到服务器端的数据传送。
2,服务器端收到这个FIN,它发回一个ACK,确认序号Ack 为收到的序号z+1。此时服务器还可以继续发送数据。
3,服务器端关闭与客户端的连接,发送一个FIN seq = y 给客户端。
4,客户端发回ACK 报文确认,确认序号ack 为收到的序号y+1。
一般的TCP连接与断开总过程

图1 三次握手四次挥手全过程
这个图是我结合参考文章画的。

恭喜你学习到这里了


TCP状态解读

TCP状态变迁图

图2 TCP状态变迁图

上半部分是TCP三路握手过程的状态变迁,下半部分是TCP四次挥手过程的状态变迁。

TCP 三次握手过程的状态变迁

CLOSED:起始点,在超时或者连接关闭时候进入此状态,这并不是一个真正的状态,而是这个状态图的假想起点和终点。
LISTEN:服务器端等待连接的状态。服务器经过 socket,bind,listen 函数之后进入此状态,开始监听客户端发过来的连接请求。此称为应用程序被动打开(等到客户端连接请求)。
SYN_SENT:第一次握手发生阶段,客户端发起连接。客户端调用 connect,发送 SYN 给服务器端,然后进入 SYN_SENT 状态,等待服务器端确认(三次握手中的第二个报文)。如果服务器端不能连接,则直接进入CLOSED状态。
SYN_RCVD:第二次握手发生阶段,跟 3 对应,这里是服务器端接收到了客户端的 SYN,此时服务器由 LISTEN 进入 SYN_RCVD状态,同时服务器端回应一个 ACK,然后再发送一个 SYN 即 SYN+ACK 给客户端。状态图中还描绘了这样一种情况,当客户端在发送 SYN 的同时也收到服务器端的 SYN请求,即两个同时发起连接请求,那么客户端就会从 SYN_SENT 转换到 SYN_REVD 状态。
ESTABLISHED:第三次握手发生阶段,客户端接收到服务器端的 ACK 包(ACK,SYN)之后,也会发送一个 ACK 确认包,客户端进入 ESTABLISHED 状态,表明客户端这边已经准备好,但TCP 需要两端都准备好才可以进行数据传输。服务器端收到客户端的 ACK 之后会从 SYN_RCVD 状态转移到 ESTABLISHED 状态,表明服务器端也准备好进行数据传输了。这样客户端和服务器端都是 ESTABLISHED 状态,就可以进行后面的数据传输了。所以 ESTABLISHED 也可以说是一个数据传送状态。

TCP四次挥手过程的状态变迁

FIN_WAIT_1:第一次挥手。主动关闭的一方(执行主动关闭的一方既可以是客户端,也可以是服务器端,这里以客户端执行主动关闭为例),终止连接时,发送 FIN 给对方,然后等待对方返回 ACK 。调用 close() 第一次挥手就进入此状态。
CLOSE_WAIT:接收到FIN 之后,被动关闭的一方进入此状态。具体动作是接收到 FIN,同时发送 ACK。之所以叫 CLOSE_WAIT 可以理解为被动关闭的一方此时正在等待上层应用程序发出关闭连接指令。前面已经说过,TCP关闭是全双工过程,这里客户端执行了主动关闭,被动方服务器端接收到FIN 后也需要调用 close 关闭,这个 CLOSE_WAIT 就是处于这个状态,等待发送 FIN,发送了FIN 则进入 LAST_ACK 状态。
FIN_WAIT_2:主动端(这里是客户端)先执行主动关闭发送FIN,然后接收到被动方返回的 ACK 后进入此状态。
LAST_ACK:被动方(服务器端)发起关闭请求,由状态2 进入此状态,具体动作是发送 FIN给对方,同时在接收到ACK 时进入CLOSED状态。
CLOSING:两边同时发起关闭请求时(即主动方发送FIN,等待被动方返回ACK,同时被动方也发送了FIN,主动方接收到了FIN之后,发送ACK给被动方),主动方会由FIN_WAIT_1 进入此状态,等待被动方返回ACK。
TIME_WAIT:从状态变迁图会看到,四次挥手操作最后都会经过这样一个状态然后进入CLOSED状态。共有三个状态会进入该状态

  • 由CLOSING进入:同时发起关闭情况下,当主动端接收到ACK后,进入此状态,实际上这里的同时是这样的情况:客户端发起关闭请求,发送FIN之后等待服务器端回应ACK,但此时服务器端同时也发起关闭请求,也发送了FIN,并且被客户端先于ACK接收到。
  • 由FIN_WAIT_1进入:发起关闭后,发送了FIN,等待ACK的时候,正好被动方(服务器端)也发起关闭请求,发送了FIN,这时客户端接收到了先前ACK,也收到了对方的FIN,然后发送ACK(对对方FIN的回应),与CLOSING进入的状态不同的是接收到FIN和ACK的先后顺序
  • 由FIN_WAIT_2进入:这是不同时的情况,主动方在完成自身发起的主动关闭请求后,接收到了对方发送过来的FIN,然后回应 ACK。
为什么要有TIME_WAIT

TIME_WAIT状态是主动关闭一方持有的状态,除了CLOSING状态,其他情况最后的ACK都是由主动关闭方发出。

情况一:假如最后回应的ACK丢失了,服务器将继续发送FIN,客户端维护状态信息(TIME_WAIT)允许它重发最后的那个ACK如果没有这个TIME_WAIT状态,客户端处于CLOSED状态,那么客户端将响应RST服务器端收到后会将该RST分节解释成一个错误,也就不能实现最后的全双工关闭了(可能是主动方单方的关闭)。所以要实现TCP全双工连接的正常终止(两方都关闭连接),必须处理终止过程中四个分节任何一个分节的丢失情况,那么主动关闭连接的主动端必须维持TIME_WAIT状态,最后一个回应ACK的是主动执行关闭的那端。从变迁图可以看出,如果没有TIME_WAIT状态,我们将没有任何机制来保证最后一个ACK能够正常到达。前面的FIN,ACK正常到达均有相应的状态对应。

情况二:如果目前的通信双方都已经调用了 close(),都到达了CLOSED状态,没有TIME_WAIT状态时,会出现这样一种情况,现在有一个新的连接被建立起来,使用的IP地址和端口和这个先前到达了CLOSED状态的完全相同,假定原先的连接中还有数据报残存在网络之中,这样新的连接建立以后传输的数据极有可能就是原先的连接的数据报,为了防止这一点,TCP不允许从处于TIME_WAIT状态的socket 建立一个连接。处于TIME_WAIT状态的 socket 在等待了两倍的MSL时间之后,将会转变为CLOSED状态。这里TIME_WAIT状态持续的时间是2MSL(MSL是任何IP数据报能够在因特网中存活的最长时间),足以让这两个方向上的数据包被丢弃(最长是2MSL)。通过实施这个规则,我们就能保证每成功建立一个TCP连接时,来自该连接先前化身的老的重复分组都已经在网络中消逝了。

综上来看:TIME_WAIT存在的两个理由就是

  • 可靠地实现TCP全双工连接的终止;
  • 允许老的重复分节(数据报)在网络中消逝。
为什么是三次握手
为什么不是两次?

为了防止已经失效的连接请求报文又突然发送到服务器端,导致发生错误。

已经失效的连接请求报文:可能在某个网络结点长时间的滞留了,并没有丢失,导致在到连接请求释放后的某个时间点才到服务器端。本来已经是失效的连接请求报文,但服务器端接到请求后误以为客户端又发起了一个新的连接请求,如果两次握手就能建立连接的话,现在服务器端发送ACK给客户端,连接就建立起来了。但是,问题就在于,客户端并没有发送这个请求,于是便不会理睬服务器端的响应,也不会发送数据给服务器端。而服务器端却以为连接已经建立,一直在等待客户端发来数据。这样,服务器端很多资源就这样白白浪费了。而采用三次握手,就可以阻止这样的事情发生,由于服务器端没有收到客户端的确认,服务器端就知道连接没有建立,就不会等待。

为什么不是四次?

三次能解决的问题为什么要四次解决呢,这不是浪费吗……
**因为!**只有双方连接都建立了,才能进行数据的传输。每次的数据发送都要对方的确认,因此在第二次响应客户端的连接请求时,服务器端就把请求连接也发送过去,如果不发的话,只是客户端处于ESTABLISHED状态,而服务器端却处于CLOSING状态,也是不能传输数据的。所以确认完客户端连接请求后,服务器端只能建立连接,而合在一起比较节约资源,也提高了连接的速度和效率,所以就SYN 和ACK就一起发了。

为什么是四次挥手

这个问题就是为什么第二步和第三步不合起来。因为TCP是全双工的,主动方没有数据要发送后便发送FIN,但是被动方可能还有数据要发送,于是就只能先ACK对方。此后主动方只会对被动方后面发送的消息进行确认,但是不会再发送其他数据。被动方的断开连接请求发生在被动方数据要发送完成后。所以第二步和第三步不能合起来,除非刚好另外一方也刚好没信息要发送才会三次,即主动方在FIN_WAIT_1进入TIME_WAIT的那种情景。

socket流程

基于TCP(面向连接)的socket编程的服务器端程序如下:

1、创建套接字(socket)
2、将套接字绑定到一个本地地址和端口上(bind)
3、将套接字设为监听模式,准备接收客户端请求(listen)
4、等待客户请求到来,当请求到来后,接收连接请求,返回一个新的对应于此次连接的套接字(accept)
5、用返回的套接字和客户端进行通信(send/recv)
6、返回,等待另一客户请求
7、关闭套接字

基于TCP(面向连接)的socket编程的客户端程序如下:

1、创建套接字(socket)
2、向服务器发出连接请求(connect)
3、和服务器端进行通信(send/recv)
4、关闭套接字

基于UDP(面向对象)的socket编程的服务器端程序如下:

1、创建套接字(socket)
2、将套接字绑定到一个本地地址和端口上(bind)
3、等待接收数据(recvfrom)
4、关闭套接字

基于UDP(面向对象)的socket编程的客户端程序如下:

1、创建套接字(socket)
2、向服务器发送数据(sendto)

欢迎大家评论区讨论

参考文章:
【1】TCP的三次握手和四次挥手
【2】【Unix 网络编程】TCP状态转换图详解

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值