Java 网络
2022/1/1约 2683 字大约 9 分钟
Java 网络
Java 网络编程主要涉及通过网络通信在不同设备或应用之间传输数据。Java 提供了丰富的类库来实现网络通信,通常使用 java.net
包。以下是 Java 网络编程的一些关键概念和常用类:
1. 基础概念
- IP 地址:每个连接到互联网的设备都有一个唯一的地址,称为 IP 地址。IP 地址有两种类型:IPv4 和 IPv6。
- 端口号:端口号用于区分同一台设备上的不同服务。HTTP 通常使用端口 80,HTTPS 使用端口 443。
- 协议:网络协议定义了计算机间通信的规则。常见的协议有 TCP、UDP、HTTP、FTP 等。
2. 常用类
InetAddress
:表示一个 IP 地址,提供了获取主机名和 IP 地址的功能。常用于网络地址解析。InetAddress address = InetAddress.getByName("www.example.com"); System.out.println(address.getHostAddress());
Socket
:用于客户端与服务器端之间通过 TCP 协议建立连接。它用于发送请求并接收响应。Socket socket = new Socket("www.example.com", 80); // 连接到远程服务器
ServerSocket
:用于服务器端,监听客户端连接请求并建立连接。ServerSocket serverSocket = new ServerSocket(8080); // 监听端口 8080 Socket clientSocket = serverSocket.accept(); // 接受客户端连接
DatagramSocket
和DatagramPacket
:用于 UDP 协议,适合不需要建立连接的情况,常用于实时性要求高但容错性强的应用。DatagramSocket socket = new DatagramSocket(); byte[] buffer = new byte[256]; DatagramPacket packet = new DatagramPacket(buffer, buffer.length); socket.receive(packet);
不考
3. TCP 编程
TCP 是一种面向连接的协议,在进行数据传输之前必须建立连接。Java 使用 Socket
和 ServerSocket
来实现 TCP 客户端和服务器。
流程
服务器端流程:
- 创建一个服务器套接字
ServerSocket
对象:- 用于监听特定的端口。
- 等待客户端连接。
- 端口范围0~65536,0~1024为保留端口号不可使用
- 接受客户端连接:
- 调用
ServerSocket.accept()
方法,阻塞等待客户端发起连接。 - 每接收到一个客户端连接,就返回一个
Socket
对象。
- 调用
- 为每个客户端启动一个独立线程:
- 在新的线程中处理客户端的请求,避免阻塞主线程。
- 使用
Runnable
或线程池管理客户端线程。
- 在客户端线程中处理通信逻辑:
- 使用输入输出流 (
InputStream
和OutputStream
) 与客户端进行数据交换。 - 可实现自定义协议,比如简单的消息回复或广播功能。
- 使用输入输出流 (
- 关闭连接:
- 客户端结束通信后,关闭
Socket
和流。
- 客户端结束通信后,关闭
客户端流程:
- 创建一个
Socket
对象:- 指定服务器的 IP 地址和端口号。
- 建立连接:
- 如果服务器在线,连接成功后即可获取到
Socket
。
- 如果服务器在线,连接成功后即可获取到
- 发送和接收数据:
- 使用输入输出流与服务器进行通信。
- 关闭连接:
- 通信结束后,关闭
Socket
和流。
- 通信结束后,关闭
实现步骤
服务器端实现步骤
初始化服务器:
ServerSocket serverSocket = new ServerSocket(12345); // 监听12345端口 System.out.println("服务器启动...");
等待客户端连接:
Socket clientSocket = serverSocket.accept(); // 阻塞,直到有客户端连接 System.out.println("客户端连接:" + clientSocket.getInetAddress().getHostAddress());
一对多,为客户端启动线程(线程池):
ExecutorService threadPool = Executors.newCachedThreadPool(); // 使用线程池 threadPool.execute(new ClientHandler(clientSocket)); // 将任务交给线程池
在线程中处理客户端逻辑:
- 读取客户端发送的消息。
- 对消息进行处理后,返回响应。
按数据类型读
DataInputStream dataInputStream = new DataInputStream(socket.getInputStream()); DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream()); // 读 Double r = dataInputStream.readDouble(); // 写 dataOutputStream.writeDouble(area);
按行读取
PrintWriter out = new PrintWriter(socket.getOutputStream(), true); BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); out.println("Hello Server"); // 向服务器发送数据 String response = in.readLine(); // 接收服务器的响应
关闭资源:
- 线程完成任务后,关闭客户端
Socket
。
clientSocket.close();
- 线程完成任务后,关闭客户端
客户端实现步骤
连接服务器:
Socket socket = new Socket("127.0.0.1", 12345); // 连接服务器
发送和接收消息:
- 同服务端
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); PrintWriter out = new PrintWriter(socket.getOutputStream(), true); out.println("Hello, Server!"); // 发送消息 System.out.println("服务器回复:" + in.readLine()); // 接收服务器的回复
关闭资源:
- 通信完成后,关闭
Socket
和流。
socket.close();
- 通信完成后,关闭
简单TCP 客户端示例
import java.io.*;
import java.net.*;
public class TCPClient {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("localhost", 8080); // 连接到本地的 8080 端口
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out.println("Hello Server"); // 向服务器发送数据
String response = in.readLine(); // 接收服务器的响应
System.out.println("Server says: " + response);
socket.close();
}
}
简单TCP 服务器端示例
import java.io.*;
import java.net.*;
public class TCPServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8080); // 监听 8080 端口
Socket socket = serverSocket.accept(); // 接受客户端连接
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
String message = in.readLine(); // 接收客户端的消息
System.out.println("Client says: " + message);
out.println("Hello Client"); // 向客户端发送响应
socket.close();
}
}
4. UDP 编程
不考略
5. javafx中网络编程
注意点:
- JavaFX 的主线程负责绘制界面和处理用户交互。如果在主线程中执行耗时任务(例如套接字通信),界面会被阻塞,用户无法进行任何操作,比如按钮无法点击,窗口无法拖动等
- 创建新线程
- 需要while循环等待多个客户端连接
- 若想在服务端显示日志,需要通过
Platform.runLater(()->{});
来实现
两种客户端与服务器通信模式
短连接模式
(触发事件时连接服务器,逻辑结束后关闭连接):
- 优点:实现简单,资源占用少。
- 缺点:每次通信都需要重新建立连接,存在性能开销。
长连接模式
(启动时连接服务器,一直保持连接):
- 优点:性能更好,适用于频繁通信场景。
- 缺点:实现稍复杂,需要管理连接状态和异常处理。
特性 | 长连接模式 | 短连接模式 |
---|---|---|
建立连接频率 | 启动时建立一次 | 每次事件建立一次连接 |
资源利用率 | 更高效,适合频繁通信 | 资源利用稍低,适合偶尔通信 |
实现难度 | 较高,需处理长连接维护 | 简单,每次独立处理即可 |
适用场景 | 高频请求、实时性要求高的场景 | 请求频率低、无需保持连接的场景 |
短连接模式(建议使用)
1. 服务端代码(Server)
package javafx;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class Server1 extends Application {
@Override
public void start(Stage stage) {
TextArea area = new TextArea();
VBox vBox = new VBox(10);
vBox.getChildren().add(area);
Scene scene = new Scene(vBox, 300, 200);
stage.setScene(scene);
stage.show();
// 启动服务器线程
new Thread(() -> {
try (ServerSocket serverSocket = new ServerSocket(9999)) {
area.appendText("服务器已启动,等待客户端连接...\n");
while (true) {
Socket socket = serverSocket.accept(); // 接受客户端连接
DataInputStream dataInputStream = new DataInputStream(socket.getInputStream());
DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());
double height = dataInputStream.readDouble();
double weight = dataInputStream.readDouble();
double bmi = weight / (height * height); // 计算 BMI
dataOutputStream.writeDouble(bmi);
Platform.runLater(() -> area.appendText("收到客户端请求 - 身高: " + height + ", 体重: " + weight + ", BMI: " + bmi + "\n"));
}
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
}
2. 客户端代码(Client)
客户端触发事件后连接并传输信息
package javafx;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
public class Client1 extends Application {
@Override
public void start(Stage stage) {
// 设置界面
GridPane gridPane = new GridPane();
TextField textField1 = new TextField();
TextField textField2 = new TextField();
Button button = new Button("提交");
gridPane.add(new Label("身高 (米):"), 0, 0);
gridPane.add(new Label("体重 (公斤):"), 0, 1);
gridPane.add(textField1, 1, 0);
gridPane.add(textField2, 1, 1);
gridPane.add(button, 2, 1);
TextArea area = new TextArea();
VBox vBox = new VBox(10);
vBox.getChildren().addAll(gridPane, area);
Scene scene = new Scene(vBox, 300, 300);
stage.setScene(scene);
stage.show();
// 按钮事件处理
button.setOnAction(e -> {
String heightText = textField1.getText();
String weightText = textField2.getText();
if (!heightText.isEmpty() && !weightText.isEmpty()) {
try (Socket socket = new Socket("localhost", 9999);
DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());
DataInputStream dataInputStream = new DataInputStream(socket.getInputStream())) {
double height = Double.parseDouble(heightText);
double weight = Double.parseDouble(weightText);
dataOutputStream.writeDouble(height);
dataOutputStream.writeDouble(weight);
double bmi = dataInputStream.readDouble();
Platform.runLater(() -> area.appendText("你的BMI是: " + bmi + "\n"));
} catch (IOException ex) {
Platform.runLater(() -> area.appendText("连接服务器失败: " + ex.getMessage() + "\n"));
}
}
});
}
}
长连接模式
1.服务端代码
package javafx;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
public class Client extends Application {
// 定义成员变量 存储客户端连接的 流和socket
private DataOutputStream dataOutputStream = null;
private DataInputStream dataInputStream = null;
private Socket socket = null;
@Override
public void start(Stage stage) {
// 设置界面
GridPane gridPane = new GridPane();
TextField textField1 = new TextField();
TextField textField2 = new TextField();
Button button = new Button("提交");
gridPane.add(new Label("身高 (米):"), 0, 0);
gridPane.add(new Label("体重 (公斤):"), 0, 1);
gridPane.add(textField1, 1, 0);
gridPane.add(textField2, 1, 1);
gridPane.add(button, 2, 1);
// 防止无数据传输
Label label = new Label("请输入信息");
gridPane.add(label, 2, 0);
label.setVisible(false);
TextArea area = new TextArea();
VBox vBox = new VBox(10);
vBox.getChildren().addAll(gridPane, area);
Scene scene = new Scene(vBox, 300, 300);
stage.setScene(scene);
stage.show();
// 启动客户端时 连接服务器
try {
// 将连接的socket和流保存到成员变量中
socket = new Socket("localhost", 9999); // 建立连接
dataOutputStream = new DataOutputStream(socket.getOutputStream());
dataInputStream = new DataInputStream(socket.getInputStream());
area.appendText("连接服务器成功!\n");
} catch (IOException e) {
area.appendText("无法连接服务器: " + e.getMessage() + "\n");
}
// 按钮事件处理
button.setOnAction(e -> {
// 使用成员变量进行事件处理与服务端传输数据
String heightText = textField1.getText();
String weightText = textField2.getText();
if (!heightText.isEmpty() && !weightText.isEmpty()) { // 判断是否填入数据
label.setVisible(false);
try {
double height = Double.parseDouble(heightText);
double weight = Double.parseDouble(weightText);
dataOutputStream.writeDouble(height);
dataOutputStream.writeDouble(weight);
double bmi = dataInputStream.readDouble();
area.appendText("你的BMI是: " + bmi + "\n");
} catch (IOException ex) {
area.appendText("发送数据失败: " + ex.getMessage() + "\n");
}
} else {
label.setVisible(true);
}
});
}
}
2.客户端
package javafx;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Server extends Application {
@Override
public void start(Stage stage) throws Exception {
TextArea area = new TextArea();
VBox vBox = new VBox(10);
vBox.getChildren().add(area);
Scene scene = new Scene(vBox, 300, 300);
stage.setScene(scene);
stage.show();
// 启动服务器线程
new Thread(() -> {
try {
// 服务端监听指定端口
ServerSocket serverSocket = new ServerSocket(9999);
area.appendText("服务器已启动,等待客户端连接...\n"); // 输出服务器启动信息
// 死循环等连接
while (true) {
Socket socket = serverSocket.accept(); // 接受客户端连接
// 为客户端分配线程
new Thread(() -> {
try {
// 获取输入输出流
DataInputStream dataInputStream = new DataInputStream(socket.getInputStream());
DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());
// 输出客户端连接信息
Platform.runLater(() -> area.appendText("客户端已连接: " + socket.getInetAddress() +
new SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy").format(new Date()) + "\n"));
// 死循环等待客户端触发事件传输数据
while (true) {
// 持续监听客户端发送的数据
double height = dataInputStream.readDouble();
double weight = dataInputStream.readDouble();
double bmi = weight / (height * height); // 计算 BMI
dataOutputStream.writeDouble(bmi);
Platform.runLater(() -> area.appendText("收到数据 - 身高: " + height + " 体重: " + weight + " BMI: " + bmi + "\n"));
}
} catch (IOException e) {
// 输出客户端断开连接信息
Platform.runLater(() -> area.appendText("客户端断开连接: " + socket.getInetAddress() +
new SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy").format(new Date()) + "\n"));
}
}).start(); // 用线程池处理每个客户端
}
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
}
Tips
服务端显示客户端连接的时间等
Platform.runLater(() -> {
taLog.appendText(" started at "+ new SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy").format(new Date()) + '\n')
});