Java java.net 教程
java.net 包是 Java 标准库中用于网络编程的核心部分,它提供了丰富的类和接口,使得开发者可以轻松地实现客户端/服务器应用、访问网络资源等。

本教程将分为以下几个部分:
- 网络基础回顾:理解核心概念。
- InetAddress 类:代表 IP 地址。
- URL 和 URLConnection 类:访问网络资源的高级方式。
- Socket 和 ServerSocket (TCP):面向连接的可靠通信。
- DatagramSocket 和 DatagramPacket (UDP):无连接的快速通信。
- NIO.2 (New I/O):现代、高性能的网络编程。
- 实战案例:构建一个简单的聊天室。
网络基础回顾
在开始编码前,快速回顾几个核心概念:
- IP 地址:网络中设备的唯一标识,如
168.1.100(IPv4) 或2001:0db8:85a3:0000:0000:8a2e:0370:7334(IPv6)。 - 端口号:设备上应用程序的标识,范围是 0-65535,Web 服务通常使用 80 或 443 端口。
- 协议:设备间通信的规则,主要分为两种:
- TCP (Transmission Control Protocol):面向连接、可靠的协议,通信前需要先建立连接(三次握手),确保数据无丢失、无重复、按序到达,适合要求高可靠性的场景,如文件传输、网页浏览。
- UDP (User Datagram Protocol):无连接、不可靠的协议,发送方直接发送数据包,不保证对方一定能收到,优点是开销小、传输快,适合对实时性要求高、能容忍少量丢包的场景,如视频会议、在线游戏。
InetAddress 类
InetAddress 是 java.net 包中的一个核心类,它不包含端口信息,仅用于表示 IP 地址(主机名)。
常用方法
static InetAddress getByName(String host): 根据主机名或 IP 地址字符串获取InetAddress实例。static InetAddress[] getAllByName(String host): 获取主机名对应的所有 IP 地址(一个主机可能有多个网卡,即多个 IP)。String getHostName(): 获取此 IP 地址的主机名。String getHostAddress(): 获取此 IP 地址的字符串形式。boolean isReachable(int timeout): 测试是否能到达该地址。
代码示例
import java.net.InetAddress;
import java.net.UnknownHostException;
public class InetAddressExample {
public static void main(String[] args) {
try {
// 1. 根据主机名获取 InetAddress 对象
InetAddress address = InetAddress.getByName("www.baidu.com");
System.out.println("主机名: " + address.getHostName());
System.out.println("IP 地址: " + address.getHostAddress());
System.out.println("----------------------------------");
// 2. 根据IP地址获取 InetAddress 对象
InetAddress addressByIp = InetAddress.getByName("182.61.200.7");
System.out.println("IP 对应的主机名: " + addressByIp.getHostName());
System.out.println("IP 地址: " + addressByIp.getHostAddress());
System.out.println("----------------------------------");
// 3. 获取本机的 InetAddress 对象
InetAddress localHost = InetAddress.getLocalHost();
System.out.println("本机主机名: " + localHost.getHostName());
System.out.println("本机IP地址: " + localHost.getHostAddress());
System.out.println("----------------------------------");
// 4. 获取一个主机的所有IP地址
InetAddress[] allAddresses = InetAddress.getAllByName("www.google.com");
System.out.println("www.google.com 的所有IP地址:");
for (InetAddress addr : allAddresses) {
System.out.println(addr.getHostAddress());
}
} catch (UnknownHostException e) {
System.err.println("无法找到主机: " + e.getMessage());
}
}
}
URL 和 URLConnection 类
URL (Uniform Resource Locator) 类提供了一种高级方式来访问互联网上的资源,它封装了网络资源的详细信息。

