MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

TCP协议中的三次握手与四次挥手详解

2024-02-154.8k 阅读

TCP协议概述

传输控制协议(Transmission Control Protocol,TCP)是一种面向连接的、可靠的、基于字节流的传输层通信协议。在网络通信中,TCP确保数据能够准确无误地从发送端传输到接收端,广泛应用于对数据准确性要求较高的场景,如文件传输、电子邮件、网页浏览等。

TCP协议通过一系列机制来保证数据传输的可靠性,其中三次握手用于建立连接,四次挥手用于断开连接。深入理解这两个过程对于掌握TCP协议以及进行高效稳定的网络编程至关重要。

三次握手

什么是三次握手

三次握手(Three-way Handshake)是指在建立一个TCP连接时,需要客户端和服务器端总共发送三个包以确认连接的建立。这个过程确保了双方都能知道彼此的发送和接收能力正常,为后续的数据传输奠定基础。

三次握手的过程

  1. 第一次握手(SYN):客户端向服务器发送一个SYN(同步序列号,Synchronize Sequence Numbers)包,其中包含客户端初始序列号(Sequence Number,seq),假设为x。这个包表示客户端希望与服务器建立连接,并告知服务器自己的初始序列号。此时客户端进入SYN_SENT状态。
  2. 第二次握手(SYN + ACK):服务器接收到客户端的SYN包后,会返回一个SYN + ACK包。这个包中,SYN部分表示服务器同意建立连接,同时服务器也会发送自己的初始序列号,假设为y。ACK部分是对客户端SYN包的确认,确认号(Acknowledgment Number,ack)为客户端的序列号x加1,即ack = x + 1。此时服务器进入SYN_RCVD状态。
  3. 第三次握手(ACK):客户端接收到服务器的SYN + ACK包后,会向服务器发送一个ACK包。该ACK包的确认号为服务器的序列号y加1,即ack = y + 1,序列号为客户端第一次发送的序列号x加1,即seq = x + 1。服务器接收到这个ACK包后,连接正式建立,双方进入ESTABLISHED状态,可以开始进行数据传输。

为什么需要三次握手

  1. 防止已失效的连接请求报文段突然又传送到了服务器:设想一种情况,如果只进行两次握手。客户端发送了一个连接请求,由于网络延迟等原因,这个请求在网络中滞留了很长时间,以致于超过了客户端认为的合理时间,客户端就会重新发送连接请求。而此时,原来滞留的请求到达了服务器,服务器基于这个请求建立连接并开始等待数据。但客户端实际已经使用新的连接,不会再向这个旧连接发送数据,这就导致服务器端资源浪费。通过三次握手,客户端在收到服务器的确认后,会再次确认,避免了这种情况的发生。
  2. 确保双方的接收和发送能力正常:三次握手过程中,每一次发送和接收都包含了对对方能力的确认。客户端通过发送SYN包表明自己有发起连接的能力,服务器通过返回SYN + ACK包表明自己能接收连接请求并发送确认,客户端再次发送ACK包表明自己能正确接收服务器的确认信息并能发送确认。这样双方都确认了彼此的接收和发送能力正常,为可靠的数据传输奠定基础。

三次握手的代码示例(Python + socket)

import socket


def server():
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socket.bind(('127.0.0.1', 8888))
    server_socket.listen(1)
    print('Server is listening on 127.0.0.1:8888')
    while True:
        client_socket, addr = server_socket.accept()
        print(f'Connected by {addr}')
        data = client_socket.recv(1024)
        print(f'Received: {data.decode()}')
        client_socket.sendall(b'Hello, client!')
        client_socket.close()


def client():
    client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    client_socket.connect(('127.0.0.1', 8888))
    client_socket.sendall(b'Hello, server!')
    data = client_socket.recv(1024)
    print(f'Received: {data.decode()}')
    client_socket.close()


if __name__ == '__main__':
    from threading import Thread

    server_thread = Thread(target=server)
    client_thread = Thread(target=client)

    server_thread.start()
    client_thread.start()

    server_thread.join()
    client_thread.join()

在上述代码中,server函数模拟服务器端,client函数模拟客户端。服务器端通过bind绑定到指定地址和端口,并通过listen开始监听。客户端通过connect发起连接请求,这就类似于三次握手中客户端发送SYN包。服务器接收到连接请求后,通过accept接受连接,类似服务器返回SYN + ACK包。客户端发送数据和接收数据,服务器接收数据和发送数据,完成了类似三次握手后的通信过程。

四次挥手

什么是四次挥手

四次挥手(Four-way Handshake)是指在关闭一个TCP连接时,需要客户端和服务器端总共发送四个包以确认连接的断开。这一过程确保了双方都能确认数据传输已经完成,并且释放相关的资源。

