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,绑定端口 9999DatagramSocket 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();}}// 关闭 socketsocket.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();}}// 关闭 socketsocket.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