TCP和UDP编程详解

2024/1/1

# TCP和UDP编程详解

# 1. TCP vs UDP 协议对比

# 1.1 TCP协议特点

传输控制协议(TCP - Transmission Control Protocol)

  • 面向连接:通信前需要建立连接
  • 可靠传输:保证数据完整性和顺序性
  • 流量控制:防止发送方发送过快
  • 拥塞控制:避免网络拥塞
  • 全双工通信:支持双向数据传输
  • 字节流服务:将数据看作连续的字节流

适用场景:

  • 文件传输
  • 网页浏览
  • 邮件传输
  • 数据库连接
  • 任何需要可靠传输的应用

# 1.2 UDP协议特点

用户数据报协议(UDP - User Datagram Protocol)

  • 无连接:发送数据前无需建立连接
  • 不可靠传输:不保证数据到达和顺序
  • 无流量控制:发送速度不受限制
  • 无拥塞控制:不考虑网络状况
  • 支持一对一、一对多、多对多通信
  • 面向报文:保留报文边界

适用场景:

  • 实时音视频传输
  • 在线游戏
  • DNS查询
  • 广播和组播
  • 对实时性要求高的应用

# 1.3 协议对比表

特性 TCP UDP
连接性 面向连接 无连接
可靠性 可靠 不可靠
速度 较慢 较快
开销
数据边界 无边界(字节流) 有边界(数据报)
适用场景 可靠性要求高 实时性要求高

# 2. TCP编程详解

# 2.1 TCP三次握手和四次挥手

# 三次握手建立连接

客户端                    服务器
   |                        |
   |-------- SYN ---------->|
   |<------- SYN+ACK -------|
   |-------- ACK ---------->|
   |                        |
   |     连接建立完成        |

# 四次挥手断开连接

客户端                    服务器
   |                        |
   |-------- FIN ---------->|
   |<------- ACK -----------|
   |<------- FIN -----------|
   |-------- ACK ---------->|
   |                        |
   |     连接断开完成        |

# 2.2 TCP服务器实现

import java.io.*;
import java.net.*;
import java.util.concurrent.*;

public class TCPServer {
    private static final int PORT = 8080;
    private static final int BACKLOG = 50;
    private ExecutorService threadPool;
    private ServerSocket serverSocket;
    private volatile boolean running = false;
    
    public TCPServer() {
        this.threadPool = Executors.newCachedThreadPool();
    }
    
    public void start() throws IOException {
        serverSocket = new ServerSocket(PORT, BACKLOG);
        running = true;
        
        System.out.println("TCP服务器启动,监听端口: " + PORT);
        
        while (running) {
            try {
                Socket clientSocket = serverSocket.accept();
                threadPool.submit(new TCPClientHandler(clientSocket));
            } catch (IOException e) {
                if (running) {
                    System.err.println("接受连接异常: " + e.getMessage());
                }
            }
        }
    }
    
    public void stop() throws IOException {
        running = false;
        if (serverSocket != null && !serverSocket.isClosed()) {
            serverSocket.close();
        }
        threadPool.shutdown();
        try {
            if (!threadPool.awaitTermination(60, TimeUnit.SECONDS)) {
                threadPool.shutdownNow();
            }
        } catch (InterruptedException e) {
            threadPool.shutdownNow();
        }
    }
    
    private static class TCPClientHandler implements Runnable {
        private final Socket clientSocket;
        
        public TCPClientHandler(Socket clientSocket) {
            this.clientSocket = clientSocket;
        }
        
        @Override
        public void run() {
            String clientAddress = clientSocket.getRemoteSocketAddress().toString();
            System.out.println("新客户端连接: " + clientAddress);
            
            try (BufferedReader reader = new BufferedReader(
                    new InputStreamReader(clientSocket.getInputStream()));
                 PrintWriter writer = new PrintWriter(
                    clientSocket.getOutputStream(), true)) {
                
                // 设置读取超时
                clientSocket.setSoTimeout(30000); // 30秒超时
                
                String message;
                while ((message = reader.readLine()) != null) {
                    System.out.println("[" + clientAddress + "] 收到: " + message);
                    
                    // 处理不同类型的消息
                    String response = processMessage(message);
                    writer.println(response);
                    
                    if ("QUIT".equalsIgnoreCase(message)) {
                        break;
                    }
                }
            } catch (SocketTimeoutException e) {
                System.out.println("客户端超时: " + clientAddress);
            } catch (IOException e) {
                System.err.println("处理客户端异常: " + e.getMessage());
            } finally {
                try {
                    clientSocket.close();
                    System.out.println("客户端断开连接: " + clientAddress);
                } catch (IOException e) {
                    System.err.println("关闭客户端连接异常: " + e.getMessage());
                }
            }
        }
        