四次挥手的过程

  1. 第一次挥手(FIN):主动关闭方(假设为客户端)发送一个FIN(结束标志,Finish)包,其中包含客户端当前的序列号seq = u。这个包表示客户端已经没有数据要发送给服务器了,请求关闭连接。此时客户端进入FIN_WAIT_1状态。
  2. 第二次挥手(ACK):服务器接收到客户端的FIN包后,会返回一个ACK包。该ACK包的确认号为客户端的序列号u加1,即ack = u + 1,序列号为服务器当前的序列号v。这个ACK包表示服务器已经收到客户端的关闭请求,但服务器可能还有数据要发送给客户端。此时服务器进入CLOSE_WAIT状态,而客户端进入FIN_WAIT_2状态。
  3. 第三次挥手(FIN):当服务器完成数据发送后,会向客户端发送一个FIN包,其中序列号为w,确认号仍然为u + 1。这个FIN包表示服务器也没有数据要发送给客户端了,请求关闭连接。此时服务器进入LAST_ACK状态。
  4. 第四次挥手(ACK):客户端接收到服务器的FIN包后,会返回一个ACK包。该ACK包的确认号为服务器的序列号w加1,即ack = w + 1,序列号为u + 1。服务器接收到这个ACK包后,连接正式关闭。客户端等待2MSL(Maximum Segment Lifetime,最长报文段寿命)时间后,也关闭连接。此时客户端进入TIME_WAIT状态,2MSL时间过后,客户端彻底关闭连接。

为什么需要四次挥手

在TCP连接中,数据的传输是双向的。当一方(如客户端)想要关闭连接时,首先发送FIN包表示自己不再发送数据,但对方(服务器)可能还在发送数据。所以服务器先返回ACK确认收到关闭请求,等自己数据发送完后再发送FIN包。这样就形成了四次挥手,确保双方都能安全地关闭连接,避免数据丢失。

为什么客户端在第四次挥手后要等待2MSL

  1. 确保最后一个ACK包能被服务器正确接收:如果服务器没有收到客户端的ACK包,会重新发送FIN包。客户端等待2MSL时间,如果在此期间收到服务器重发的FIN包,就可以重新发送ACK包。2MSL时间足够让ACK包和可能重发的FIN包在网络中往返。
  2. 防止旧的连接请求在网络中滞留:2MSL时间可以保证在这段时间内,网络中所有旧的数据包都已经消失。这样当客户端重新建立连接时,就不会受到旧连接数据包的干扰。

四次挥手的代码示例(Python + socket)

import socket


def server():
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socket.bind(('127.0.0.1', 8888))
    server_socket.listen(1)
    print('Server is listening on 127.0.0.1:8888')
    client_socket, addr = server_socket.accept()
    print(f'Connected by {addr}')
    data = client_socket.recv(1024)
    print(f'Received: {data.decode()}')
    client_socket.sendall(b'Hello, client!')
    client_socket.close()


def client():
    client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    client_socket.connect(('127.0.0.1', 8888))
    client_socket.sendall(b'Hello, server!')
    data = client_socket.recv(1024)
    print(f'Received: {data.decode()}')
    client_socket.close()


if __name__ == '__main__':
    from threading import Thread

    server_thread = Thread(target=server)
    client_thread = Thread(target=client)

    server_thread.start()
    client_thread.start()

    server_thread.join()
    client_thread.join()

在这个简单示例中,虽然没有直观地体现四次挥手的过程,但当客户端和服务器调用close方法时,底层的TCP协议会执行四次挥手的操作。在实际应用中,可以通过设置socket的一些选项来更深入地观察和控制连接关闭的过程。例如,在Python中可以通过setsockopt方法设置SO_LINGER选项来控制关闭连接时的行为。

三次握手和四次挥手可能出现的问题及解决方法

三次握手可能出现的问题

  1. SYN攻击:攻击者发送大量伪造的SYN包,目标服务器会为每个SYN包分配资源并返回SYN + ACK包,但由于源IP是伪造的,服务器收不到ACK包,导致这些半连接占用大量资源,最终使服务器无法正常处理合法的连接请求。
    • 解决方法:可以采用SYN Cookie技术。服务器在收到SYN包时,不立即分配资源,而是根据SYN包中的信息计算出一个Cookie值,将这个值作为ACK包中的初始序列号返回给客户端。客户端响应ACK包时,服务器通过验证这个Cookie值来确认连接的合法性,从而避免为伪造的连接分配资源。
  2. 网络延迟导致握手超时:在三次握手过程中,如果网络延迟较高,可能导致某个包的传输时间过长,超过了对方的等待超时时间,从而使握手失败。
    • 解决方法:可以适当调整等待超时时间,根据网络环境和业务需求进行合理设置。同时,也可以采用重传机制,当发送方在一定时间内没有收到对方的响应时,重新发送相应的包。

