1.UDP通信实现

1.1 UDP 通信实现

- 以下是 UDP 通信的 Java 代码案例,包含客户端和服务端,并附有详细注释:
UDP 服务端代码

java
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class UDPServer {
public static void main(String[] args) throws Exception {
// 创建服务端 DatagramSocket 对象,指定端口号为 8888
DatagramSocket serverSocket = new DatagramSocket(8888);
System.out.println("UDP 服务端已启动,监听端口 8888...");
// 准备字节数组用于接收数据,长度设为 1024
byte[] receiveData = new byte[1024];
// 创建用于接收数据的 DatagramPacket 对象
DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
// 接收客户端发送的数据
serverSocket.receive(receivePacket);
// 将接收到的字节数据转换为字符串
String clientMessage = new String(receivePacket.getData(), 0, receivePacket.getLength());
// 获取客户端的 IP 地址
InetAddress clientAddress = receivePacket.getAddress();
// 获取客户端的端口号
int clientPort = receivePacket.getPort();
System.out.println("收到客户端 " + clientAddress + ":" + clientPort + " 的消息:" + clientMessage);
// 准备要发送给客户端的响应数据
String response = "服务端已收到你的消息,这是我的回复!";
byte[] sendData = response.getBytes();
// 创建要发送的 DatagramPacket 对象,指定客户端的 IP 和端口
DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, clientAddress, clientPort);
// 发送响应数据给客户端
serverSocket.send(sendPacket);
System.out.println("已向客户端发送响应:" + response);
// 关闭 DatagramSocket
serverSocket.close();
}
}
UDP 客户端代码

java
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class UDPClient {
public static void main(String[] args) throws Exception {
// 创建客户端 DatagramSocket 对象,系统随机分配端口
DatagramSocket clientSocket = new DatagramSocket();
// 服务端的 IP 地址(这里用本地回环地址表示本机服务端)
InetAddress serverAddress = InetAddress.getByName("localhost");
// 服务端的端口号,要和服务端指定的一致
int serverPort = 8888;
// 准备要发送给服务端的消息
String message = "你好,UDP 服务端!我是客户端~";
byte[] sendData = message.getBytes();
// 创建要发送的 DatagramPacket 对象,指定服务端的 IP 和端口
DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, serverAddress, serverPort);
// 发送消息给服务端
clientSocket.send(sendPacket);
System.out.println("已向服务端发送消息:" + message);
// 准备字节数组用于接收服务端的响应,长度设为 1024
byte[] receiveData = new byte[1024];
// 创建用于接收响应的 DatagramPacket 对象
DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
// 接收服务端的响应
clientSocket.receive(receivePacket);
// 将接收到的字节数据转换为字符串
String serverResponse = new String(receivePacket.getData(), 0, receivePacket.getLength());
System.out.println("收到服务端的响应:" + serverResponse);
// 关闭 DatagramSocket
clientSocket.close();
}
}
代码注释说明
-
DatagramSocket 类:
- 服务端通过 new DatagramSocket(8888) 指定端口创建,用于监听客户端请求;客户端通过 new DatagramSocket() 随机分配端口创建,用于发起通信。
- send(DatagramPacket dp) 方法用于发送数据包,receive(DatagramPacket p) 方法用于接收数据包。
-
DatagramPacket 类:
- 发送数据时,使用 DatagramPacket(byte[] buf, int length, InetAddress address, int port) 构造器,指定数据字节数组、长度、目标 IP 和端口。
- 接收数据时,使用 DatagramPacket(byte[] buf, int length) 构造器,指定用于存储数据的字节数组和长度。
-
通信流程:客户端发送消息给服务端,服务端接收并回复,客户端再接收服务端的回复,完成一次 UDP 通信。

2.TCP通信

-
以下是 TCP 通信客户端的完整代码案例,并附带详细注释:
java
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
public class TCPClient {
public static void main(String[] args) throws IOException {
// 1. 创建Socket对象,请求与服务端建立连接
// host:服务端IP地址(这里用localhost表示本机服务端)
// port:服务端监听的端口号(需与服务端保持一致,示例用8888)
Socket socket = new Socket("localhost", 8888);
System.out.println("客户端Socket创建成功,已与服务端建立连接");
// 2. 获取字节输出流,用于向服务端发送数据
OutputStream os = socket.getOutputStream();
// 发送的消息内容
String message = "你好,TCP服务端!我是客户端,这是我的消息~";
// 将字符串转换为字节数组并发送
os.write(message.getBytes());
System.out.println("客户端已向服务端发送消息:" + message);
// 3. 获取字节输入流,用于接收服务端的响应数据
InputStream is = socket.getInputStream();
// 准备字节数组存储接收的数据
byte[] buffer = new byte[1024];
// 读取服务端响应的数据
int length = is.read(buffer);
// 将字节数据转换为字符串
String response = new String(buffer, 0, length);
System.out.println("客户端收到服务端的响应:" + response);
// 4. 关闭资源(顺序:先关闭流,再关闭Socket)
is.close();
os.close();
socket.close();
System.out.println("客户端资源已关闭");
}
}

