面试题-TCP和UDP的区别,TCP为什么是三次握手,不是两次?

TCP和UDP的区别

TCP(传输控制协议)和UDP(用户数据报协议)是网络通信中两种重要的传输层协议。它们都用于在网络中传输数据,但设计目标和实现方式有很大不同。以下是它们的详细对比:

1. 连接类型

  • TCP:是一种面向连接的协议。在数据传输之前,通信双方必须通过一系列的握手过程建立一个可靠的连接。这个过程类似于打电话,双方必须先拨通电话并确认对方接听后,才能开始交流。TCP的这种机制确保了通信的可靠性,但需要额外的开销来建立和维护连接。
  • UDP:是一种无连接的协议。它不需要事先建立连接,直接发送数据报即可。这种方式类似于寄送明信片,不需要提前确认收件人是否准备好接收,直接投递即可。UDP的这种机制使得它在实时性要求高的场景中表现出色,但牺牲了可靠性。

2. 可靠性

  • TCP:提供高度可靠的传输服务。它通过确认机制(ACK)、重传机制、拥塞控制等技术,确保数据的完整性和顺序性。如果数据丢失或出错,TCP会自动重传,直到数据正确到达对方为止。这种机制使得TCP非常适合对数据完整性要求高的场景,例如文件传输、网页浏览等。
  • UDP:不保证数据的可靠传输,也不进行错误检测和纠正。数据报可能丢失、重复或乱序到达,但UDP不会进行干预。这种机制适用于对实时性要求高的场景,例如视频流、语音通话或在线游戏。这些场景中,即使出现少量数据丢失,也不会对用户体验产生太大影响。

3. 性能

  • TCP:由于需要建立连接、确认数据和重传机制,TCP的延迟相对较高。它适合对数据完整性要求高的场景,例如文件传输或网页浏览。TCP的可靠性是以牺牲一定的性能为代价的。
  • UDP:传输速度快,延迟低,适合对实时性要求高的场景,例如视频流、语音通话或在线游戏。UDP的高性能是通过牺牲可靠性来实现的。

4. 数据传输单位

  • TCP:以字节流的形式传输数据,不保留数据边界。这意味着发送端和接收端需要自行处理数据的分段和重组。TCP的这种机制使得它在传输大量数据时表现出色,但需要额外的处理来确保数据的完整性和顺序性。
  • UDP:以数据报的形式传输数据,每个数据报大小有限制(通常不超过64KB)。这种方式简化了数据传输过程,但对数据报的大小有限制。UDP的这种机制使得它在传输小块数据时表现出色,但不适合传输大量数据。

5. 应用场景

  • TCP:适用于对可靠性要求高的场景,例如HTTP/HTTPS(网页浏览)、FTP(文件传输)、SMTP(邮件发送)等。这些应用需要确保数据完整无误地到达对方。
  • UDP:适用于对实时性要求高的场景,例如VoIP(语音通话)、视频会议、在线游戏等。这些应用更注重数据的快速传输,即使出现少量数据丢失,也不会对用户体验产生太大影响。

TCP为什么是三次握手,而不是两次

TCP的三次握手是建立可靠连接的关键过程。通过三次握手,TCP能够确保通信双方都准备好进行数据传输,并且能够有效避免错误连接的建立。以下是三次握手的详细过程和原因:

三次握手的过程

  1. 第一次握手(SYN)
  • 客户端向服务器发送一个SYN(同步序列号)报文,表示请求建立连接。此时客户端进入SYN-SENT状态。
  • 作用:客户端告诉服务器自己的初始序列号(ISN),并询问服务器是否准备好建立连接。这相当于客户端在说:“你好,我想和你建立连接,这是我的序列号。”
  • 示例:客户端发送一个SYN报文,序列号为100。
  1. 第二次握手(SYN-ACK)
  • 服务器收到客户端的SYN报文后,回复一个SYN-ACK报文。这个报文包含两部分:
    • 对客户端的SYN报文进行确认(ACK),表示收到客户端的请求。
    • 同时发送自己的SYN报文,告诉客户端自己的初始序列号。
  • 服务器进入SYN-RCVD状态。
  • 作用:服务器确认客户端的请求,并告知自己的初始序列号。这相当于服务器在说:“我收到了你的请求,这是我的序列号,我们可以开始建立连接了。”
  • 示例:服务器收到客户端的SYN报文后,回复一个SYN-ACK报文,确认客户端的序列号100,同时发送自己的序列号200。
  1. 第三次握手(ACK)
  • 客户端收到服务器的SYN-ACK报文后,回复一个ACK报文,确认收到服务器的SYN报文。
  • 客户端进入ESTABLISHED状态,服务器也进入ESTABLISHED状态,连接建立完成。
  • 作用:客户端确认收到服务器的SYN报文,双方进入稳定连接状态。这相当于客户端在说:“我收到了你的响应,现在我们可以开始通信了。”
  • 示例:客户端收到服务器的SYN-ACK报文后,回复一个ACK报文,确认服务器的序列号200。此时,双方都认为连接已经建立。

