简介
Java 网络编程主要是通过套接字 (Socket) 来实现的,它允许程序在网络上进行通信。Java 提供了丰富的网络编程 API,特别是 java.net 包,其中包括一些核心类,如 Socket、ServerSocket、InetAddress 等。
根据使用的传输协议的不同将我们将网络编程分为以下两大类:
- UDP (用户数据报协议):无连接协议,适合对速度要求高但不一定需要可靠传输的应用场景。
- TCP (传输控制协议):面向连接,提供可靠的数据传输。
UDP
UDP 是一种无连接协议,适合需要快速传输而不要求可靠性的应用场景。
在客户端使用 DatagramSocket 将 DatagramPacket 发送至指定 IP 的指定端口,在服务器端使用 DatagramSocket 监听指定端口。
UDP 服务器示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| import java.net.*;
public class UDPServer { public static void main(String[] args) { try (DatagramSocket socket = new DatagramSocket(12345)) { byte[] buffer = new byte[1024]; DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
System.out.println("UDP 服务器已启动,等待数据...");
while (true) { socket.receive(packet); String received = new String(packet.getData(), 0, packet.getLength()); System.out.println("收到数据:" + received);
String response = "服务器返回:" + received; byte[] responseData = response.getBytes(); DatagramPacket responsePacket = new DatagramPacket(responseData, responseData.length, packet.getAddress(), packet.getPort()); socket.send(responsePacket); } } catch (Exception e) { e.printStackTrace(); } } }
|
UDP 客户端示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import java.net.*;
public class UDPClient { public static void main(String[] args) { try (DatagramSocket socket = new DatagramSocket()) { String message = "Hello UDP Server!"; byte[] buffer = message.getBytes();
DatagramPacket packet = new DatagramPacket(buffer, buffer.length, InetAddress.getByName("localhost"), 12345); socket.send(packet);
byte[] responseBuffer = new byte[1024]; DatagramPacket responsePacket = new DatagramPacket(responseBuffer, responseBuffer.length); socket.receive(responsePacket); String response = new String(responsePacket.getData(), 0, responsePacket.getLength()); System.out.println("从服务器收到数据:" + response); } catch (Exception e) { e.printStackTrace(); } } }
|
以上示例是基于 UDP 的单播实现,使用 UDP 还可以实现组播和广播。
在服务器使用 DatagramSocket 将 DatagramPacket 发送到指定 IP 的指定端口,在客户端使用 MulticastSocket 加入组播组监听指定端口。
UDP 组播服务器示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import java.net.*;
public class MulticastServer { public static void main(String[] args) { try (DatagramSocket socket = new DatagramSocket()) { String message = "Hello, this is a multicast message!"; byte[] buffer = message.getBytes();
InetAddress group = InetAddress.getByName("224.0.0.1");
DatagramPacket packet = new DatagramPacket(buffer, buffer.length, group, 12345);
socket.send(packet); System.out.println("组播消息已发送到组播地址:" + group.getHostAddress()); } catch (Exception e) { e.printStackTrace(); } } }
|
UDP 组播客户端示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| import java.io.IOException; import java.net.*;
public class MulticastClient { public static void main(String[] args) { try { MulticastSocket socket = new MulticastSocket(12345);
InetAddress group = InetAddress.getByName("224.0.0.1"); socket.joinGroup(group); System.out.println("已加入组播组:" + group.getHostAddress());
byte[] buffer = new byte[1024]; DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
socket.receive(packet); String received = new String(packet.getData(), 0, packet.getLength()); System.out.println("接收到的组播消息:" + received);
socket.leaveGroup(group); socket.close(); } catch (IOException e) { e.printStackTrace(); } } }
|
UDP 的广播实现与单播无太大差异,发送信号端将指定 IP 更改为 255.255.255.255 即可。
UDP 广播服务器示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import java.net.*;
public class BroadcastServer { public static void main(String[] args) { try (DatagramSocket socket = new DatagramSocket()) { socket.setBroadcast(true); String message = "Hello, this is a broadcast message!"; byte[] buffer = message.getBytes();
DatagramPacket packet = new DatagramPacket(buffer, buffer.length, InetAddress.getByName("255.255.255.255"), 12345);
socket.send(packet); System.out.println("广播消息已发送"); } catch (Exception e) { e.printStackTrace(); } } }
|
UDP 广播客户端示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import java.net.*;
public class BroadcastClient { public static void main(String[] args) { try (DatagramSocket socket = new DatagramSocket(12345)) { socket.setBroadcast(true); byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length); System.out.println("等待广播消息...");
socket.receive(packet); String received = new String(packet.getData(), 0, packet.getLength()); System.out.println("接收到的广播消息:" + received); } catch (Exception e) { e.printStackTrace(); } } }
|
TCP
TCP 是面向连接的协议,单播通信中常用 TCP 来保证数据的可靠传输。
在客户端使用 Socket 连接到服务器,在服务器使用 ServerSocket 监听连接。
服务器示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| import java.io.*; import java.net.*;
public class SimpleServer { public static void main(String[] args) { try (ServerSocket serverSocket = new ServerSocket(12345)) { System.out.println("服务器已启动,等待客户端连接...");
while (true) { Socket clientSocket = serverSocket.accept(); System.out.println("客户端已连接:" + clientSocket.getInetAddress());
new ClientHandler(clientSocket).start(); } } catch (IOException e) { e.printStackTrace(); } } }
class ClientHandler extends Thread { private Socket clientSocket;
public ClientHandler(Socket socket) { this.clientSocket = socket; }
public void run() { try ( BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true) ) { String clientInput; while ((clientInput = in.readLine()) != null) { System.out.println("收到客户端信息:" + clientInput); out.println("服务器返回:" + clientInput); } } catch (IOException e) { e.printStackTrace(); } finally { try { clientSocket.close(); } catch (IOException e) { e.printStackTrace(); } } } }
|
客户端示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import java.io.*; import java.net.*;
public class SimpleClient { public static void main(String[] args) { try (Socket socket = new Socket("localhost", 12345); 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("从服务器收到信息:" + response); } catch (IOException e) { e.printStackTrace(); } } }
|
处理并发连接
上面的例子使用多线程来处理并发连接的问题,但是这个例子的实现并不好,频繁的创建和销毁线程往往是一笔巨大的开销,所以在多客户端的场景中,使用线程池来处理并发连接是很常见的做法。
使用线程池对服务器进行改造:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import java.io.*; import java.net.*; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;
public class ThreadPoolServer { private static final int PORT = 12345;
public static void main(String[] args) { ExecutorService pool = Executors.newFixedThreadPool(10);
try (ServerSocket serverSocket = new ServerSocket(PORT)) { System.out.println("服务器已启动,等待客户端连接...");
while (true) { Socket clientSocket = serverSocket.accept(); pool.execute(new ClientHandler(clientSocket)); } } catch (IOException e) { e.printStackTrace(); } finally { pool.shutdown(); } } }
|
错误处理和异常管理
网络编程中,错误和异常处理非常重要,例如:
使用 try-catch 块捕获异常,并适时记录或处理。