        private String processMessage(String message) {
            if (message.startsWith("ECHO ")) {
                return message.substring(5);
            } else if (message.equals("TIME")) {
                return "当前时间: " + new java.util.Date();
            } else if (message.equals("PING")) {
                return "PONG";
            } else {
                return "未知命令: " + message;
            }
        }
    }
    
    public static void main(String[] args) {
        TCPServer server = new TCPServer();
        
        // 添加关闭钩子
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            try {
                System.out.println("正在关闭服务器...");
                server.stop();
            } catch (IOException e) {
                System.err.println("关闭服务器异常: " + e.getMessage());
            }
        }));
        
        try {
            server.start();
        } catch (IOException e) {
            System.err.println("启动服务器失败: " + e.getMessage());
        }
    }
}

# 2.3 TCP客户端实现

import java.io.*;
import java.net.*;
import java.util.Scanner;

public class TCPClient {
    private static final String SERVER_HOST = "localhost";
    private static final int SERVER_PORT = 8080;
    private static final int CONNECT_TIMEOUT = 5000;
    private static final int READ_TIMEOUT = 10000;
    
    public static void main(String[] args) {
        TCPClient client = new TCPClient();
        client.start();
    }
    
    public void start() {
        try (Socket socket = createConnection();
             PrintWriter writer = new PrintWriter(
                socket.getOutputStream(), true);
             BufferedReader reader = new BufferedReader(
                new InputStreamReader(socket.getInputStream()));
             Scanner scanner = new Scanner(System.in)) {
            
            System.out.println("连接到TCP服务器: " + SERVER_HOST + ":" + SERVER_PORT);
            System.out.println("可用命令: ECHO <message>, TIME, PING, QUIT");
            
            String userInput;
            while ((userInput = scanner.nextLine()) != null) {
                writer.println(userInput);
                
                String response = reader.readLine();
                if (response != null) {
                    System.out.println("服务器响应: " + response);
                } else {
                    System.out.println("服务器断开连接");
                    break;
                }
                
                if ("QUIT".equalsIgnoreCase(userInput)) {
                    break;
                }
            }
        } catch (IOException e) {
            System.err.println("客户端异常: " + e.getMessage());
        }
    }
    
    private Socket createConnection() throws IOException {
        Socket socket = new Socket();
        socket.connect(new InetSocketAddress(SERVER_HOST, SERVER_PORT), CONNECT_TIMEOUT);
        socket.setSoTimeout(READ_TIMEOUT);
        return socket;
    }
}

# 3. UDP编程详解

# 3.1 UDP通信特点

  • 无连接:直接发送数据包
  • 数据包独立:每个数据包都是独立的
  • 不保证顺序:数据包可能乱序到达
  • 可能丢失:网络拥塞时可能丢包
  • 广播支持:支持一对多通信

# 3.2 UDP服务器实现

import java.io.IOException;
import java.net.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class UDPServer {
    private static final int PORT = 8081;
    private static final int BUFFER_SIZE = 1024;
    private DatagramSocket socket;
    private ExecutorService threadPool;
    private volatile boolean running = false;
    
    public UDPServer() throws SocketException {
        socket = new DatagramSocket(PORT);
        threadPool = Executors.newCachedThreadPool();
    }
    
    public void start() {
        running = true;
        System.out.println("UDP服务器启动,监听端口: " + PORT);
        
        while (running) {
            try {
                byte[] buffer = new byte[BUFFER_SIZE];
                DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
                
                socket.receive(packet);
                
                // 使用线程池处理请求
                threadPool.submit(() -> handleRequest(packet));
                
            } catch (IOException e) {
                if (running) {
                    System.err.println("接收数据异常: " + e.getMessage());
                }
            }
        }
    }
    
    private void handleRequest(DatagramPacket receivedPacket) {
        try {
            String message = new String(receivedPacket.getData(), 0, 
                receivedPacket.getLength()).trim();
            InetAddress clientAddress = receivedPacket.getAddress();
            int clientPort = receivedPacket.getPort();
            
            System.out.println("收到来自 " + clientAddress + ":" + clientPort + " 的消息: " + message);
            
            // 处理消息
            String response = processUDPMessage(message);
            
            // 发送响应
            byte[] responseData = response.getBytes();
            DatagramPacket responsePacket = new DatagramPacket(
                responseData, responseData.length, clientAddress, clientPort);
            
            socket.send(responsePacket);
            
        } catch (IOException e) {
            System.err.println("处理UDP请求异常: " + e.getMessage());
        }
    }
    
    private String processUDPMessage(String message) {
        if (message.startsWith("ECHO ")) {
            return "UDP_ECHO: " + message.substring(5);
        } else if (message.equals("TIME")) {
            return "UDP_TIME: " + new java.util.Date();
        } else if (message.equals("PING")) {
            return "UDP_PONG";
        } else {
            return "UDP_UNKNOWN: " + message;
        }
    }
    
    public void stop() {
        running = false;
        if (socket != null && !socket.isClosed()) {
            socket.close();
        }
        threadPool.shutdown();
    }
    
    public static void main(String[] args) {
        try {
            UDPServer server = new UDPServer();
            
            Runtime.getRuntime().addShutdownHook(new Thread(() -> {
                System.out.println("正在关闭UDP服务器...");
                server.stop();
            }));
            
            server.start();
        } catch (SocketException e) {
            System.err.println("启动UDP服务器失败: " + e.getMessage());
        }
    }
}