URL 类常用方法
String getProtocol(): 获取协议 (e.g.,http).String getHost(): 获取主机名。int getPort(): 获取端口号,如果未指定则返回 -1。String getPath(): 获取路径。InputStream openStream(): 打开一个到该 URL 的输入流,用于读取数据。
URLConnection 类
openStream() 方法返回的是一个 InputStream,如果想进行更复杂的操作,比如获取/设置请求头、发送 POST 请求等,可以使用 URLConnection。
代码示例:读取网页内容
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
public class UrlExample {
public static void main(String[] args) {
// 使用 try-with-resources 自动关闭流
try {
// 创建一个URL对象
URL url = new URL("https://www.baidu.com");
// 打开输入流
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(url.openStream()))) {
String line;
// 逐行读取并打印
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}
} catch (IOException e) {
System.err.println("读取URL时发生错误: " + e.getMessage());
}
}
}
TCP Socket 编程
TCP 是最常用的网络协议,Java 使用 Socket 和 ServerSocket 来实现 TCP 通信。
Socket(客户端):代表一个客户端套接字,它尝试连接到服务器。ServerSocket(服务器):代表一个服务器套接字,它在指定端口上监听客户端的连接请求。
通信流程
服务器端:
- 创建
ServerSocket对象,并绑定一个端口号。 - 调用
accept()方法,阻塞等待客户端连接,该方法返回一个Socket对象,代表与客户端建立的连接。 - 通过
Socket对象的getInputStream()和getOutputStream()获取输入/输出流。 - 使用流进行读写操作(与客户端通信)。
- 通信结束后,关闭
Socket和ServerSocket。
客户端:

