Java 网络编程详解
Java 网络编程是指通过 Java 语言实现计算机之间的网络通信,核心是利用 Java 提供的网络类库(如
java.net 包)操作 TCP/IP 协议,实现数据的发送与接收。无论是客户端与服务器的通信、分布式系统交互,还是网络爬虫等场景,都依赖于网络编程的基础能力。本文将从核心概念、TCP/UDP 编程、高层协议应用到 NIO 进阶,全面解析 Java 网络编程。
一、网络编程基础概念
在开始代码实现前,需先理解几个核心概念,它们是网络通信的 “语言规则”:
- IP 地址:标识网络中唯一的计算机(如
192.168.1.1或www.baidu.com,域名会通过 DNS 解析为 IP)。 - 端口号:标识计算机中运行的某个进程(范围 0-65535,0-1023 为系统端口,如 HTTP 默认 80,建议使用 1024+ 避免冲突)。
- 协议:通信双方的规则约定,Java 网络编程主要基于 TCP 和 UDP 协议:
- TCP:面向连接的可靠协议(三次握手建立连接,四次挥手断开),数据传输有序、不丢失(适合文件传输、登录等)。
- UDP:无连接的不可靠协议(数据以 “数据报” 形式发送,可能丢失或乱序),但速度快(适合视频通话、实时游戏等)。
二、核心类库:Java 网络编程的 “工具包”
Java 的
java.net 包提供了丰富的类,简化了网络通信的实现。核心类如下:| 类 / 接口 | 作用 | 适用协议 |
|---|---|---|
InetAddress |
表示 IP 地址(支持域名解析) | 通用 |
Socket |
客户端 TCP 套接字(建立连接、传输数据) | TCP |
ServerSocket |
服务器端 TCP 套接字(监听连接、接收请求) | TCP |
DatagramSocket |
UDP 套接字(发送 / 接收数据报) | UDP |
DatagramPacket |
UDP 数据报(封装数据、目标地址和端口) | UDP |
URL / URLConnection |
处理高层协议(如 HTTP、FTP) | 应用层协议 |
三、TCP 编程:可靠的连接式通信
TCP 通信需先建立连接(类似打电话 “接通”),再传输数据,最后断开连接。核心流程是 “服务器端监听 → 客户端连接 → 双向通信 → 关闭连接”。
1. TCP 服务器端实现
服务器端需绑定端口并监听客户端连接,每接收到一个连接,通过输入流读取数据,输出流发送响应。
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class TCPServer {
public static void main(String[] args) throws IOException {
// 1. 创建 ServerSocket,绑定端口(如 8888)
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("服务器启动,监听端口 8888...");
// 2. 循环监听客户端连接(实际开发中用多线程处理多个客户端)
while (true) {
// 阻塞等待客户端连接(建立 TCP 三次握手)
Socket clientSocket = serverSocket.accept();
System.out.println("客户端 " + clientSocket.getInetAddress() + " 已连接");
// 3. 获取输入流(读客户端数据)和输出流(向客户端写数据)
try (BufferedReader in = new BufferedReader(
new InputStreamReader(clientSocket.getInputStream()));
PrintWriter out = new PrintWriter(
new OutputStreamWriter(clientSocket.getOutputStream()), true)) {
// 4. 读取客户端消息
String clientMsg;
while ((clientMsg = in.readLine()) != null) {
System.out.println("收到客户端消息:" + clientMsg);
// 5. 向客户端发送响应
out.println("服务器已收到:" + clientMsg);
// 若客户端发送 "exit",断开连接
if ("exit".equals(clientMsg)) {
break;
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 6. 关闭客户端连接
clientSocket.close();
System.out.println("客户端 " + clientSocket.getInetAddress() + " 已断开");
}
}
}
}
2. TCP 客户端实现
客户端需指定服务器 IP 和端口,建立连接后,通过输出流发送数据,输入流接收响应。
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
public class TCPClient {
public static void main(String[] args) throws IOException {
// 1. 创建 Socket,连接服务器(IP 为本地 localhost,端口 8888)
Socket socket = new Socket("localhost", 8888);
System.out.println("已连接服务器");
// 2. 获取输入流(读服务器响应)和输出流(向服务器发数据)
try (BufferedReader in = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(
new OutputStreamWriter(socket.getOutputStream()), true);
Scanner scanner = new Scanner(System.in)) {
// 3. 循环发送消息给服务器
String msg;
while (true) {
System.out.print("请输入消息(输入 exit 退出):");
msg = scanner.nextLine();
// 发送消息
out.println(msg);
// 若输入 exit,退出循环
if ("exit".equals(msg)) {
break;
}
// 4. 接收服务器响应
String serverMsg = in.readLine();
System.out.println("服务器响应:" + serverMsg);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 5. 关闭连接
socket.close();
System.out.println("已断开与服务器的连接");
}
}
}
3. 关键说明
- 多线程处理:上述服务器只能处理一个客户端,实际开发中需为每个
clientSocket启动一个线程(或用线程池),避免阻塞。 - 流的关闭:使用 try-with-resources 自动关闭流和套接字,避免资源泄露。
- 数据传输格式:示例用
readLine()按行读取,需确保客户端和服务器端统一格式(如换行符),复杂场景可使用 JSON 等序列化格式。
四、UDP 编程:高效的无连接通信
UDP 通信无需建立连接,数据以 “数据报” 形式发送,发送方和接收方通过数据报交互(类似发短信,无需对方 “接通”)。
1. UDP 服务器端实现
服务器端通过
DatagramSocket 接收数据报,解析数据后发送响应数据报。import java.net.*;
import java.nio.charset.StandardCharsets;
public class UDPServer {
public static void main(String[] args) throws SocketException {
// 1. 创建 DatagramSocket,绑定端口 9999
DatagramSocket socket = new DatagramSocket(9999);
System.out.println("UDP 服务器启动,监听端口 9999...");
// 2. 创建缓冲区接收数据(大小根据实际需求设置,如 1024 字节)
byte[] buffer = new byte[1024];
DatagramPacket receivePacket = new DatagramPacket(buffer, buffer.length);
while (true) {
try {
// 3. 阻塞接收数据报(无连接,无需“等待连接”)
socket.receive(receivePacket);
// 解析数据
String clientMsg = new String(
receivePacket.getData(), 0, receivePacket.getLength(),
StandardCharsets.UTF_8
);
System.out.println("收到客户端 " + receivePacket.getAddress() + " 消息:" + clientMsg);
// 4. 准备响应数据
String response = "UDP 服务器已收到:" + clientMsg;
byte[] responseData = response.getBytes(StandardCharsets.UTF_8);
// 创建响应数据报(包含数据、长度、客户端地址和端口)
DatagramPacket sendPacket = new DatagramPacket(
responseData, responseData.length,
receivePacket.getAddress(), receivePacket.getPort()
);
// 发送响应
socket.send(sendPacket);
// 若客户端发送 "exit",退出循环
if ("exit".equals(clientMsg)) {
break;
}
} catch (Exception e) {
e.printStackTrace();
}
}
// 关闭 socket
socket.close();
}
}
2. UDP 客户端实现
客户端创建数据报,指定服务器地址和端口,发送数据后等待响应。
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
public class UDPClient {
public static void main(String[] args) throws SocketException {
// 1. 创建 DatagramSocket(客户端无需绑定固定端口,系统自动分配)
DatagramSocket socket = new DatagramSocket();
// 服务器地址和端口
InetAddress serverAddr;
try {
serverAddr = InetAddress.getByName("localhost");
} catch (UnknownHostException e) {
throw new RuntimeException("服务器地址解析失败", e);
}
int serverPort = 9999;
Scanner scanner = new Scanner(System.in);
while (true) {
try {
// 2. 输入消息并创建数据报
System.out.print("请输入消息(输入 exit 退出):");
String msg = scanner.nextLine();
byte[] data = msg.getBytes(StandardCharsets.UTF_8);
DatagramPacket sendPacket = new DatagramPacket(
data, data.length, serverAddr, serverPort
);
// 3. 发送数据报
socket.send(sendPacket);
// 4. 接收服务器响应
byte[] buffer = new byte[1024];
DatagramPacket receivePacket = new DatagramPacket(buffer, buffer.length);
socket.receive(receivePacket);
String serverMsg = new String(
receivePacket.getData(), 0, receivePacket.getLength(),
StandardCharsets.UTF_8
);
System.out.println("服务器响应:" + serverMsg);
if ("exit".equals(msg)) {
break;
}
} catch (Exception e) {
e.printStackTrace();
}
}
// 关闭 socket
socket.close();
scanner.close();
}
}
3. 关键说明
- 不可靠性:UDP 不保证数据一定到达,也不保证顺序,需在应用层实现重传、校验等机制(如实时游戏中丢一帧数据不影响体验)。
- 数据报大小:
DatagramPacket缓冲区大小限制单次传输数据量(通常不超过 64KB),大文件传输需拆分数据报。
五、高层协议应用:URL 与 HTTP 通信
除了底层 TCP/UDP,Java 还提供
URL 和 URLConnection 类,简化 HTTP 等高层协议的操作(如爬取网页、调用 API)。示例:通过 HTTP 访问网页内容
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
public class HttpDemo {
public static void main(String[] args) {
try {
// 1. 创建 URL 对象(指定网页地址)
URL url = new URL("https://www.baidu.com");
// 2. 打开连接(默认是 HTTP 连接)
URLConnection connection = url.openConnection();
// 设置请求头(模拟浏览器,避免被服务器拒绝)
connection.setRequestProperty("User-Agent", "Mozilla/5.0");
// 3. 获取输入流,读取网页内容
try (BufferedReader in = new BufferedReader(
new InputStreamReader(connection.getInputStream()))) {
String line;
StringBuilder content = new StringBuilder();
while ((line = in.readLine()) != null) {
content.append(line).append("\n");
}
System.out.println("网页内容:\n" + content.toString());
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 扩展:复杂 HTTP 操作(如 POST 请求、带参数、Cookie 管理)可使用
HttpURLConnection或第三方库(如 OkHttp、HttpClient)。
六、进阶:NIO 非阻塞网络编程
传统的 BIO(阻塞 IO)在高并发场景下效率低(一个连接一个线程),Java NIO(New IO,JDK 1.4+)通过 非阻塞 IO 和 多路复用 提升性能,核心组件:
- Channel:双向通道(类似流,但可异步读写),如
SocketChannel(TCP)、DatagramChannel(UDP)。 - Buffer:数据容器(Channel 只能通过 Buffer 读写数据)。
- Selector:多路复用器,一个线程可管理多个 Channel(监听读写事件)。
NIO 服务器简单示例(TCP)
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
public class NIOServer {
public static void main(String[] args) throws IOException {
// 1. 创建 ServerSocketChannel 并绑定端口
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.socket().bind(new InetSocketAddress(8888));
serverChannel.configureBlocking(false); // 设置为非阻塞
// 2. 创建 Selector 并注册 ServerSocketChannel(监听 ACCEPT 事件)
Selector selector = Selector.open();
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("NIO 服务器启动,监听端口 8888...");
while (true) {
// 3. 阻塞等待事件(0 表示不阻塞,>0 表示超时时间毫秒)
selector.select();
// 获取所有就绪事件
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectedKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
// 处理事件后移除,避免重复处理
iterator.remove();
if (key.isAcceptable()) {
// 4. 处理连接事件(客户端连接)
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel clientChannel = server.accept();
clientChannel.configureBlocking(false); // 客户端通道也设为非阻塞
// 注册客户端通道到 Selector,监听 READ 事件
clientChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
System.out.println("客户端 " + clientChannel.getRemoteAddress() + " 连接");
} else if (key.isReadable()) {