为什么不是两次握手?

如果只有两次握手,可能会出现以下问题:

  1. 无法确认客户端是否收到服务器的响应:如果只有两次握手,客户端发送SYN后,服务器回复SYN-ACK,但客户端可能因为网络问题没有收到这个响应。此时服务器会认为连接已经建立,但客户端却不知道,导致连接状态不一致。
  2. 避免已失效的连接请求干扰:三次握手可以有效避免因为网络延迟或丢包导致的旧连接请求干扰。例如,客户端可能在很久之前发送了一个SYN报文,但因为网络问题没有完成连接建立。如果只有两次握手,服务器可能会误以为这是一个新的连接请求,从而导致错误的连接建立。

通过三次握手,TCP可以确保双方都准备好建立连接,并且能够有效避免上述问题,从而实现可靠连接的建立。


Java示例代码

1. TCP示例代码

以下是一个简单的TCP服务器和客户端的示例代码,展示了如何使用Java的ServerSocketSocket类建立TCP连接。

TCP服务器代码:

import java.io.*;
import java.net.*;

public class TCPServer {
    public static void main(String[] args) {
        int port = 12345;

        try (ServerSocket serverSocket = new ServerSocket(port)) {
            System.out.println("TCP服务器已启动,监听端口:" + port);

            // 等待客户端连接
            Socket clientSocket = serverSocket.accept();
            System.out.println("客户端已连接:" + clientSocket.getInetAddress());

            // 获取输入流和输出流
            BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
            PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);

            // 读取客户端消息
            String message = in.readLine();
            System.out.println("收到客户端消息:" + message);

            // 向客户端发送响应
            out.println("服务器已收到消息:" + message);

            // 关闭连接
            clientSocket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

TCP客户端代码:

import java.io.*;
import java.net.*;

public class TCPClient {
    public static void main(String[] args) {
        String host = "localhost";
        int port = 12345;

        try (Socket serverSocket = new Socket(host, port)) {
            System.out.println("已连接到服务器:" + host + ":" + port);

            // 获取输入流和输出流
            BufferedReader in = new BufferedReader(new InputStreamReader(serverSocket.getInputStream()));
            PrintWriter out = new PrintWriter(serverSocket.getOutputStream(), true);

            // 向服务器发送消息
            out.println("你好,服务器!");

            // 读取服务器响应
            String response = in.readLine();
            System.out.println("服务器响应:" + response);

            // 关闭连接
            serverSocket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

2. UDP示例代码

以下是一个简单的UDP服务器和客户端的示例代码,展示了如何使用Java的DatagramSocketDatagramPacket类发送和接收UDP数据报。

UDP服务器代码:

import java.io.*;
import java.net.*;

public class UDPServer {
    public static void main(String[] args) {
        int port = 12345;

        try (DatagramSocket serverSocket = new DatagramSocket(port)) {
            System.out.println("UDP服务器已启动,监听端口:" + port);

            // 创建数据报缓冲区
            byte[] buffer = new byte[1024];
            DatagramPacket packet = new DatagramPacket(buffer, buffer.length);

            // 等待接收数据报
            serverSocket.receive(packet);
            String message = new String(packet.getData(), 0, packet.getLength());
            System.out.println("收到客户端消息:" + message);

            // 发送响应
            String response = "服务器已收到消息";
            buffer = response.getBytes();
            DatagramPacket responsePacket = new DatagramPacket(buffer, buffer.length, packet.getAddress(), packet.getPort());
            serverSocket.send(responsePacket);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

UDP客户端代码:

import java.io.*;
import java.net.*;

public class UDPClient {
    public static void main(String[] args) {
        String host = "localhost";
        int port = 12345;

        try (DatagramSocket clientSocket = new DatagramSocket()) {
            System.out.println("UDP客户端已启动");

            // 创建数据报
            String message = "你好,服务器!";
            byte[] buffer = message.getBytes();
            InetAddress address = InetAddress.getByName(host);
            DatagramPacket packet = new DatagramPacket(buffer, buffer.length, address, port);

            // 发送数据报
            clientSocket.send(packet);

            // 接收服务器响应
            buffer = new byte[1024];
            DatagramPacket responsePacket = new DatagramPacket(buffer, buffer.length);
            clientSocket.receive(responsePacket);
            String response = new String(responsePacket.getData(), 0, responsePacket.getLength());
            System.out.println("服务器响应:" + response);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
posted @   软件职业规划  阅读(29)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示