💡 摘要:你是否好奇网络应用如何实现实时通信?是否想了解HTTP客户端背后的工作原理?是否对Socket编程感到神秘?
别担心,网络编程是Java中处理网络通信的核心能力,从底层的Socket到高级的HTTP客户端都在Java生态中有着完善的支持。
本文将带你从网络基础概念讲起,理解TCP和UDP协议的区别。然后深入Socket编程,学习如何实现客户端和服务器端的双向通信。
接着探索HTTP客户端开发,从传统的HttpURLConnection到现代的HttpClient。最后通过实战案例展示文件传输、聊天应用、API调用等常见场景。从协议原理到代码实现,从阻塞IO到NIO非阻塞,让你全面掌握Java网络编程的精髓。文末附性能优化和面试高频问题,助你构建高性能网络应用。
一、网络编程基础概念
1. TCP vs UDP:两种传输协议
TCP(传输控制协议)特点:
- ✅ 面向连接:需要建立连接后才能通信
- ✅ 可靠传输:保证数据顺序和完整性
- ✅ 流量控制:避免发送方淹没接收方
- ✅ 适用场景:文件传输、Web浏览、邮件等
UDP(用户数据报协议)特点:
- ✅ 无连接:直接发送数据包
- ✅ 不可靠:不保证顺序和送达
- ✅ 高效:开销小,延迟低
- ✅ 适用场景:视频流、游戏、DNS查询等
协议选择指南:
java
// TCP适合需要可靠传输的场景
// UDP适合实时性要求高的场景
if (需要可靠传输 && 允许一定延迟) {
选择TCP();
} else if (实时性优先 && 允许数据丢失) {
选择UDP();
}
2. 网络基础术语
关键概念解释:
java
// IP地址:设备的网络标识
String ipAddress = "192.168.1.100"; // IPv4
String ipv6Address = "2001:0db8:85a3:0000:0000:8a2e:0370:7334"; // IPv6
// 端口号:应用程序的标识(0-65535)
int port = 8080; // Web服务常用端口
int databasePort = 3306; // MySQL端口
// URL:统一资源定位符
URL url = new URL("https://api.example.com:443/data?param=value");
二、Socket编程:TCP通信实现
1. 服务器端编程
基础服务器实现:
java
public class BasicServer {
public static void main(String[] args) {
// 使用try-with-resources确保资源关闭
try (ServerSocket serverSocket = new ServerSocket(8080)) {
System.out.println("服务器启动,监听端口 8080...");
while (true) {
// 等待客户端连接(阻塞方法)
Socket clientSocket = serverSocket.accept();
System.out.println("客户端连接: " + clientSocket.getInetAddress());
// 处理客户端请求
handleClient(clientSocket);
}
} catch (IOException e) {
System.err.println("服务器错误: " + e.getMessage());
}
}
private static void handleClient(Socket clientSocket) {
try (InputStream input = clientSocket.getInputStream();
OutputStream output = clientSocket.getOutputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(input));
PrintWriter writer = new PrintWriter(output, true)) {
// 读取客户端请求
String request = reader.readLine();
System.out.println("收到请求: " + request);
// 处理请求并响应
String response = processRequest(request);
writer.println(response);
System.out.println("发送响应: " + response);
} catch (IOException e) {
System.err.println("处理客户端错误: " + e.getMessage());
} finally {
try {
clientSocket.close();
} catch (IOException e) {
System.err.println("关闭客户端连接错误: " + e.getMessage());
}
}
}
private static String processRequest(String request) {
// 简单的请求处理逻辑
if ("TIME".equalsIgnoreCase(request)) {
return LocalDateTime.now().toString();
} else if ("HELLO".equalsIgnoreCase(request)) {
return "Hello from Server!";
} else {
return "Unknown command: " + request;
}
}
}
多线程服务器:
java
public class MultiThreadedServer {
public static void main(String[] args) {
try (ServerSocket serverSocket = new ServerSocket(8080)) {
System.out.println("多线程服务器启动...");
ExecutorService threadPool = Executors.newFixedThreadPool(10);
while (true) {
Socket clientSocket = serverSocket.accept();
// 为每个客户端创建新线程
threadPool.execute(new ClientHandler(clientSocket));
}
} catch (IOException e) {
System.err.println("服务器错误: " + e.getMessage());
}
}
static class ClientHandler implements Runnable {
private final Socket clientSocket;
public ClientHandler(Socket socket) {
this.clientSocket = socket;
}
@Override
public void run() {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(clientSocket.getInputStream()));
PrintWriter writer = new PrintWriter(clientSocket.getOutputStream(), true)) {
String request;
while ((request = reader.readLine()) != null) {
System.out.println(Thread.currentThread().getName() + " 处理: " + request);
String response = "Echo: " + request;
writer.println(response);
if ("BYE".equalsIgnoreCase(request)) {
break;
}
}
} catch (IOException e) {
System.err.println("客户端处理错误: " + e.getMessage());
} finally {
try {
clientSocket.close();
} catch (IOException e) {
System.err.println("关闭连接错误: " + e.getMessage());
}
}
}
}
}
2. 客户端编程
基础客户端实现:
java
public class BasicClient {
public static void main(String[] args) {
String hostname = "localhost";
int port = 8080;
try (Socket socket = new Socket(hostname, port);
PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
BufferedReader reader = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
BufferedReader consoleReader = new BufferedReader(
new InputStreamReader(System.in))) {
System.out.println("连接到服务器 " + hostname + ":" + port);
String userInput;
while ((userInput = consoleReader.readLine()) != null) {
// 发送请求到服务器
writer.println(userInput);
// 读取服务器响应
String response = reader.readLine();
System.out.println("服务器响应: " + response);
if ("BYE".equalsIgnoreCase(userInput)) {
break;
}
}
} catch (UnknownHostException e) {
System.err.println("未知主机: " + hostname);
} catch (IOException e) {
System.err.println("IO错误: " + e.getMessage());
}
}
}
带超时设置的客户端:
java
public class TimeoutClient {
public static void main(String[] args) {
String hostname = "example.com";
int port = 80;
int timeout = 5000; // 5秒超时
try {
Socket socket = new Socket();
// 设置连接超时
socket.connect(new InetSocketAddress(hostname, port), timeout);
// 设置读写超时
socket.setSoTimeout(timeout);
try (InputStream input = socket.getInputStream();
OutputStream output = socket.getOutputStream()) {
// 发送HTTP请求
String httpRequest = "GET / HTTP/1.1\r\nHost: " + hostname + "\r\n\r\n";
output.write(httpRequest.getBytes(StandardCharsets.UTF_8));
// 读取响应
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = input.read(buffer)) != -1) {
System.out.write(buffer, 0, bytesRead);
}
}
} catch (SocketTimeoutException e) {
System.err.println("连接或读写超时");
} catch (IOException e) {
System.err.println("网络错误: " + e.getMessage());
}
}
}
三、UDP编程:数据报通信
1. UDP服务器端
java
public class UDPServer {
public static void main(String[] args) {
try (DatagramSocket socket = new DatagramSocket(9876)) {
System.out.println("UDP服务器启动,端口 9876");
byte[] receiveBuffer = new byte[1024];
while (true) {
// 接收数据包
DatagramPacket receivePacket = new DatagramPacket(receiveBuffer, receiveBuffer.length);
socket.receive(receivePacket); // 阻塞等待
// 处理接收到的数据
String receivedData = new String(receivePacket.getData(), 0, receivePacket.getLength());
InetAddress clientAddress = receivePacket.getAddress();
int clientPort = receivePacket.getPort();
System.out.println("收到来自 " + clientAddress + ":" + clientPort + " 的数据: " + receivedData);
// 准备响应数据
String responseData = "UDP响应: " + receivedData.toUpperCase();
byte[] responseBuffer = responseData.getBytes(StandardCharsets.UTF_8);
// 发送响应
DatagramPacket responsePacket = new DatagramPacket(
responseBuffer, responseBuffer.length, clientAddress, clientPort);
socket.send(responsePacket);
}
} catch (IOException e) {
System.err.println("UDP服务器错误: " + e.getMessage());
}
}
}
2. UDP客户端
java
public class UDPClient {
public static void main(String[] args) {
String hostname = "localhost";
int port = 9876;
try (DatagramSocket socket = new DatagramSocket();
BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in))) {
InetAddress serverAddress = InetAddress.getByName(hostname);
System.out.println("UDP客户端就绪,输入消息发送到服务器 (输入 'exit' 退出)");
String message;
while ((message = consoleReader.readLine()) != null) {
if ("exit".equalsIgnoreCase(message)) {
break;
}
// 发送数据
byte[] sendBuffer = message.getBytes(StandardCharsets.UTF_8);
DatagramPacket sendPacket = new DatagramPacket(sendBuffer, sendBuffer.length, serverAddress, port);
socket.send(sendPacket);
// 接收响应
byte[] receiveBuffer = new byte[1024];
DatagramPacket receivePacket = new DatagramPacket(receiveBuffer, receiveBuffer.length);
socket.receive(receivePacket);
String response = new String(receivePacket.getData(), 0, receivePacket.getLength());
System.out.println("服务器响应: " + response);
}
} catch (IOException e) {
System.err.println("UDP客户端错误: " + e.getMessage());
}
}
}
四、HTTP客户端开发
1. 传统的HttpURLConnection
GET请求示例:
java
public class HttpUrlConnectionExample {
public static void main(String[] args) {
String urlString = "https://jsonplaceholder.typicode.com/posts/1";
try {
URL url = new URL(urlString);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
// 设置请求方法
connection.setRequestMethod("GET");
connection.setConnectTimeout(5000);
connection.setReadTimeout(5000);
// 设置请求头
connection.setRequestProperty("Accept", "application/json");
connection.setRequestProperty("User-Agent", "Java-HTTP-Client");
// 获取响应码
int responseCode = connection.getResponseCode();
System.out.println("响应码: " + responseCode);
if (responseCode == HttpURLConnection.HTTP_OK) {
// 读取响应内容
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(connection.getInputStream()))) {
String line;
StringBuilder response = new StringBuilder();
while ((line = reader.readLine()) != null) {
response.append(line);
}
System.out.println("响应内容: " + response.toString());
// 可以解析JSON响应
}
} else {
System.out.println("请求失败,响应码: " + responseCode);
}
connection.disconnect();
} catch (IOException e) {
System.err.println("HTTP请求错误: " + e.getMessage());
}
}
}
POST请求示例:
java
public class HttpPostExample {
public static void main(String[] args) {
String urlString = "https://jsonplaceholder.typicode.com/posts";
try {
URL url = new URL(urlString);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
// 设置POST请求
connection.setRequestMethod("POST");
connection.setDoOutput(true); // 允许输出
connection.setRequestProperty("Content-Type", "application/json");
connection.setRequestProperty("Accept", "application/json");
// 准备请求体
String jsonInputString = "{\"title\": \"foo\", \"body\": \"bar\", \"userId\": 1}";
// 发送请求体
try (OutputStream os = connection.getOutputStream()) {
byte[] input = jsonInputString.getBytes(StandardCharsets.UTF_8);
os.write(input, 0, input.length);
}
// 读取响应
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_CREATED) {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(connection.getInputStream()))) {
String line;
StringBuilder response = new StringBuilder();
while ((line = reader.readLine()) != null) {
response.append(line);
}
System.out.println("创建成功: " + response.toString());
}
} else {
System.out.println("POST请求失败,响应码: " + responseCode);
}
} catch (IOException e) {
System.err.println("HTTP POST错误: " + e.getMessage());
}
}
}
2. 现代的HttpClient(Java 11+)
HttpClient基础使用:
java
public class ModernHttpClient {
public static void main(String[] args) {
// 创建HttpClient
HttpClient client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2) // HTTP/2支持
.connectTimeout(Duration.ofSeconds(5)) // 连接超时
.followRedirects(HttpClient.Redirect.NORMAL) // 重定向策略
.build();
// 创建GET请求
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://jsonplaceholder.typicode.com/posts/1"))
.header("Accept", "application/json")
.header("User-Agent", "Java-11-HttpClient")
.GET()
.build();
try {
// 发送请求
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
// 处理响应
System.out.println("状态码: " + response.statusCode());
System.out.println("响应头: " + response.headers());
System.out.println("响应体: " + response.body());
} catch (IOException | InterruptedException e) {
System.err.println("请求失败: " + e.getMessage());
}
}
}
异步HTTP请求:
java
public class AsyncHttpClient {
public static void main(String[] args) {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/data"))
.timeout(Duration.ofSeconds(10))
.build();
// 发送异步请求
CompletableFuture<HttpResponse<String>> future =
client.sendAsync(request, HttpResponse.BodyHandlers.ofString());
// 处理异步结果
future.thenApply(HttpResponse::body)
.thenAccept(responseBody -> {
System.out.println("异步响应: " + responseBody);
// 可以在这里更新UI或处理数据
})
.exceptionally(e -> {
System.err.println("异步请求错误: " + e.getMessage());
return null;
});
// 等待异步操作完成(在实际应用中可能不需要)
future.join();
System.out.println("主线程继续执行...");
}
}
五、实战应用案例
1. 文件传输服务器
TCP文件传输:
java
public class FileTransferServer {
public static void main(String[] args) {
int port = 9090;
try (ServerSocket serverSocket = new ServerSocket(port)) {
System.out.println("文件传输服务器启动,端口: " + port);
while (true) {
Socket clientSocket = serverSocket.accept();
new Thread(new FileTransferHandler(clientSocket)).start();
}
} catch (IOException e) {
System.err.println("服务器错误: " + e.getMessage());
}
}
static class FileTransferHandler implements Runnable {
private final Socket clientSocket;
public FileTransferHandler(Socket socket) {
this.clientSocket = socket;
}
@Override
public void run() {
try (DataInputStream dis = new DataInputStream(clientSocket.getInputStream());
DataOutputStream dos = new DataOutputStream(clientSocket.getOutputStream())) {
// 读取文件名和文件大小
String fileName = dis.readUTF();
long fileSize = dis.readLong();
System.out.println("接收文件: " + fileName + " (" + fileSize + " bytes)");
// 创建本地文件
Path outputPath = Paths.get("received_" + fileName);
try (OutputStream fileOutput = Files.newOutputStream(outputPath)) {
byte[] buffer = new byte[8192];
long totalRead = 0;
int bytesRead;
while (totalRead < fileSize &&
(bytesRead = dis.read(buffer)) != -1) {
fileOutput.write(buffer, 0, bytesRead);
totalRead += bytesRead;
// 显示进度
double progress = (double) totalRead / fileSize * 100;
System.out.printf("进度: %.2f%%\n", progress);
}
}
// 发送确认消息
dos.writeUTF("文件接收成功: " + fileName);
} catch (IOException e) {
System.err.println("文件传输错误: " + e.getMessage());
} finally {
try {
clientSocket.close();
} catch (IOException e) {
System.err.println("关闭连接错误: " + e.getMessage());
}
}
}
}
}
2. 简单的聊天应用
多客户端聊天服务器:
java
public class ChatServer {
private static final Set<PrintWriter> clientWriters = Collections.synchronizedSet(new HashSet<>());
public static void main(String[] args) {
try (ServerSocket serverSocket = new ServerSocket(8888)) {
System.out.println("聊天服务器启动,端口 8888");
while (true) {
Socket clientSocket = serverSocket.accept();
new Thread(new ClientHandler(clientSocket)).start();
}
} catch (IOException e) {
System.err.println("服务器错误: " + e.getMessage());
}
}
static class ClientHandler implements Runnable {
private final Socket clientSocket;
private PrintWriter writer;
public ClientHandler(Socket socket) {
this.clientSocket = socket;
}
@Override
public void run() {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(clientSocket.getInputStream()))) {
writer = new PrintWriter(clientSocket.getOutputStream(), true);
clientWriters.add(writer);
String message;
while ((message = reader.readLine()) != null) {
System.out.println("收到消息: " + message);
broadcastMessage(message);
}
} catch (IOException e) {
System.err.println("客户端错误: " + e.getMessage());
} finally {
if (writer != null) {
clientWriters.remove(writer);
}
try {
clientSocket.close();
} catch (IOException e) {
System.err.println("关闭连接错误: " + e.getMessage());
}
}
}
private void broadcastMessage(String message) {
synchronized (clientWriters) {
for (PrintWriter writer : clientWriters) {
writer.println(message);
}
}
}
}
}
六、性能优化与最佳实践
1. 连接池管理
HttpClient连接池:
java
public class ConnectionPoolExample {
public static void main(String[] args) {
// 配置连接池
HttpClient client = HttpClient.newBuilder()
.executor(Executors.newFixedThreadPool(10)) // 线程池
.connectTimeout(Duration.ofSeconds(5))
.connectionTimeout(Duration.ofSeconds(5))
.build();
List<CompletableFuture<Void>> futures = new ArrayList<>();
// 并发发送多个请求
for (int i = 1; i <= 10; i++) {
int id = i;
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://jsonplaceholder.typicode.com/posts/" + id))
.build();
CompletableFuture<Void> future = client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenAccept(response -> {
System.out.println("请求 " + id + " 完成: " + response.statusCode());
});
futures.add(future);
}
// 等待所有请求完成
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
System.out.println("所有请求完成");
}
}
2. 超时与重试机制
智能重试策略:
java
public class RetryHttpClient {
private static final HttpClient client = HttpClient.newHttpClient();
private static final int MAX_RETRIES = 3;
private static final Duration RETRY_DELAY = Duration.ofSeconds(2);
public static HttpResponse<String> sendWithRetry(HttpRequest request) throws Exception {
int attempt = 0;
while (attempt < MAX_RETRIES) {
try {
return client.send(request, HttpResponse.BodyHandlers.ofString());
} catch (IOException e) {
attempt++;
if (attempt == MAX_RETRIES) {
throw e;
}
System.out.println("请求失败,第 " + attempt + " 次重试...");
Thread.sleep(RETRY_DELAY.toMillis());
}
}
throw new Exception("所有重试尝试失败");
}
public static void main(String[] args) {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/data"))
.timeout(Duration.ofSeconds(5))
.build();
try {
HttpResponse<String> response = sendWithRetry(request);
System.out.println("最终响应: " + response.statusCode());
} catch (Exception e) {
System.err.println("请求最终失败: " + e.getMessage());
}
}
}
七、总结:网络编程最佳实践
1. 安全性考虑
安全注意事项:
java
// 1. 验证输入数据
if (!isValidInput(userInput)) {
throw new SecurityException("无效输入");
}
// 2. 使用HTTPS加密通信
HttpClient client = HttpClient.newBuilder()
.sslContext(SSLContext.getDefault())
.build();
// 3. 限制资源使用
serverSocket.setReceiveBufferSize(8192); // 合理设置缓冲区大小
// 4. 处理异常,避免信息泄露
catch (IOException e) {
logger.error("网络错误"); // 不要暴露详细错误信息给客户端
throw new RuntimeException("服务暂时不可用");
}
2. 性能优化建议
优化策略:
- ✅ 使用连接池复用HTTP连接
- ✅ 合理设置超时时间
- ✅ 使用异步非阻塞IO
- ✅ 启用压缩减少数据传输量
- ✅ 使用HTTP/2协议
八、面试高频问题
❓1. TCP和UDP的主要区别是什么?
答:TCP是面向连接的可靠协议,保证数据顺序和完整性;UDP是无连接的不可靠协议,效率更高但可能丢失数据。
❓2. 什么是Socket?它在网络编程中起什么作用?
答:Socket是网络通信的端点,包含了IP地址和端口号。它提供了应用程序与网络协议栈之间的接口。
❓3. HttpClient相比HttpURLConnection有什么优势?
答:HttpClient支持HTTP/2、异步请求、连接池、更好的API设计,是现代Java应用的推荐选择。
❓4. 如何处理网络编程中的超时问题?
答:需要设置连接超时、读取超时,并实现重试机制和熔断器模式。
❓5. 什么是阻塞IO和非阻塞IO?
答:阻塞IO会一直等待数据就绪,非阻塞IO会立即返回并通过回调或轮询处理数据。NIO提供了非阻塞IO支持。