网络编程是现代软件开发中不可或缺的核心技能之一。作为Java开发者,掌握网络编程不仅能让你构建分布式系统,还能深入理解互联网应用的工作原理。网络编程的本质是让运行在不同设备上的程序能够相互通信和数据交换。
网络通信需要三个基本要素:通信双方地址、通信规则和通信内容。地址用于定位网络中的设备,规则定义了如何通信,内容则是实际传输的数据。
IP地址是网络设备的唯一标识,相当于现实中的门牌号。Java中使用InetAddress类来表示和操作IP地址。这个类既支持IPv4(如192.168.1.1),也支持IPv6(如2001:0db8:85a3:0000:0000:8a2e:0370:7334)。
java复制// 获取本机IP地址示例
InetAddress localHost = InetAddress.getLocalHost();
System.out.println("本地主机地址: " + localHost.getHostAddress());
// 解析域名示例
InetAddress googleAddress = InetAddress.getByName("www.google.com");
System.out.println("Google服务器IP: " + googleAddress.getHostAddress());
端口号用于区分同一设备上的不同应用程序。端口范围是0-65535,其中0-1023是系统保留端口,开发中通常使用1024以上的端口。常见的端口号有:
网络通信协议通常采用分层模型,最著名的是TCP/IP四层模型:
这种分层设计使得各层可以独立发展,上层协议不需要关心下层的实现细节。例如,HTTP协议不需要知道底层是使用有线网络还是WiFi传输数据。
实际开发中,我们主要工作在应用层和传输层。理解这种分层结构有助于我们更好地定位和解决网络问题。
TCP(传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层协议。它的主要特点包括:
TCP通过"三次握手"建立连接,通过"四次挥手"终止连接。这个过程确保了通信双方都准备好传输数据,并且能够有序地结束通信。
Java中使用Socket和ServerSocket类实现TCP通信。下面是一个完整的TCP服务器和客户端实现示例:
TCP服务器端代码:
java复制public class TcpServer {
public static void main(String[] args) throws IOException {
// 创建服务器套接字,监听8888端口
try (ServerSocket serverSocket = new ServerSocket(8888)) {
System.out.println("服务器启动,等待客户端连接...");
// 接受客户端连接
try (Socket clientSocket = serverSocket.accept();
BufferedReader in = new BufferedReader(
new InputStreamReader(clientSocket.getInputStream()));
PrintWriter out = new PrintWriter(
clientSocket.getOutputStream(), true)) {
System.out.println("客户端已连接: " + clientSocket.getInetAddress());
// 读取客户端消息并回复
String inputLine;
while ((inputLine = in.readLine()) != null) {
System.out.println("收到客户端消息: " + inputLine);
out.println("服务器回复: " + inputLine);
if ("exit".equalsIgnoreCase(inputLine)) {
break;
}
}
}
}
}
}
TCP客户端代码:
java复制public class TcpClient {
public static void main(String[] args) {
String hostname = "localhost";
int port = 8888;
try (Socket socket = new Socket(hostname, port);
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("已连接到服务器,可以开始通信(输入'exit'退出)");
String userInput;
while ((userInput = stdIn.readLine()) != null) {
out.println(userInput);
System.out.println("服务器回复: " + in.readLine());
if ("exit".equalsIgnoreCase(userInput)) {
break;
}
}
} catch (UnknownHostException e) {
System.err.println("未知主机: " + hostname);
} catch (IOException e) {
System.err.println("连接服务器失败: " + e.getMessage());
}
}
}
文件传输是TCP通信的常见应用场景。下面是实现文件传输的关键代码片段:
服务器端接收文件:
java复制try (ServerSocket serverSocket = new ServerSocket(port);
Socket socket = serverSocket.accept();
InputStream in = socket.getInputStream();
FileOutputStream fileOut = new FileOutputStream("received_file.txt")) {
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
fileOut.write(buffer, 0, bytesRead);
}
System.out.println("文件接收完成");
}
客户端发送文件:
java复制try (Socket socket = new Socket(hostname, port);
OutputStream out = socket.getOutputStream();
FileInputStream fileIn = new FileInputStream("file_to_send.txt")) {
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = fileIn.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
System.out.println("文件发送完成");
}
实际应用中,建议添加文件校验机制(如MD5校验)确保文件传输的完整性,并考虑大文件传输时的内存优化问题。
UDP(用户数据报协议)是一种无连接的、不可靠的传输层协议。与TCP相比,UDP有以下特点:
UDP适用于对实时性要求高但允许少量丢包的应用场景,如视频会议、在线游戏、DNS查询等。
Java中使用DatagramSocket和DatagramPacket类实现UDP通信。下面是UDP通信的基本实现:
UDP服务器端代码:
java复制public class UdpServer {
public static void main(String[] args) throws IOException {
// 创建UDP套接字,绑定8888端口
try (DatagramSocket socket = new DatagramSocket(8888)) {
System.out.println("UDP服务器启动,等待客户端数据...");
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
while (true) {
// 接收数据包
socket.receive(packet);
String received = new String(
packet.getData(), 0, packet.getLength());
System.out.printf("收到来自%s:%d的消息: %s%n",
packet.getAddress(), packet.getPort(), received);
// 回复客户端
String response = "服务器已收到你的消息: " + received;
byte[] responseData = response.getBytes();
DatagramPacket responsePacket = new DatagramPacket(
responseData, responseData.length,
packet.getAddress(), packet.getPort());
socket.send(responsePacket);
}
}
}
}
UDP客户端代码:
java复制public class UdpClient {
public static void main(String[] args) throws IOException {
String hostname = "localhost";
int port = 8888;
try (DatagramSocket socket = new DatagramSocket()) {
InetAddress address = InetAddress.getByName(hostname);
// 发送消息
String message = "Hello, UDP Server!";
byte[] sendData = message.getBytes();
DatagramPacket sendPacket = new DatagramPacket(
sendData, sendData.length, address, port);
socket.send(sendPacket);
System.out.println("消息已发送: " + message);
// 接收回复
byte[] receiveData = new byte[1024];
DatagramPacket receivePacket = new DatagramPacket(
receiveData, receiveData.length);
socket.receive(receivePacket);
String response = new String(
receivePacket.getData(), 0, receivePacket.getLength());
System.out.println("收到服务器回复: " + response);
}
}
}
UDP支持广播(发送到同一网络所有主机)和多播(发送到特定组的主机)。下面是多播的实现示例:
多播发送端:
java复制public class MulticastSender {
public static void main(String[] args) throws IOException {
String multicastGroup = "230.0.0.0";
int port = 8888;
try (DatagramSocket socket = new DatagramSocket()) {
InetAddress group = InetAddress.getByName(multicastGroup);
for (int i = 0; i < 5; i++) {
String message = "多播消息 " + i;
byte[] buffer = message.getBytes();
DatagramPacket packet = new DatagramPacket(
buffer, buffer.length, group, port);
socket.send(packet);
System.out.println("发送: " + message);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
多播接收端:
java复制public class MulticastReceiver {
public static void main(String[] args) throws IOException {
String multicastGroup = "230.0.0.0";
int port = 8888;
try (MulticastSocket socket = new MulticastSocket(port)) {
InetAddress group = InetAddress.getByName(multicastGroup);
socket.joinGroup(group);
System.out.println("加入多播组,等待接收消息...");
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
while (true) {
socket.receive(packet);
String received = new String(
packet.getData(), 0, packet.getLength());
System.out.printf("收到来自%s的消息: %s%n",
packet.getAddress(), received);
}
}
}
}
使用多播时需要注意:多播地址范围是224.0.0.0到239.255.255.255,其中224.0.0.0到224.0.0.255是保留地址,不应在应用中使用。
Java NIO(New I/O)提供了非阻塞的网络编程能力,适合高并发场景。核心组件包括:
NIO服务器示例:
java复制public class NioServer {
public static void main(String[] args) throws IOException {
Selector selector = Selector.open();
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.bind(new InetSocketAddress(8888));
serverChannel.configureBlocking(false);
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("NIO服务器启动,监听8888端口...");
while (true) {
selector.select();
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iter = selectedKeys.iterator();
while (iter.hasNext()) {
SelectionKey key = iter.next();
if (key.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel client = server.accept();
client.configureBlocking(false);
client.register(selector, SelectionKey.OP_READ);
System.out.println("客户端连接: " + client.getRemoteAddress());
}
if (key.isReadable()) {
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int bytesRead = client.read(buffer);
if (bytesRead == -1) {
key.cancel();
client.close();
System.out.println("客户端断开连接");
} else if (bytesRead > 0) {
buffer.flip();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
String message = new String(bytes);
System.out.println("收到消息: " + message);
// 回复客户端
ByteBuffer response = ByteBuffer.wrap(
("服务器回复: " + message).getBytes());
client.write(response);
}
}
iter.remove();
}
}
}
}
TCP粘包/拆包问题:
连接超时处理:
java复制// 设置连接超时
Socket socket = new Socket();
socket.connect(new InetSocketAddress(hostname, port), 5000); // 5秒超时
// 设置读取超时
socket.setSoTimeout(3000); // 3秒读取超时
资源释放问题:
确保所有网络资源(套接字、流等)都在finally块或try-with-resources中关闭,防止资源泄漏。
网络安全考虑:
bash复制telnet localhost 8888
bash复制netstat -ano | findstr 8888
掌握这些工具能极大提高网络问题排查效率。例如,当TCP连接异常时,先用telnet测试端口是否可达;用netstat查看连接状态;必要时用Wireshark抓包分析具体通信过程。
在实际项目中,网络编程往往需要结合具体业务场景进行优化。例如,游戏服务器需要低延迟,可能选择UDP并实现可靠传输层;金融系统需要高可靠性,会采用TCP并添加应用层确认机制;视频直播则可能结合TCP控制信令和UDP媒体流传输。理解各种协议的特性和适用场景,才能设计出高效可靠的网络应用。