- 创建
Socket对象,指定服务器的 IP 地址和端口号。 - 连接建立后,通过
Socket对象的getInputStream()和getOutputStream()获取输入/输出流。 - 使用流进行读写操作(与服务器通信)。
- 通信结束后,关闭
Socket。
代码示例:简单的 Echo 服务器和客户端
EchoServer.java (服务器端)
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class EchoServer {
public static void main(String[] args) {
int port = 12345; // 定义服务器监听端口
try (ServerSocket serverSocket = new ServerSocket(port)) {
System.out.println("服务器已启动,等待客户端连接...");
// accept() 方法会阻塞,直到有客户端连接
try (Socket clientSocket = serverSocket.accept();
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()))) {
System.out.println("客户端已连接: " + clientSocket.getInetAddress().getHostAddress());
String inputLine;
// 读取客户端发送的一行数据
while ((inputLine = in.readLine()) != null) {
System.out.println("收到客户端消息: " + inputLine);
// 将收到的消息回写给客户端
out.println("服务器回显: " + inputLine);
// 如果客户端发送 "bye",则退出循环
if ("bye".equalsIgnoreCase(inputLine)) {
break;
}
}
}
} catch (IOException e) {
System.err.println("服务器异常: " + e.getMessage());
e.printStackTrace();
}
System.out.println("服务器已关闭。");
}
}
EchoClient.java (客户端)
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
public class EchoClient {
public static void main(String[] args) {
String hostName = "localhost"; // 服务器地址,本机测试用
int portNumber = 12345; // 服务器端口
try (Socket socket = new Socket(hostName, portNumber);
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in))) {
System.out.println("已连接到服务器。");
System.out.println("请输入消息,输入 'bye' 退出:");
String userInput;
// 从控制台读取用户输入
while ((userInput = stdIn.readLine()) != null) {
// 将用户输入发送给服务器
out.println(userInput);
// 从服务器读取回显
String response = in.readLine();
System.out.println("服务器响应: " + response);
if ("bye".equalsIgnoreCase(userInput)) {
break;
}
}
} catch (UnknownHostException e) {
System.err.println("不知道主机: " + hostName);
e.printStackTrace();
} catch (IOException e) {
System.err.println("I/O 对主机 " + hostName + " 的连接失败: " + e.getMessage());
e.printStackTrace();
}
}
}
如何运行:
- 先运行
EchoServer。 - 再运行
EchoClient。 - 在客户端的控制台输入消息,按回车,你会在服务器和客户端的控制台看到相应的输出。
UDP Socket 编程
UDP 是一种无连接的协议,通信更快速,但不保证可靠性。
DatagramSocket:用于发送和接收数据报包。DatagramPacket:数据报包,包含了要发送的数据、目标地址和端口,或接收到的数据和源地址。
通信流程
发送方:
- 创建
DatagramSocket对象(可以指定端口,也可以不指定,让系统分配)。 - 准备要发送的数据,并将其打包到
DatagramPacket中,同时指定接收方的 IP 和端口。 - 调用
DatagramSocket的send()方法发送数据包。 - 关闭
DatagramSocket。
接收方:
- 创建
DatagramSocket对象,并绑定一个监听端口。 - 创建一个空的
DatagramPacket对象,用于接收数据。 - 调用
DatagramSocket的receive()方法阻塞等待数据包,当有数据包到达时,数据会被填充到空的DatagramPacket中。 - 从
DatagramPacket中解析出数据和发送方信息。 - 关闭
DatagramSocket。
代码示例:UDP 通信
UDPServer.java
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class UDPServer {
public static void main(String[] args) {
int port = 9876;
try (DatagramSocket serverSocket = new DatagramSocket(port)) {
System.out.println("UDP 服务器已启动,监听端口 " + port);
byte[] receiveData = new byte[1024];
while (true) { // 持续监听
// 创建接收数据包
DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
// 接收数据包 (阻塞)
serverSocket.receive(receivePacket);
// 提取数据
String sentence = new String(receivePacket.getData(), 0, receivePacket.getLength());
System.out.println("收到消息: " + sentence);
// 获取客户端地址和端口
InetAddress clientAddress = receivePacket.getAddress();
int clientPort = receivePacket.getPort();
// 创建响应数据
String capitalizedSentence = sentence.toUpperCase();
byte[] sendData = capitalizedSentence.getBytes();
// 创建并发送响应数据包
DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, clientAddress, clientPort);
serverSocket.send(sendPacket);
System.out.println("已向客户端发送响应: " + capitalizedSentence);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
UDPClient.java
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;
public class UDPClient {
public static void main(String[] args) {
String hostName = "localhost";
int port = 9876;
try (DatagramSocket clientSocket = new DatagramSocket();
Scanner scanner = new Scanner(System.in)) {
InetAddress IPAddress = InetAddress.getByName(hostName);
byte[] sendData;
byte[] receiveData = new byte[1024];
System.out.println("UDP 客户端已启动。");
System.out.println("请输入消息,输入 'exit' 退出:");
while (true) {
String sentence = scanner.nextLine();
if ("exit".equalsIgnoreCase(sentence)) {
break;
}
// 发送数据
sendData = sentence.getBytes();
DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, IPAddress, port);
clientSocket.send(sendPacket);
// 接收响应
DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
clientSocket.receive(receivePacket);
String modifiedSentence = new String(receivePacket.getData(), 0, receivePacket.getLength());
System.out.println("服务器响应: " + modifiedSentence);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
NIO.2 (New I/O)
传统的 I/O (BIO - Blocking I/O) 是阻塞式的,当一个线程在读写数据时,如果数据还没准备好,线程就会被阻塞,直到操作完成,在高并发场景下,这会导致大量的线程被创建和阻塞,消耗大量资源。
NIO.2 (Java 1.4 引入,在 Java 7 中得到极大增强) 提供了非阻塞 I/O 的能力,是构建高性能网络服务器的关键技术。
核心概念
- Channel (通道):类似流,但可以双向读写(
InputStream只能读,OutputStream只能写)。SocketChannel和ServerSocketChannel是网络编程中常用的通道。 - Buffer (缓冲区):数据被读取到
Buffer中,或从Buffer写出,所有读写操作都通过Buffer进行。 - Selector (选择器):这是 NIO 的核心,一个
Selector可以同时监控多个Channel的事件(如连接、接受、读、写),当某个Channel有事件发生时,Selector会通知我们,这使得我们可以用一个线程管理多个连接,大大提高了效率。
简单流程 (NIO Server)
- 创建
ServerSocketChannel并设置为非阻塞模式。 - 将
ServerSocketChannel注册到Selector上,监听OP_ACCEPT(新连接)事件。 - 循环调用
Selector.select(),它会阻塞直到至少一个注册的通道上有事件发生。 - 获取
SelectorKeys(发生事件的通道集合)。 - 遍历
SelectorKeys,对每个事件进行处理:- 如果是
OP_ACCEPT,则接受新连接,并将新的SocketChannel也注册到Selector上,监听OP_READ事件。 - 如果是
OP_READ,则从SocketChannel读取数据到Buffer,处理数据,然后可以注册OP_WRITE事件以便后续写入。
- 如果是
- 处理完毕后,将
SelectorKey从集合中移除。
NIO 的 API 相对复杂,但性能优势巨大,Netty、Mina 等知名框架都是基于 NIO 构建的。
实战案例:简单的多人聊天室
我们将使用 TCP Socket 来构建一个简单的命令行聊天室。
功能:
- 服务器端:接收所有客户端的消息,并广播给所有其他客户端。
- 客户端:可以发送消息给服务器,并接收服务器转发的其他客户端的消息。
ChatServer.java
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.concurrent.*;
public class ChatServer {
private static final int PORT = 8888;
// 使用一个线程安全的集合来存储所有客户端的输出流
private static Set<PrintWriter> clientWriters = ConcurrentHashMap.newKeySet();
public static void main(String[] args) throws Exception {
System.out.println("聊天室服务器启动...");
// 使用线程池来处理每个客户端的连接
ExecutorService pool = Executors.newFixedThreadPool(200); // 假设最多200个客户端
try (ServerSocket serverSocket = new ServerSocket(PORT)) {
while (true) {
// 接受新客户端连接
Socket clientSocket = serverSocket.accept();
System.out.println("新客户端已连接: " + clientSocket.getInetAddress().getHostAddress());
// 为每个客户端创建一个处理线程
pool.execute(new ClientHandler(clientSocket));
}
}
}
/**
* 客户端处理器
*/
private static class ClientHandler implements Runnable {
private Socket socket;
private PrintWriter out;
private BufferedReader in;
public ClientHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
// 获取输入输出流
out = new PrintWriter(socket.getOutputStream(), true);
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// 将新客户端的输出流添加到集合中
clientWriters.add(out);
String inputLine;
// 读取客户端发送的消息
while ((inputLine = in.readLine()) != null) {
System.out.println("收到消息: " + inputLine);
// 将消息广播给所有客户端
for (PrintWriter writer : clientWriters) {
writer.println(inputLine);
}
}
} catch (IOException e) {
System.out.println("与客户端 " + socket.getInetAddress() + " 的连接出现错误或中断。");
} finally {
// 客户端断开连接后,将其输出流从集合中移除
if (out != null) {
clientWriters.remove(out);
}
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("客户端 " + socket.getInetAddress() + " 已断开连接。");
}
}
}
}
ChatClient.java
import java.io.*;
import java.net.*;
import java.util.Scanner;
public class ChatClient {
private static final String SERVER_ADDRESS = "localhost";
private static final int SERVER_PORT = 8888;
public static void main(String[] args) {
try (
// 创建Socket连接服务器
Socket socket = new Socket(SERVER_ADDRESS, SERVER_PORT);
// 用于发送消息的流
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
// 用于接收消息的流
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// 用于读取用户控制台输入的流
Scanner scanner = new Scanner(System.in)
) {
System.out.println("已连接到聊天室服务器。");
System.out.println("请输入您的消息,输入 'quit' 退出:");
// 启动一个线程来异步接收服务器消息
Thread receiveThread = new Thread(() -> {
try {
String serverMessage;
while ((serverMessage = in.readLine()) != null) {
System.out.println(serverMessage);
}
} catch (IOException e) {
System.out.println("与服务器连接已断开。");
}
});
receiveThread.start();
// 主线程用于读取用户输入并发送
while (scanner.hasNextLine()) {
String userInput = scanner.nextLine();
if ("quit".equalsIgnoreCase(userInput)) {
break;
}
out.println(userInput);
}
} catch (UnknownHostException e) {
System.err.println("不知道主机: " + SERVER_ADDRESS);
} catch (IOException e) {
System.err.println("I/O 对主机 " + SERVER_ADDRESS + " 的连接失败: " + e.getMessage());
}
System.out.println("客户端已退出。");
}
}
如何运行:
- 运行
ChatServer。 - 运行多个
ChatClient(可以在多个命令行窗口中运行)。 - 在任何一个客户端输入消息,所有连接的客户端都会收到这条消息。
| 特性 | TCP (Socket/ServerSocket) | UDP (DatagramSocket/DatagramPacket) | NIO.2 (Channels, Buffers, Selector) |
|---|---|---|---|
| 类型 | 面向连接 | 无连接 | 面向连接/非阻塞 |
| 可靠性 | 高,保证数据顺序和完整性 | 低,不保证,可能丢包或重复 | 高,但通过非阻塞实现高吞吐 |
| 速度 | 较慢,有连接开销 | 快,直接发送 | 极快,单线程管理多连接 |
| 复杂性 | 简单,易于理解 | 较简单 | 复杂 |
| 适用场景 | 文件传输、网页浏览、邮件 | 视频会议、在线游戏、DNS | 高性能服务器、Netty框架 |
- 对于简单的网络应用,TCP Socket 是最常用和最容易上手的选择。
- 如果对实时性要求高且能容忍少量丢包,UDP 是不错的选择。
- 当你需要构建能够处理成千上万个并发连接的高性能服务器时,NIO.2 是必经之路。
希望这份详细的教程能帮助你理解并掌握 Java 的 java.net 编程!
