简介

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();

// 组播地址 (224.0.0.1 是保留的组播地址之一,可用的组播地址为 224.0.0.0 - 239.255.255.255)
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 用于加入组播组
MulticastSocket socket = new MulticastSocket(12345);

// 加入组播组 (224.0.0.1)
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();

// 创建广播地址 255.255.255.255
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(); // 关闭线程池
}
}
}

错误处理和异常管理

网络编程中,错误和异常处理非常重要,例如:

  • 网络不通
  • 超时
  • IO 异常

使用 try-catch 块捕获异常,并适时记录或处理。