面试题:三次握手,为什么要三次而不是两次四次?
三次握手的概念
三次握手是TCP协议用于建立连接的一种机制。它涉及到客户端和服务器之间的三个步骤,确保双方都能够正常通信。
- 第一次握手(SYN): 客户端向服务器发送一个SYN(同步)标志,表示客户端希望建立连接。
- 第二次握手(SYN + ACK): 服务器接收到客户端的SYN后,回应一个带有SYN和ACK(确认)标志的报文,表示服务器已准备好接受连接请求。
- 第三次握手(ACK): 客户端接收到服务器的响应后,发送一个带有ACK标志的报文,表示客户端也已准备好建立连接。
这样,通过三次握手,双方确认彼此都能够正常通信,建立了可靠的连接。
为什么是三次握手?
为什么不是两次握手或四次握手呢?这涉及到建立连接的可靠性和防止网络中的不确定性。让我通过一个实际的案例来理解为什么三次握手是必要的。
案例分析:网络延迟引发的问题
假设我只有两次握手,而不是三次。客户端发送SYN,服务器回应SYN + ACK,看起来连接已经建立。但在这之后,由于网络延迟或其他原因,客户端并没有收到服务器的确认,导致客户端以为连接已建立,而服务器并不知情。
这种情况下,客户端和服务器之间的连接状态将变得不一致,可能导致各种问题,如资源浪费、连接超时等。为了防止这种情况,引入第三次握手可以确保双方都能够确认连接已经建立,避免了不确定性带来的问题。
代码示例:Python中的三次握手
让我通过一个简单的Python代码示例来演示三次握手的过程。
# 客户端代码 import socket client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect(('server_ip', 8080)) # 第一次握手 client.send(b'SYN') # 第二次握手 response = client.recv(1024) if b'SYN-ACK' in response: client.send(b'ACK') print('Connection established successfully!') else: print('Connection failed.') client.close()
# 服务器端代码 import socket server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind(('server_ip', 8080)) server.listen(1) client, addr = server.accept() # 第一次握手 request = client.recv(1024) if b'SYN' in request: client.send(b'SYN-ACK') # 第三次握手 response = client.recv(1024) if b'ACK' in response: print('Connection established successfully!') else: print('Connection failed.') client.close() server.close()
安全性考虑
三次握手在一定程度上提高了连接的安全性。通过要求客户端和服务器都发送和确认连接请求,它减少了未经授权的连接建立的可能性。如果只有两次握手,可能会容易受到一些网络攻击,例如SYN洪泛攻击,因为服务器无法确认客户端是否真的要建立连接。
可靠性和状态同步
三次握手的每一步都具有明确定义的状态。第一次握手表示客户端希望建立连接,第二次握手表示服务器接受连接,并准备好接收数据,第三次握手表示客户端也确认连接建立。这种状态同步确保了双方都了解连接的当前状态。
处理网络延迟
三次握手的过程中,如果某一步的消息由于网络延迟未能及时到达,发送方会在一段时间后重新发送。这种机制有助于处理因为网络延迟引起的消息丢失,确保了连接的可靠性。
为什么不是两次握手?
如果只有两次握手,存在一些潜在的问题。例如,在两次握手中,服务器接收到连接请求后立即回应,这时连接就建立了。但如果这个回应由于网络延迟而在一段时间后才到达,客户端就无法得知连接是否真的建立成功。这可能导致客户端错误地认为连接已经建立,而服务器并不知情。
为什么不是四次握手?
四次握手是在连接关闭时使用的,与连接建立时的三次握手不同。在连接关闭时,需要双方确认彼此都已准备好断开连接。而在连接建立时,通过三次握手就能够确保连接的可靠性和安全性。
TCP连接的状态转换图
TCP连接的状态转换图描述了连接的生命周期,包括连接的建立、数据传输和连接的终止。在三次握手的背景下,我将关注连接的建立阶段。
+---------+ +---------+ CLOSED | LISTEN |----------------->| CLOSED | +---------+ Passive +---------+ | ^ CLOSE OPEN | | / v | +--------+ +---------+ +->| IDLE | | SYN- | | / +-----+ | SENT |<----+ +---------+ | | | | Active | | | v | +---------+ | +----------------| SYN- | | | Passive | RECEIVED |<------+ | OPEN +---------+ v | | | Close | | | +------| v | | +---------+ | +--->| FIN- | +------------>| WAIT- | | | CLOSE | v +---------+ +-------------+ | CLOSED | +-------------+
详细步骤:建立连接
让我通过更详细的步骤来理解三次握手的建立连接过程。
- 客户端向服务器发送SYN: 客户端初始处于CLOSED状态,向服务器发送一个SYN标志的TCP报文,进入SYN-SENT状态。
- 服务器接收SYN并回应SYN+ACK: 服务器接收到客户端的SYN后,回应一个带有SYN和ACK标志的报文,表示服务器已准备好接受连接。服务器进入SYN-RECEIVED状态。
- 客户端发送ACK: 客户端接收到服务器的响应后,发送一个带有ACK标志的报文,表示客户端也确认连接建立。客户端和服务器都进入ESTABLISHED状态,连接建立成功。
详细步骤:关闭连接
当连接建立后,双方通信完成后可能需要关闭连接。下面是关闭连接的详细步骤:
- 主动关闭方发送FIN: 主动关闭方(可以是客户端或服务器)发送一个带有FIN标志的TCP报文,表示它已经完成了数据的发送。
- 被动关闭方接收FIN并回应ACK: 被动关闭方接收到FIN后,回应一个带有ACK标志的报文,表示接收到了关闭请求。此时,被动关闭方进入CLOSE-WAIT状态。
- 主动关闭方接收ACK: 主动关闭方接收到ACK后,进入FIN-WAIT-1状态。
- 被动关闭方发送FIN: 被动关闭方也可能在完成数据的发送后发送FIN,进入LAST-ACK状态。
- 主动关闭方回应ACK: 主动关闭方接收到被动关闭方的FIN后,回应一个ACK,进入TIME-WAIT状态。
- 连接关闭: 在一定时间后,主动关闭方进入CLOSED状态,连接关闭。
为什么不是两次握手或四次握手?
两次握手存在的问题已经在前面的部分讨论过,容易导致不确定性。而四次握手是在连接关闭时使用的,与连接建立时的三次握手不同。三次握手在确保连接的可靠性、安全性和性能优化方面已经被广泛接受。