# 3.3 UDP客户端实现

import java.io.IOException;
import java.net.*;
import java.util.Scanner;

public class UDPClient {
    private static final String SERVER_HOST = "localhost";
    private static final int SERVER_PORT = 8081;
    private static final int BUFFER_SIZE = 1024;
    private static final int TIMEOUT = 5000;
    
    public static void main(String[] args) {
        UDPClient client = new UDPClient();
        client.start();
    }
    
    public void start() {
        try (DatagramSocket socket = new DatagramSocket();
             Scanner scanner = new Scanner(System.in)) {
            
            socket.setSoTimeout(TIMEOUT);
            InetAddress serverAddress = InetAddress.getByName(SERVER_HOST);
            
            System.out.println("连接到UDP服务器: " + SERVER_HOST + ":" + SERVER_PORT);
            System.out.println("可用命令: ECHO <message>, TIME, PING, quit");
            
            String userInput;
            while ((userInput = scanner.nextLine()) != null) {
                if ("quit".equalsIgnoreCase(userInput)) {
                    break;
                }
                
                // 发送数据
                byte[] sendData = userInput.getBytes();
                DatagramPacket sendPacket = new DatagramPacket(
                    sendData, sendData.length, serverAddress, SERVER_PORT);
                socket.send(sendPacket);
                
                // 接收响应
                try {
                    byte[] receiveData = new byte[BUFFER_SIZE];
                    DatagramPacket receivePacket = new DatagramPacket(
                        receiveData, receiveData.length);
                    socket.receive(receivePacket);
                    
                    String response = new String(receivePacket.getData(), 0, 
                        receivePacket.getLength());
                    System.out.println("服务器响应: " + response);
                    
                } catch (SocketTimeoutException e) {
                    System.out.println("响应超时,可能数据包丢失");
                }
            }
        } catch (IOException e) {
            System.err.println("UDP客户端异常: " + e.getMessage());
        }
    }
}

# 4. UDP广播和组播

# 4.1 UDP广播实现

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

public class UDPBroadcastSender {
    private static final int PORT = 8082;
    private static final String BROADCAST_ADDRESS = "255.255.255.255";
    
    public static void main(String[] args) {
        try (DatagramSocket socket = new DatagramSocket()) {
            socket.setBroadcast(true);
            
            String message = "广播消息: " + new java.util.Date();
            byte[] data = message.getBytes();
            
            InetAddress broadcastAddr = InetAddress.getByName(BROADCAST_ADDRESS);
            DatagramPacket packet = new DatagramPacket(
                data, data.length, broadcastAddr, PORT);
            
            socket.send(packet);
            System.out.println("广播消息已发送: " + message);
            
        } catch (IOException e) {
            System.err.println("广播发送异常: " + e.getMessage());
        }
    }
}

public class UDPBroadcastReceiver {
    private static final int PORT = 8082;
    
    public static void main(String[] args) {
        try (DatagramSocket socket = new DatagramSocket(PORT)) {
            System.out.println("等待广播消息...");
            
            byte[] buffer = new byte[1024];
            DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
            
            while (true) {
                socket.receive(packet);
                String message = new String(packet.getData(), 0, packet.getLength());
                System.out.println("收到广播: " + message + 
                    " 来自: " + packet.getAddress());
            }
        } catch (IOException e) {
            System.err.println("广播接收异常: " + e.getMessage());
        }
    }
}

# 4.2 UDP组播实现

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

public class UDPMulticastSender {
    private static final String MULTICAST_ADDRESS = "224.0.0.1";
    private static final int PORT = 8083;
    