四次挥手可能出现的问题

  1. TIME_WAIT状态过长:客户端在四次挥手后进入TIME_WAIT状态并等待2MSL时间,这期间端口会被占用。如果短时间内有大量连接建立和关闭,可能导致可用端口耗尽。
    • 解决方法:可以根据实际情况适当缩短2MSL时间,但要注意可能带来的风险,如旧的数据包干扰新连接。也可以通过设置SO_REUSEADDR选项,允许在TIME_WAIT状态下重新使用端口。但使用这个选项时要谨慎,因为可能会导致数据丢失或错误。
  2. FIN包丢失:在四次挥手过程中,如果FIN包丢失,可能导致连接无法正常关闭。例如服务器发送的FIN包丢失,客户端就不会进入TIME_WAIT状态,服务器也无法彻底关闭连接。
    • 解决方法:TCP协议本身有重传机制,发送方在一定时间内没有收到确认包时会重发FIN包。同时,接收方也可以通过心跳机制来检测连接状态,及时发现异常并进行处理。

三次握手和四次挥手在不同场景下的应用

高并发场景下的三次握手优化

在高并发场景中,大量的连接请求可能导致服务器性能瓶颈。为了优化三次握手过程,可以采用以下方法:

  1. 使用线程池或进程池:服务器可以使用线程池或进程池来处理连接请求,避免为每个连接创建新的线程或进程带来的资源开销。当有新的连接请求时,从线程池或进程池中获取一个空闲的线程或进程来处理,提高处理效率。
  2. 采用异步I/O:利用异步I/O技术,服务器在处理连接请求时可以不必阻塞等待,提高服务器的并发处理能力。例如在Python中,可以使用asyncio库实现异步I/O操作,让服务器在等待I/O操作完成的同时可以处理其他连接请求。

长连接和短连接中的四次挥手处理

  1. 长连接:长连接是指在一次连接建立后,双方可以进行多次数据传输,而不会频繁地进行连接建立和关闭操作。在长连接中,四次挥手通常在业务结束时进行。为了保证连接的稳定性,可能会引入心跳机制,定期发送心跳包来检测连接是否正常。如果发现连接异常,及时进行四次挥手关闭连接。
  2. 短连接:短连接是指每次数据传输完成后就立即关闭连接。在短连接中,四次挥手的频率较高。为了提高性能,可以优化关闭连接的过程,例如采用快速关闭机制,在确保数据传输完成的情况下,尽量减少关闭连接的时间开销。

总结三次握手和四次挥手与TCP协议其他机制的关系

  1. 与序列号和确认号的关系:三次握手和四次挥手过程中,序列号和确认号起到了关键作用。在三次握手时,通过序列号和确认号确认双方的初始序列号,为后续的数据传输建立基础。在四次挥手时,序列号和确认号用于确认关闭请求和响应,确保连接安全关闭。序列号和确认号的正确使用保证了TCP协议的可靠性,使数据能够按序传输并被正确接收。
  2. 与滑动窗口机制的关系:滑动窗口机制用于控制数据的流量,提高传输效率。在三次握手建立连接后,双方会协商窗口大小。在数据传输过程中,发送方根据接收方的窗口大小来发送数据,接收方通过调整窗口大小来告知发送方自己的接收能力。在四次挥手过程中,虽然数据传输逐渐停止,但滑动窗口机制仍然在一定程度上影响着连接关闭的过程。例如,在关闭连接前,发送方需要确保已发送的数据都被确认接收,这与滑动窗口机制中对数据确认的要求是一致的。
  3. 与重传机制的关系:重传机制是TCP协议保证可靠性的重要手段。在三次握手和四次挥手过程中,如果某个包丢失或超时未收到确认,重传机制就会发挥作用。例如在三次握手时,如果客户端发送的SYN包未收到服务器的响应,客户端会重发SYN包。在四次挥手时,如果服务器发送的FIN包未收到客户端的ACK确认,服务器会重发FIN包。重传机制确保了三次握手和四次挥手过程的顺利进行,避免因包丢失导致连接建立或关闭失败。

通过深入理解TCP协议中的三次握手与四次挥手,以及它们与其他机制的关系,开发者能够更好地进行网络编程,优化网络应用的性能,确保数据传输的可靠性和稳定性。无论是在传统的C/S架构应用,还是在现代的分布式系统、云计算等领域,TCP协议的这些核心机制都起着至关重要的作用。