一、Java 网络编程基础
- IP 地址和端口号
- IP 地址:
- IP 地址是互联网协议地址,用于标识网络中的设备。在 Java 中,
InetAddress
类是用于表示 IP 地址的主要类。例如,InetAddress.getByName("www.example.com")
可以获取指定域名对应的 IP 地址。IP 地址分为 IPv4(如192.168.0.1
)和 IPv6(如2001:0db8:85a3:0000:0000:8a2e:0370:7334
)两种格式。IPv4 地址由 32 位二进制数组成,通常用点分十进制表示;IPv6 地址由 128 位二进制数组成,用于提供更多的 IP 地址以满足不断增长的网络设备需求。
- IP 地址是互联网协议地址,用于标识网络中的设备。在 Java 中,
- 端口号:
- 端口号是用于标识设备上的特定网络服务或应用程序的数字。它的取值范围是 0 - 65535,其中 0 - 1023 是系统保留端口,用于一些常见的网络服务,如 HTTP 服务通常使用端口 80,HTTPS 使用端口 443。在 Java 中,当创建网络应用程序时,需要指定一个端口号来让客户端能够找到并连接到服务端。例如,一个简单的 Web 服务器可能会在端口 8080 上监听客户端请求。
- IP 地址:
- 网络通信模型
- OSI 模型和 TCP/IP 模型:
- OSI(开放式系统互联)模型是一个理论上的网络通信参考模型,分为七层,从下到上分别是物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。而 TCP/IP 模型是实际应用更为广泛的网络通信模型,它分为四层,分别是网络接口层、网络层、传输层和应用层。在 Java 网络编程中,主要涉及的是传输层(如 TCP 和 UDP 协议)和应用层。
- TCP/IP 模型的网络层主要负责处理 IP 数据包的寻址和路由,确保数据能够在不同的网络之间传输。传输层则提供端到端的通信服务,TCP 协议保证数据的可靠传输,UDP 协议提供快速但不可靠的传输。应用层是程序员直接接触的一层,在这里可以通过各种协议(如 HTTP、FTP 等)来构建网络应用程序。
- OSI 模型和 TCP/IP 模型:
二、基于 TCP 的 Java 网络编程
- Socket 和 ServerSocket 类
- ServerSocket:
ServerSocket
是服务端用于监听客户端连接请求的类。当创建一个ServerSocket
对象并指定一个端口号后,它就开始在该端口上监听。例如,ServerSocket serverSocket = new ServerSocket(8080);
表示在端口 8080 上等待客户端连接。当有客户端请求连接时,可以通过serverSocket.accept()
方法接受连接,这个方法会阻塞当前线程,直到有客户端连接成功,然后返回一个Socket
对象用于和客户端进行通信。
- Socket:
Socket
类代表一个客户端和服务端之间的连接。在客户端,可以通过Socket socket = new Socket("localhost", 8080);
来创建一个连接到本地主机(localhost
)的 8080 端口的连接。一旦连接建立,客户端和服务端就可以通过Socket
对象获取输入流和输出流来进行数据的读写。例如,服务端可以通过InputStream inputStream = socket.getInputStream();
获取客户端发送的数据输入流,通过OutputStream outputStream = socket.getOutputStream();
向客户端发送数据。
- ServerSocket:
- TCP 通信流程示例
- 服务端流程:
- 首先创建
ServerSocket
对象并开始监听端口。 - 当客户端连接请求到来时,接受连接并获取
Socket
对象。 - 通过
Socket
对象获取输入流和输出流,读取客户端发送的数据并进行处理,然后将处理结果通过输出流发送回客户端。 - 通信结束后,关闭
Socket
和ServerSocket
对象。
- 首先创建
- 客户端流程:
- 创建
Socket
对象连接到服务端。 - 获取输入流和输出流,将数据发送给服务端,并读取服务端返回的数据。
- 通信结束后,关闭
Socket
对象。
- 创建
- 服务端流程:
- 应用场景和示例代码
- 简单的文件传输服务端和客户端示例:
- 服务端代码:
- 简单的文件传输服务端和客户端示例:
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class FileTransferServer {
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(8080);
System.out.println("服务器正在等待客户端连接...");
Socket socket = serverSocket.accept();
System.out.println("客户端已连接");
InputStream inputStream = socket.getInputStream();
FileOutputStream fileOutputStream = new FileOutputStream("received_file.txt");
byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer))!= -1) {
fileOutputStream.write(buffer, 0, length);
}
System.out.println("文件接收成功");
socket.close();
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 客户端代码:
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
public class FileTransferClient {
public static void main(String[] args) {
try {
Socket socket = new Socket("localhost", 8080);
FileInputStream fileInputStream = new FileInputStream("source_file.txt");
OutputStream outputStream = socket.getOutputStream();
byte[] buffer = new byte[1024];
int length;
while ((length = fileInputStream.read(buffer))!= -1) {
outputStream.write(buffer, 0, length);
}
System.out.println("文件发送成功");
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 在这个示例中,客户端将本地的
source_file.txt
文件发送给服务端,服务端接收文件并保存为received_file.txt
。通过 TCP 协议的可靠传输保证文件内容的完整性。
三、基于 UDP 的 Java 网络编程
- DatagramSocket 和 DatagramPacket 类
- DatagramSocket:
DatagramSocket
类用于发送和接收 UDP 数据包。在服务端和客户端都可以使用这个类。在服务端,可以使用DatagramSocket
在指定端口监听 UDP 数据包;在客户端,可以使用它向指定的 IP 地址和端口发送 UDP 数据包。例如,DatagramSocket socket = new DatagramSocket();
创建一个 UDP 套接字,用于发送和接收 UDP 数据包。
- DatagramPacket:
DatagramPacket
类用于表示 UDP 数据包。它包含了要发送或接收的数据、数据的长度、目标 IP 地址和端口号(对于发送数据包)或者源 IP 地址和端口号(对于接收数据包)。例如,创建一个发送数据包可以使用byte[] data = "Hello".getBytes(); DatagramPacket packet = new DatagramPacket(data, data.length, InetAddress.getByName("localhost"), 8080);
,这里指定了要发送的数据为Hello
,目标地址为本地主机的 8080 端口。
- DatagramSocket:
- UDP 通信流程示例
- 服务端流程:
- 创建
DatagramSocket
对象并绑定到指定端口。 - 创建
DatagramPacket
对象用于接收数据,通过DatagramSocket
的receive
方法接收数据包。 - 处理接收到的数据,然后可以创建新的
DatagramPacket
对象将处理结果发送回客户端。 - 通信结束后,关闭
DatagramSocket
对象。
- 创建
- 客户端流程:
- 创建
DatagramSocket
对象(如果不需要接收数据,也可以不创建)。 - 创建
DatagramPacket
对象,将数据填充进去,通过DatagramSocket
的send
方法发送数据包给服务端。 - 如果需要接收服务端返回的数据,可以创建接收数据包并使用
receive
方法接收。 - 通信结束后,关闭
DatagramSocket
对象(如果创建了的话)。
- 创建
- 服务端流程:
- 应用场景和示例代码
- 简单的 UDP 消息发送和接收示例:
- 服务端代码:
- 简单的 UDP 消息发送和接收示例:
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) {
try {
DatagramSocket socket = new DatagramSocket(8080);
byte[] buffer = new byte[1024];
DatagramPacket receivePacket = new DatagramPacket(buffer, buffer.length);
socket.receive(receivePacket);
String message = new String(receivePacket.getData(), 0, receivePacket.getLength());
System.out.println("收到消息: " + message);
InetAddress clientAddress = receivePacket.getAddress();
int clientPort = receivePacket.getPort();
String response = "已收到你的消息";
byte[] responseData = response.getBytes();
DatagramPacket sendPacket = new DatagramPacket(responseData, responseData.length, clientAddress, clientPort);
socket.send(sendPacket);
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 客户端代码:
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class UDPClient {
public static void main(String[] args) {
try {
DatagramSocket socket = new DatagramSocket();
String message = "你好";
byte[] data = message.getBytes();
InetAddress serverAddress = InetAddress.getByName("localhost");
DatagramPacket packet = new DatagramPacket(data, data.length, serverAddress, 8080);
socket.send(packet);
byte[] buffer = new byte[1024];
DatagramPacket receivePacket = new DatagramPacket(buffer, buffer.length);
socket.receive(receivePacket);
String response = new String(receivePacket.getData(), 0, receivePacket.getLength());
System.out.println("收到回复: " + response);
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 在这个示例中,客户端向服务端发送一个简单的消息,服务端接收消息后发送一个回复给客户端。UDP 协议的快速性使得这种简单的消息交互能够快速完成,但不保证消息一定能完整和准确地到达。
四、高级网络编程主题
- 多线程在网络编程中的应用
- 在网络编程中,尤其是服务端编程,为了能够同时处理多个客户端的连接请求,通常会使用多线程。例如,在基于 TCP 的服务端中,当接受一个客户端连接后,可以为这个客户端连接创建一个新的线程来处理后续的通信,这样服务端就可以继续监听其他客户端的连接请求,而不会因为一个客户端的长时间通信而阻塞。
- 以下是一个简单的基于多线程的 TCP 服务端示例:
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
class ClientHandler implements Runnable {
private Socket socket;
public ClientHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();
byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer))!= -1) {
String message = new String(buffer, 0, length);
System.out.println("收到客户端消息: " + message);
String response = "已收到你的消息";
outputStream.write(response.getBytes());
}
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class MultiThreadedTCPServer {
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(8080);
System.out.println("服务器正在等待客户端连接...");
while (true) {
Socket socket = serverSocket.accept();
System.out.println("客户端已连接");
Thread thread = new Thread(new ClientHandler(socket));
thread.start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 在这个示例中,
ClientHandler
类实现了Runnable
接口,用于处理单个客户端的通信。在MultiThreadedTCPServer
的main
方法中,每当有客户端连接时,就创建一个新的线程来运行ClientHandler
,这样就可以同时处理多个客户端的通信。
网络安全相关概念(SSL/TLS)
- SSL/TLS 简介:
- SSL(安全套接层)和 TLS(传输层安全)是用于在网络通信中提供安全加密的协议。它们可以保证数据在传输过程中的保密性、完整性和认证性。在 Java 中,可以通过
javax.net.ssl
包来实现 SSL/TLS 加密的网络通信。例如,在基于 TCP 的服务端和客户端通信中,可以使用 SSL/TLS 来加密传输的数据,防止数据被窃取或篡改。
- SSL(安全套接层)和 TLS(传输层安全)是用于在网络通信中提供安全加密的协议。它们可以保证数据在传输过程中的保密性、完整性和认证性。在 Java 中,可以通过
- 使用示例(简单介绍):
- 首先需要创建密钥库(KeyStore)和信任库(TrustStore),用于存储服务器和客户端的证书、私钥等信息。然后在创建
ServerSocket
和Socket
对象时,通过SSLContext
等相关类来配置 SSL/TLS 加密。具体的实现较为复杂,涉及到证书的生成、配置和管理等多个步骤,但这样可以为网络通信提供更高的安全性。
- 首先需要创建密钥库(KeyStore)和信任库(TrustStore),用于存储服务器和客户端的证书、私钥等信息。然后在创建
- SSL/TLS 简介:
Java 网络编程中的 NIO(非阻塞 I/O)
- NIO 简介:
- Java NIO(New Input/Output)是一种非阻塞式的 I/O 操作方式,相比于传统的阻塞式 I/O,它可以在一个线程中同时处理多个通道(Channel)的 I/O 事件,提高了网络编程的效率。NIO 主要涉及三个核心组件:通道(Channel)、缓冲区(Buffer)和选择器(Selector)。
- 核心组件介绍:
- 通道(Channel):通道类似于传统 I/O 中的流(Stream),但它是双向的,可以用于读写数据。在网络编程中,有
SocketChannel
(用于 TCP 通信)、DatagramChannel
(用于 UDP 通信)等。例如,SocketChannel socketChannel = SocketChannel.open();
可以打开一个 TCP 通道。 - 缓冲区(Buffer):缓冲区是用于存储数据的容器,在 NIO 中,数据的读写都是通过缓冲区进行的。例如,
ByteBuffer buffer = ByteBuffer.allocate(1024);
创建了一个容量为 1024 字节的字节缓冲区。 - 选择器(Selector):选择器用于监听多个通道的 I/O 事件,如连接就绪、读就绪、写就绪等。一个线程可以通过一个选择器来管理多个通道,当某个通道有 I/O 事件发生时,选择器会通知该线程进行处理。例如,
Selector selector = Selector.open();
创建一个选择器,然后可以将通道注册到选择器上,并设置感兴趣的 I/O 事件。
- 通道(Channel):通道类似于传统 I/O 中的流(Stream),但它是双向的,可以用于读写数据。在网络编程中,有
- 简单示例(以 TCP 为例):
- NIO 简介:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
public class NIOServer {
public static void main(String[] args) {
try {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(8080));
serverSocketChannel.configureBlocking(false);
Selector selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("服务器正在等待客户端连接...");
while (true) {
selector.select();
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isAcceptable()) {
ServerSocketChannel acceptedChannel = (ServerSocketChannel) key.channel();
SocketChannel socketChannel = acceptedChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
SocketChannel socketChannel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int length = socketChannel.read(buffer);
if (length == -1) {
socketChannel.close();
key.cancel();
} else {
buffer.flip();
System.out.println("收到客户端消息: " + new String(buffer.array(), 0, length));
ByteBuffer responseBuffer = ByteBuffer.wrap("已收到你的消息".getBytes());
socketChannel.write(responseBuffer);
}