- 优化上面代码
- 以下是使用 DataOutputStream 实现的 TCP 客户端代码案例,DataOutputStream 可以更方便地发送各种基本数据类型(如字符串、整数等),无需手动转换字节数组,注释详细且清晰:
java
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
public class TCPDataClient {
public static void main(String[] args) {
// 定义服务端IP和端口(本地测试用localhost,端口需与服务端一致)
String serverIp = "localhost";
int serverPort = 8888;
Socket socket = null;
DataOutputStream dos = null;
InputStream is = null;
try {
// 1. 创建Socket,与服务端建立TCP连接(三次握手在此过程中自动完成)
socket = new Socket(serverIp, serverPort);
System.out.println("客户端已与服务端建立连接:" + socket);
// 2. 获取输出流,并包装为DataOutputStream(方便发送各种数据类型)
OutputStream os = socket.getOutputStream();
dos = new DataOutputStream(os); // 包装字节输出流为数据输出流
// 3. 使用DataOutputStream发送数据(支持多种数据类型,无需手动转字节)
// 发送字符串(注意:writeUTF会自动处理字符串长度,服务端需用readUTF接收)
dos.writeUTF("你好,服务端!这是用DataOutputStream发送的字符串~");
// 发送整数
dos.writeInt(2024); // 发送年份
// 发送布尔值
dos.writeBoolean(true); // 表示"消息发送完成"
// 强制刷新缓冲区,确保数据立即发送(避免因缓冲区未满导致延迟)
dos.flush();
System.out.println("客户端已发送数据(字符串+整数+布尔值)");
// 4. 接收服务端的响应(简单示例:接收字符串)
is = socket.getInputStream();
byte[] buffer = new byte[1024];
int len = is.read(buffer); // 读取服务端返回的字节
String response = new String(buffer, 0, len);
System.out.println("收到服务端响应:" + response);
} catch (IOException e) {
e.printStackTrace(); // 捕获连接或IO异常(如服务端未启动、网络中断等)
} finally {
// 5. 关闭资源(后打开的先关闭,避免资源泄露)
try {
if (is != null) is.close(); // 关闭输入流
if (dos != null) dos.close(); // 关闭数据输出流(会自动关闭包装的字节流)
if (socket != null) socket.close(); // 关闭Socket(四次挥手在此过程中自动完成)
System.out.println("客户端资源已关闭");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

- 服务端

- 以下是配合客户端(使用 DataOutputStream)的 TCP 服务端代码案例,服务端通过 DataInputStream 接收客户端发送的多种数据类型,并使用 DataOutputStream 回复,注释详细:
java
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class TCPDataServer {
public static void main(String[] args) {
// 服务端监听的端口(需与客户端连接的端口一致)
int port = 8888;
ServerSocket serverSocket = null;
Socket socket = null;
try {
// 1. 创建ServerSocket,注册端口并启动服务端
serverSocket = new ServerSocket(port);
System.out.println("TCP服务端已启动,监听端口:" + port + "(等待客户端连接...)");
// 2. 阻塞等待客户端连接(accept()会一直等待,直到有客户端连接成功)
// 连接成功后返回Socket对象,代表与该客户端的通信通道
socket = serverSocket.accept();
System.out.println("客户端已连接:" + socket.getInetAddress() + ":" + socket.getPort());
// 3. 获取输入流,包装为DataInputStream(接收客户端用DataOutputStream发送的数据)
InputStream is = socket.getInputStream();
DataInputStream dis = new DataInputStream(is);
// 4. 接收客户端发送的多种数据类型(需与客户端发送顺序一致)
// 接收字符串(对应客户端的writeUTF(),必须用readUTF()读取)
String clientMsg = dis.readUTF();
// 接收整数(对应客户端的writeInt())
int year = dis.readInt();
// 接收布尔值(对应客户端的writeBoolean())
boolean isCompleted = dis.readBoolean();
// 打印接收的数据
System.out.println("收到客户端数据:");
System.out.println("字符串消息:" + clientMsg);
System.out.println("整数(年份):" + year);
System.out.println("布尔值(是否完成):" + isCompleted);
// 5. 向客户端发送响应(使用DataOutputStream)
OutputStream os = socket.getOutputStream();
DataOutputStream dos = new DataOutputStream(os);
// 发送响应字符串
dos.writeUTF("服务端已收到所有数据!字符串、整数、布尔值均解析成功~");
dos.flush(); // 立即发送
System.out.println("已向客户端发送响应");
} catch (IOException e) {
e.printStackTrace(); // 处理连接或IO异常(如端口被占用、客户端断开等)
} finally {
// 6. 关闭资源(先关闭客户端Socket,再关闭ServerSocket)
try {
if (socket != null) socket.close(); // 关闭与客户端的连接(触发四次挥手)
if (serverSocket != null) serverSocket.close(); // 关闭服务端
System.out.println("服务端资源已关闭");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
总结
- 用 DataOutputStream 和 DataInputStream 进行 TCP 通信时,必须满足 "类型严格匹配、顺序严格一致、数量严格相等" 这三个条件,否则会直接导致数据解析错误或程序崩溃。
核心规则(一句话总结):
发送方用 writeXXX() 发什么类型、按什么顺序发、发多少个,接收方就必须用 readXXX() 按相同顺序、相同数量、相同类型接收。

- 多发多收-不能关服务器

2.1 如何支持多个客户端的同时通信

- 原理

-
- 服务端主类(负责接收连接并创建子线程)

- 服务端主类(负责接收连接并创建子线程)
java
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class TCPServer {
public static void main(String[] args) throws IOException {
// 1. 创建ServerSocket,绑定端口(如8888)
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("服务端启动,监听端口8888,等待客户端连接...");
// 2. 循环接收客户端连接(主线程核心逻辑)
while (true) {
// 阻塞等待客户端连接,连接成功后返回Socket对象(与该客户端的通信通道)
Socket socket = serverSocket.accept();
// 打印客户端信息(IP和端口)
System.out.println("新客户端连接:" + socket.getInetAddress() + ":" + socket.getPort());
// 3. 为每个客户端创建独立子线程,负责处理与该客户端的通信
new ClientHandlerThread(socket).start();
}
}
}
-
- 子线程类(负责与单个客户端通信)
java
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
// 自定义线程类,处理单个客户端的读写操作
class ClientHandlerThread extends Thread {
private Socket socket; // 与当前客户端的连接通道
// 构造方法:传入客户端Socket
public ClientHandlerThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
DataInputStream dis = null;
DataOutputStream dos = null;
try {
// 1. 获取输入流(读客户端消息)和输出流(向客户端发消息)
dis = new DataInputStream(socket.getInputStream());
dos = new DataOutputStream(socket.getOutputStream());
// 2. 循环与客户端通信(直到客户端断开连接)
while (true) {
// 读取客户端发送的消息(这里以字符串为例)
String clientMsg = dis.readUTF();
System.out.println("收到客户端[" + socket.getPort() + "]的消息:" + clientMsg);
// 向客户端发送响应
String response = "服务端已收到:" + clientMsg;
dos.writeUTF(response);
dos.flush();
}
} catch (IOException e) {
// 客户端断开连接时会触发异常(如关闭窗口),这里捕获并处理
System.out.println("客户端[" + socket.getPort() + "]已断开连接");
} finally {
// 3. 关闭资源(与该客户端的连接)
try {
if (dis != null) dis.close();
if (dos != null) dos.close();
if (socket != null) socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
-
- 客户端类(可同时启动多个实例模拟多客户端)
java
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.Scanner;
public class TCPClient {
public static void main(String[] args) throws IOException {
// 连接服务端(IP为localhost,端口8888)
Socket socket = new Socket("localhost", 8888);
System.out.println("客户端已连接服务端");
// 获取输入流(读服务端响应)和输出流(向服务端发消息)
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
DataInputStream dis = new DataInputStream(socket.getInputStream());
Scanner scanner = new Scanner(System.in);
// 循环发送消息给服务端
while (true) {
System.out.print("请输入要发送的消息(输入exit退出):");
String msg = scanner.nextLine();
if ("exit".equals(msg)) break;
// 发送消息
dos.writeUTF(msg);
dos.flush();
// 接收服务端响应
String response = dis.readUTF();
System.out.println("服务端响应:" + response);
}
// 关闭资源
scanner.close();
dis.close();
dos.close();
socket.close();
System.out.println("客户端已断开连接");
}
}

3.BS架构,客户端就是浏览器,无需开发
- 开发服务端


3.1以下是模拟 BS 架构中 HTTP 响应格式的服务端代码(用 Java 实现),模拟 Web 服务器向浏览器发送符合 HTTP 协议的响应数据,包含详细注释说明 HTTP 响应格式的每一部分:
- 模拟 Web 服务器代码(核心:按 HTTP 协议格式返回数据)
java
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class SimpleWebServer {
public static void main(String[] args) throws Exception {
// 1. 创建服务器Socket,监听8080端口(HTTP默认端口是80,这里用8080方便本地测试)
ServerSocket serverSocket = new ServerSocket(8080);
System.out.println("简易Web服务器启动,监听端口8080,等待浏览器连接...");
while (true) {
// 2. 等待浏览器(客户端)连接(类似网页访问)
Socket socket = serverSocket.accept();
System.out.println("浏览器已连接:" + socket.getInetAddress());
// 3. 获取输出流,向浏览器发送HTTP响应(必须严格遵循HTTP协议格式)
OutputStream os = socket.getOutputStream();
// 4. 构建HTTP响应内容(分三部分:状态行 + 响应头 + 响应正文)
// 注意:HTTP协议要求每行结尾必须是 "\r\n"(回车换行),且响应头与正文之间必须有一个单独的 "\r\n"
// 4.1 状态行:协议版本 状态码 状态描述(第一行固定格式)
String statusLine = "HTTP/1.1 200 OK\r\n"; // HTTP/1.1是协议版本,200是成功状态码,OK是描述
// 4.2 响应头:键值对格式,说明响应数据的类型、长度等信息(可多个)
// Content-Type:告诉浏览器响应正文的类型(text/html表示HTML内容,charset=UTF-8确保中文不乱码)
String contentType = "Content-Type: text/html; charset=UTF-8\r\n";
// 可选:Content-Length(响应正文的字节长度,浏览器可据此判断数据是否接收完整)
String contentLength = "Content-Length: 156\r\n"; // 这里156是下面HTML内容的大致字节数
// 4.3 空行:响应头结束的标志(必须有,否则浏览器无法区分头和正文)
String emptyLine = "\r\n";
// 4.4 响应正文:真正展示给用户的内容(这里是HTML代码,浏览器会解析并显示)
String content = "<html>\n" +
" <head><title>BS架构示例</title></head>\n" +
" <body>\n" +
" <h1 style='color: blue; text-align: center'>欢迎访问我的网页</h1>\n" +
" <p>这是遵循HTTP协议的响应内容</p>\n" +
" <p>当前时间:" + System.currentTimeMillis() + "</p>\n" +
" </body>\n" +
"</html>";
// 5. 拼接完整响应数据,按HTTP格式发送给浏览器
String response = statusLine + contentType + contentLength + emptyLine + content;
os.write(response.getBytes("UTF-8")); // 用UTF-8编码发送,确保中文正常显示
os.flush();
// 6. 关闭连接(模拟短连接,一次请求后断开)
socket.close();
System.out.println("响应已发送,连接关闭");
}
}
}
运行结果
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Content-Length: 156
<html>
<head><title>BS架构示例</title></head>
<body>
<h1 style='color: blue; text-align: center'>欢迎访问我的网页</h1>
<p>这是遵循HTTP协议的响应内容</p>
<p>当前时间:1620000000000</p>
</body>
</html>


3.2 用线程池进行优化
- 在 BS 架构中,使用线程池优化服务端的核心思路是:用线程池管理处理客户端请求的线程,避免频繁创建 / 销毁线程的开销,提高并发处理效率。以下是基于线程池优化的简易 Web 服务器代码,附带详细注释说明优化逻辑:
java
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolWebServer {
public static void main(String[] args) throws Exception {
// 1. 创建线程池(核心优化点)
// 这里用固定大小线程池,核心线程数设为3(可根据服务器性能调整)
// 线程池会复用线程,避免每次连接都新建线程
ExecutorService threadPool = Executors.newFixedThreadPool(3);
System.out.println("线程池优化的Web服务器启动,线程池大小:3,监听端口8080...");
// 2. 创建ServerSocket,监听端口
ServerSocket serverSocket = new ServerSocket(8080);
while (true) {
// 3. 主线程接收浏览器连接(不变)
Socket socket = serverSocket.accept();
System.out.println("新浏览器连接:" + socket.getInetAddress());
// 4. 将客户端处理任务提交给线程池(核心优化)
// 线程池会从池中取空闲线程执行任务,若无空闲线程则放入任务队列等待
threadPool.execute(new ClientHandler(socket));
}
}
// 处理单个客户端请求的任务类(实现Runnable,供线程池调用)
static class ClientHandler implements Runnable {
private Socket socket;
public ClientHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
OutputStream os = null;
try {
// 5. 按HTTP协议格式向浏览器发送响应(逻辑与之前一致)
os = socket.getOutputStream();
// 构建HTTP响应(状态行+响应头+空行+正文)
String statusLine = "HTTP/1.1 200 OK\r\n";
String contentType = "Content-Type: text/html; charset=UTF-8\r\n";
String emptyLine = "\r\n";
String content = "<html>\n" +
" <body>\n" +
" <h1>线程池优化的响应</h1>\n" +
" <p>处理当前请求的线程:" + Thread.currentThread().getName() + "</p>\n" +
" </body>\n" +
"</html>";
// 拼接响应并发送
String response = statusLine + contentType + emptyLine + content;
os.write(response.getBytes("UTF-8"));
os.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 6. 关闭连接(每个任务独立释放资源)
try {
if (os != null) os.close();
if (socket != null) socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}

