

在当今的互联网时代,网络编程是软件开发中不可或缺的重要部分。Java 提供了丰富的类库来支持网络编程,使得开发者能够轻松地实现各种网络通信功能。本章将详细介绍 Java 网络编程的核心知识,包括网络基础概念、套接字通信、数据报通信以及 URL 编程等内容,并通过完整的代码示例帮助大家掌握实际应用技能。

网络编程的本质是实现不同设备之间的数据传输。在学习 Java 网络编程之前,我们需要先了解一些计算机网络的基本概念。
为了实现不同计算机之间的通信,网络通信采用分层模型设计,每一层负责特定的功能,并通过协议规范通信方式。

最著名的网络模型有两种:
常用协议:


在网络通信中,最常见的模式是客户 / 服务器(C/S)结构:

B/S 结构(浏览器 / 服务器)是一种特殊的 C/S 结构,客户端是浏览器,通过 HTTP 协议与服务器通信。

192.168.1.12001:0db8:85a3:0000:0000:8a2e:0370:7334(为解决 IPv4 地址耗尽问题)127.0.0.1,通常映射到域名 localhostwww.csdn.net。



IP地址:端口号(如 192.168.1.100:8080)Socket 类实现套接字功能

Java 中基于 TCP 协议的网络通信主要通过套接字(Socket)实现,核心类是 ServerSocket(服务器端)和 Socket(客户端)。
ServerSocket(int port) - 绑定到指定端口Socket accept() - 阻塞等待客户端连接,返回与客户端通信的 Socket 对象Socket(String host, int port) - 连接到指定主机的指定端口InputStream getInputStream() - 获取输入流,用于读取数据OutputStream getOutputStream() - 获取输出流,用于发送数据void close() - 关闭套接字@startuml
class ServerSocket {
+ ServerSocket(int port) throws IOException
+ Socket accept() throws IOException
+ void close() throws IOException
+ int getLocalPort()
}
class Socket {
+ Socket(String host, int port) throws IOException
+ InputStream getInputStream() throws IOException
+ OutputStream getOutputStream() throws IOException
+ void close() throws IOException
+ InetAddress getInetAddress()
+ int getPort()
+ int getLocalPort()
}
ServerSocket "1" -- "*" Socket : 创建
@enduml
下面实现一个简单的 TCP 通信程序:客户端向服务器发送一条消息,服务器接收后回复一条消息。
服务器端代码(TCPServer.java):
import java.io.*;
import java.net.*;
public class TCPServer {
public static void main(String[] args) {
ServerSocket serverSocket = null;
Socket clientSocket = null;
try {
// 创建服务器套接字,绑定到8888端口
serverSocket = new ServerSocket(8888);
System.out.println("服务器已启动,等待客户端连接...");
// 阻塞等待客户端连接
clientSocket = serverSocket.accept();
System.out.println("客户端已连接:" + clientSocket.getInetAddress().getHostAddress());
// 获取输入流和输出流
BufferedReader in = new BufferedReader(
new InputStreamReader(clientSocket.getInputStream(), "UTF-8"));
PrintWriter out = new PrintWriter(
new OutputStreamWriter(clientSocket.getOutputStream(), "UTF-8"), true);
// 读取客户端发送的消息
String clientMsg = in.readLine();
System.out.println("收到客户端消息:" + clientMsg);
// 向客户端发送响应
String serverMsg = "Hello Client! 我是服务器";
out.println(serverMsg);
System.out.println("已向客户端发送消息:" + serverMsg);
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭资源
try {
if (clientSocket != null) clientSocket.close();
if (serverSocket != null) serverSocket.close();
System.out.println("服务器已关闭");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
客户端代码(TCPClient.java):
import java.io.*;
import java.net.*;
public class TCPClient {
public static void main(String[] args) {
Socket socket = null;
try {
// 连接到本地服务器的8888端口
socket = new Socket("localhost", 8888);
System.out.println("已连接到服务器");
// 获取输入流和输出流
BufferedReader in = new BufferedReader(
new InputStreamReader(socket.getInputStream(), "UTF-8"));
PrintWriter out = new PrintWriter(
new OutputStreamWriter(socket.getOutputStream(), "UTF-8"), true);
// 向服务器发送消息
String clientMsg = "Hello Server! 我是客户端";
out.println(clientMsg);
System.out.println("已向服务器发送消息:" + clientMsg);
// 读取服务器的响应
String serverMsg = in.readLine();
System.out.println("收到服务器消息:" + serverMsg);
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭资源
try {
if (socket != null) socket.close();
System.out.println("客户端已关闭");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

运行说明:
TCPServer,服务器启动并等待连接TCPClient,客户端连接服务器并进行通信上面的服务器程序只能处理一个客户端连接。要实现同时服务多个客户端,需要使用多线程:主线程负责监听连接,每收到一个连接就创建一个新线程处理与该客户端的通信。
多线程服务器代码(MultiThreadTCPServer.java):
import java.io.*;
import java.net.*;
public class MultiThreadTCPServer {
public static void main(String[] args) {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(8888);
System.out.println("多线程服务器已启动,等待客户端连接...");
int clientCount = 0; // 客户端计数器
while (true) { // 循环接受多个客户端连接
Socket clientSocket = serverSocket.accept();
clientCount++;
System.out.println("第" + clientCount + "个客户端已连接:" +
clientSocket.getInetAddress().getHostAddress());
// 创建新线程处理客户端通信
new ClientHandler(clientSocket, clientCount).start();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (serverSocket != null) serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 客户端处理线程
static class ClientHandler extends Thread {
private Socket clientSocket;
private int clientId;
public ClientHandler(Socket socket, int id) {
this.clientSocket = socket;
this.clientId = id;
}
@Override
public void run() {
BufferedReader in = null;
PrintWriter out = null;
try {
// 获取输入流和输出流
in = new BufferedReader(
new InputStreamReader(clientSocket.getInputStream(), "UTF-8"));
out = new PrintWriter(
new OutputStreamWriter(clientSocket.getOutputStream(), "UTF-8"), true);
String clientMsg;
// 循环读取客户端消息,直到客户端关闭连接
while ((clientMsg = in.readLine()) != null) {
System.out.println("收到第" + clientId + "个客户端消息:" + clientMsg);
// 向客户端发送响应
String serverMsg = "服务器已收到你的消息:" + clientMsg;
out.println(serverMsg);
}
System.out.println("第" + clientId + "个客户端已断开连接");
} catch (IOException e) {
System.out.println("第" + clientId + "个客户端通信异常:" + e.getMessage());
} finally {
// 关闭资源
try {
if (in != null) in.close();
if (out != null) out.close();
if (clientSocket != null) clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}客户端代码:可以使用前面的 TCPClient.java,也可以稍作修改使其能发送多条消息:
import java.io.*;
import java.net.*;
import java.util.Scanner;
public class MultiTCPClient {
public static void main(String[] args) {
Socket socket = null;
Scanner scanner = null;
try {
socket = new Socket("localhost", 8888);
System.out.println("已连接到服务器,输入消息发送(输入exit退出):");
// 获取输入流和输出流
BufferedReader in = new BufferedReader(
new InputStreamReader(socket.getInputStream(), "UTF-8"));
PrintWriter out = new PrintWriter(
new OutputStreamWriter(socket.getOutputStream(), "UTF-8"), true);
scanner = new Scanner(System.in);
String msg;
// 循环输入并发送消息
while (true) {
msg = scanner.nextLine();
out.println(msg);
if ("exit".equals(msg)) {
System.out.println("客户端将退出");
break;
}
// 读取服务器响应
String serverMsg = in.readLine();
System.out.println("服务器回复:" + serverMsg);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (scanner != null) scanner.close();
if (socket != null) socket.close();
System.out.println("客户端已关闭");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

运行说明:
MultiThreadTCPServerMultiTCPClient 实例,每个客户端都能与服务器独立通信
UDP(User Datagram Protocol,用户数据报协议)是一种无连接的传输层协议,与 TCP 相比,它不保证数据的可靠传输,但具有传输速度快、开销小的特点,适用于对实时性要求高但对可靠性要求不高的场景(如视频聊天、语音通话、游戏等)。
UDP 通信特点:
UDP 通信流程:
Java 中使用以下类实现 UDP 通信:
DatagramSocket() - 创建未绑定的套接字DatagramSocket(int port) - 创建绑定到指定端口的套接字void send(DatagramPacket p) - 发送数据报void receive(DatagramPacket p) - 接收数据报(阻塞)void close() - 关闭套接字DatagramPacket(byte[] buf, int length)DatagramPacket(byte[] buf, int length, InetAddress address, int port)byte[] getData() - 获取数据报中的数据int getLength() - 获取数据长度InetAddress getAddress() - 获取发送方 / 接收方的 IP 地址int getPort() - 获取发送方 / 接收方的端口号@startuml
class DatagramSocket {
+ DatagramSocket() throws SocketException
+ DatagramSocket(int port) throws SocketException
+ void send(DatagramPacket p) throws IOException
+ void receive(DatagramPacket p) throws IOException
+ void close()
}
class DatagramPacket {
+ DatagramPacket(byte[] buf, int length)
+ DatagramPacket(byte[] buf, int length, InetAddress address, int port)
+ byte[] getData()
+ int getLength()
+ InetAddress getAddress()
+ int getPort()
+ void setData(byte[] buf)
+ void setLength(int length)
}
DatagramSocket "1" -- "*" DatagramPacket : 发送/接收
@enduml
下面实现一个简单的 UDP 通信程序:客户端向服务器发送消息,服务器接收后回复。
UDP 服务器代码(UDPServer.java):
import java.net.*;
import java.nio.charset.StandardCharsets;
public class UDPServer {
public static void main(String[] args) {
DatagramSocket socket = null;
byte[] receiveBuf = new byte[1024]; // 接收缓冲区
try {
// 创建UDP套接字,绑定到8888端口
socket = new DatagramSocket(8888);
System.out.println("UDP服务器已启动,等待消息...");
while (true) { // 循环接收消息
// 创建接收数据报
DatagramPacket receivePacket = new DatagramPacket(receiveBuf, receiveBuf.length);
// 接收数据报(阻塞)
socket.receive(receivePacket);
// 解析接收的数据
String clientMsg = new String(
receivePacket.getData(), 0, receivePacket.getLength(), StandardCharsets.UTF_8);
InetAddress clientAddr = receivePacket.getAddress();
int clientPort = receivePacket.getPort();
System.out.println("收到来自 " + clientAddr.getHostAddress() + ":" + clientPort +
" 的消息:" + clientMsg);
// 准备回复消息
String serverMsg = "服务器已收到:" + clientMsg;
byte[] sendData = serverMsg.getBytes(StandardCharsets.UTF_8);
// 创建发送数据报
DatagramPacket sendPacket = new DatagramPacket(
sendData, sendData.length, clientAddr, clientPort);
// 发送回复
socket.send(sendPacket);
System.out.println("已回复消息:" + serverMsg);
// 如果收到"exit",则退出服务器
if ("exit".equals(clientMsg)) {
System.out.println("服务器将关闭");
break;
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭套接字
if (socket != null) {
socket.close();
}
}
}
}UDP 客户端代码(UDPClient.java):
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
public class UDPClient {
public static void main(String[] args) {
DatagramSocket socket = null;
Scanner scanner = null;
try {
// 创建UDP套接字(不指定端口,系统会分配一个临时端口)
socket = new DatagramSocket();
// 服务器地址和端口
InetAddress serverAddr = InetAddress.getByName("localhost");
int serverPort = 8888;
System.out.println("UDP客户端已启动,输入消息发送(输入exit退出):");
scanner = new Scanner(System.in);
while (true) {
// 读取用户输入
String msg = scanner.nextLine();
// 准备发送数据
byte[] sendData = msg.getBytes(StandardCharsets.UTF_8);
// 创建发送数据报
DatagramPacket sendPacket = new DatagramPacket(
sendData, sendData.length, serverAddr, serverPort);
// 发送数据报
socket.send(sendPacket);
System.out.println("已发送消息:" + msg);
// 如果输入"exit",则退出客户端
if ("exit".equals(msg)) {
System.out.println("客户端将关闭");
break;
}
// 接收服务器回复
byte[] receiveBuf = new byte[1024];
DatagramPacket receivePacket = new DatagramPacket(receiveBuf, receiveBuf.length);
socket.receive(receivePacket);
// 解析回复数据
String serverMsg = new String(
receivePacket.getData(), 0, receivePacket.getLength(), StandardCharsets.UTF_8);
System.out.println("收到服务器回复:" + serverMsg);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭资源
if (scanner != null) scanner.close();
if (socket != null) socket.close();
}
}
}

运行说明:
UDPServerUDPClient
URL(Uniform Resource Locator,统一资源定位符)用于标识互联网上的资源,如网页、图片、文件等。Java 提供了 URL 和 URLConnection 类来方便地访问 URL 指向的资源。


HTTP(HyperText Transfer Protocol,超文本传输协议)是一种基于 TCP 的应用层协议,用于在客户端和服务器之间传输超文本数据(如 HTML)。
HTTP 通信流程:
HTTP 请求方法:
HTTP 响应状态码:
URL 的格式:协议://主机名:端口/路径?查询参数#片段
例如:https://www.csdn.net:443/article/list/1?type=1#content
Java 中的 java.net.URL 类用于表示 URL,并提供了访问 URL 资源的方法:
URL(String spec) - 根据字符串创建 URL 对象String getProtocol() - 获取协议String getHost() - 获取主机名int getPort() - 获取端口号String getPath() - 获取路径InputStream openStream() - 获取 URL 资源的输入流 URLConnection 是一个抽象类,用于表示与 URL 所指向资源的连接。它比 URL 类提供了更多的功能,如设置请求头、获取响应头、处理表单提交等。
常用方法:
static URLConnection openConnection() - 获取 URLConnection 对象void setRequestProperty(String key, String value) - 设置请求头Map<String, List<String>> getHeaderFields() - 获取响应头InputStream getInputStream() - 获取输入流,用于读取资源OutputStream getOutputStream() - 获取输出流,用于发送数据(如 POST 请求)void connect() - 建立连接使用 URL 读取网页内容的示例(URLReader.java):
import java.io.*;
import java.net.*;
public class URLReader {
public static void main(String[] args) {
// 要访问的URL
String urlStr = "https://www.baidu.com";
BufferedReader reader = null;
try {
// 创建URL对象
URL url = new URL(urlStr);
System.out.println("协议:" + url.getProtocol());
System.out.println("主机:" + url.getHost());
System.out.println("端口:" + url.getPort()); // -1表示使用协议默认端口
System.out.println("路径:" + url.getPath());
// 打开URL连接,获取输入流
reader = new BufferedReader(
new InputStreamReader(url.openStream(), "UTF-8"));
// 读取内容并输出
String line;
System.out.println("\n网页内容:");
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (MalformedURLException e) {
System.out.println("URL格式错误:" + e.getMessage());
} catch (IOException e) {
System.out.println("读取失败:" + e.getMessage());
} finally {
// 关闭资源
try {
if (reader != null) reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
使用 URLConnection 发送 POST 请求的示例(URLConnectionPost.java):
import java.io.*;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
public class URLConnectionPost {
public static void main(String[] args) {
// 要提交的URL(这里使用一个测试接口)
String urlStr = "https://httpbin.org/post";
HttpURLConnection connection = null;
BufferedReader reader = null;
try {
// 创建URL对象
URL url = new URL(urlStr);
// 打开连接
connection = (HttpURLConnection) url.openConnection();
// 设置请求方法为POST
connection.setRequestMethod("POST");
// 设置允许输出(发送数据)
connection.setDoOutput(true);
// 设置请求头
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
connection.setRequestProperty("User-Agent", "Mozilla/5.0");
// 准备要提交的表单数据
Map<String, String> params = new HashMap<>();
params.put("name", "张三");
params.put("age", "25");
params.put("city", "北京");
// 构建请求参数字符串
StringBuilder postData = new StringBuilder();
for (Map.Entry<String, String> entry : params.entrySet()) {
if (postData.length() > 0) {
postData.append('&');
}
// 对参数进行URL编码
postData.append(URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8.name()));
postData.append('=');
postData.append(URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8.name()));
}
byte[] postDataBytes = postData.toString().getBytes(StandardCharsets.UTF_8);
// 设置请求内容长度
connection.setRequestProperty("Content-Length", String.valueOf(postDataBytes.length));
// 获取输出流,发送数据
try (DataOutputStream out = new DataOutputStream(connection.getOutputStream())) {
out.write(postDataBytes);
}
// 获取响应状态码
int responseCode = connection.getResponseCode();
System.out.println("响应状态码:" + responseCode);
// 读取响应内容
reader = new BufferedReader(
new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8));
String line;
System.out.println("响应内容:");
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (MalformedURLException e) {
System.out.println("URL格式错误:" + e.getMessage());
} catch (IOException e) {
System.out.println("请求失败:" + e.getMessage());
} finally {
// 关闭资源
try {
if (reader != null) reader.close();
} catch (IOException e) {
e.printStackTrace();
}
if (connection != null) {
connection.disconnect();
}
}
}
}
运行说明:
URLReader 会读取指定 URL 的内容并输出到控制台URLConnectionPost 会向测试接口发送 POST 请求,并输出响应结果注意:访问某些网站可能会被拒绝(如设置了反爬机制),可以尝试修改 User-Agent 等请求头信息模拟浏览器行为。
本章主要介绍了 Java 网络编程的核心内容,包括:
ServerSocket 和 Socket 类实现可靠的面向连接的通信,以及多线程服务器的实现DatagramSocket 和 DatagramPacket 类实现无连接的不可靠通信URL 和 URLConnection 类访问互联网资源,包括发送 GET 和 POST 请求Java 网络编程是 Java 编程中的重要组成部分,掌握这些知识可以帮助我们开发各种网络应用,如客户端 / 服务器程序、网络爬虫、API 调用等。
URLConnection 编写一个简单的网页爬虫,爬取指定网页中的所有链接(<a> 标签的 href 属性)通过这些练习,可以加深对 Java 网络编程的理解和应用能力。在实际开发中,还可以结合线程池、NIO 等技术进一步优化网络程序的性能。