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编程要点:
- 面向连接,需要建立和断开连接
- 提供可靠的数据传输
- 适用于对数据完整性要求高的应用
- 需要处理连接管理和异常情况
# UDP编程要点:
- 无连接,直接发送数据包
- 传输效率高但不保证可靠性
- 支持广播和组播通信
- 适用于实时性要求高的应用
# 最佳实践:
- 根据应用需求选择合适的协议
- 合理设置超时和缓冲区
- 实现适当的错误处理和重试机制
- 考虑网络环境和性能要求
在实际开发中,还需要考虑安全性、扩展性等因素,可能需要结合使用多种网络编程技术来构建完整的网络应用系统。