    public static void main(String[] args) {
        try (MulticastSocket socket = new MulticastSocket()) {
            InetAddress group = InetAddress.getByName(MULTICAST_ADDRESS);
            
            String message = "组播消息: " + new java.util.Date();
            byte[] data = message.getBytes();
            
            DatagramPacket packet = new DatagramPacket(
                data, data.length, group, PORT);
            
            socket.send(packet);
            System.out.println("组播消息已发送: " + message);
            
        } catch (IOException e) {
            System.err.println("组播发送异常: " + e.getMessage());
        }
    }
}

public class UDPMulticastReceiver {
    private static final String MULTICAST_ADDRESS = "224.0.0.1";
    private static final int PORT = 8083;
    
    public static void main(String[] args) {
        try (MulticastSocket socket = new MulticastSocket(PORT)) {
            InetAddress group = InetAddress.getByName(MULTICAST_ADDRESS);
            socket.joinGroup(group);
            
            System.out.println("已加入组播组: " + MULTICAST_ADDRESS);
            
            byte[] buffer = new byte[1024];
            
            while (true) {
                DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
                socket.receive(packet);
                
                String message = new String(packet.getData(), 0, packet.getLength());
                System.out.println("收到组播: " + message + 
                    " 来自: " + packet.getAddress());
            }
        } catch (IOException e) {
            System.err.println("组播接收异常: " + e.getMessage());
        }
    }
}

# 5. 协议选择指南

# 5.1 选择TCP的场景

  • 文件传输:需要保证数据完整性
  • Web应用:HTTP/HTTPS协议基于TCP
  • 数据库连接:需要可靠的数据传输
  • 邮件传输:SMTP、POP3、IMAP等协议
  • 远程登录:SSH、Telnet等

# 5.2 选择UDP的场景

  • 实时音视频:对延迟敏感,可容忍少量丢包
  • 在线游戏:需要快速响应
  • DNS查询:简单的请求-响应模式
  • 广播通信:一对多通信
  • 传感器数据:数据量大,实时性要求高

# 5.3 性能对比测试

import java.io.*;
import java.net.*;
import java.util.concurrent.CountDownLatch;

public class ProtocolPerformanceTest {
    private static final int MESSAGE_COUNT = 10000;
    private static final String TEST_MESSAGE = "Performance Test Message";
    
    public static void main(String[] args) {
        System.out.println("协议性能测试");
        
        // TCP性能测试
        long tcpTime = testTCPPerformance();
        System.out.println("TCP发送 " + MESSAGE_COUNT + " 条消息耗时: " + tcpTime + "ms");
        
        // UDP性能测试
        long udpTime = testUDPPerformance();
        System.out.println("UDP发送 " + MESSAGE_COUNT + " 条消息耗时: " + udpTime + "ms");
        
        System.out.println("UDP比TCP快 " + (tcpTime - udpTime) + "ms");
    }
    
    private static long testTCPPerformance() {
        long startTime = System.currentTimeMillis();
        
        try (Socket socket = new Socket("localhost", 8080);
             PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
             BufferedReader reader = new BufferedReader(
                new InputStreamReader(socket.getInputStream()))) {
            
            for (int i = 0; i < MESSAGE_COUNT; i++) {
                writer.println(TEST_MESSAGE + i);
                reader.readLine(); // 读取响应
            }
        } catch (IOException e) {
            System.err.println("TCP测试异常: " + e.getMessage());
        }
        
        return System.currentTimeMillis() - startTime;
    }
    
    private static long testUDPPerformance() {
        long startTime = System.currentTimeMillis();
        
        try (DatagramSocket socket = new DatagramSocket()) {
            InetAddress address = InetAddress.getByName("localhost");
            
            for (int i = 0; i < MESSAGE_COUNT; i++) {
                String message = TEST_MESSAGE + i;
                byte[] data = message.getBytes();
                DatagramPacket packet = new DatagramPacket(
                    data, data.length, address, 8081);
                socket.send(packet);
            }
        } catch (IOException e) {
            System.err.println("UDP测试异常: " + e.getMessage());
        }
        
        return System.currentTimeMillis() - startTime;
    }
}

# 6. 总结

本文详细介绍了TCP和UDP两种传输协议的特点和编程实现:

# TCP编程要点:

  1. 面向连接,需要建立和断开连接
  2. 提供可靠的数据传输
  3. 适用于对数据完整性要求高的应用
  4. 需要处理连接管理和异常情况

# UDP编程要点:

  1. 无连接,直接发送数据包
  2. 传输效率高但不保证可靠性
  3. 支持广播和组播通信
  4. 适用于实时性要求高的应用

# 最佳实践:

  1. 根据应用需求选择合适的协议
  2. 合理设置超时和缓冲区
  3. 实现适当的错误处理和重试机制
  4. 考虑网络环境和性能要求

在实际开发中,还需要考虑安全性、扩展性等因素,可能需要结合使用多种网络编程技术来构建完整的网络应用系统。