网络编程是 Java 开发的核心技能之一,小到即时聊天工具、文件传输,大到分布式系统、微服务通信,都离不开网络数据交互。
目录
[1.1 什么是网络编程?](#1.1 什么是网络编程?)
[1.2 两大架构:CS vs BS](#1.2 两大架构:CS vs BS)
[1.3 核心术语:IP、端口、协议](#1.3 核心术语:IP、端口、协议)
[1.3.1 IP:互联网协议](#1.3.1 IP:互联网协议)
[1.3.2 端口:](#1.3.2 端口:)
[1.3.3 协议:](#1.3.3 协议:)
[二、UDP 通信:无连接的 "快速传输"](#二、UDP 通信:无连接的 “快速传输”)
[2.1 UDP 核心类与方法](#2.1 UDP 核心类与方法)
[2.2 UDP 一发一收](#2.2 UDP 一发一收)
[1. 接收端](#1. 接收端)
[2. 发送端](#2. 发送端)
[2.3 UDP 多发多收](#2.3 UDP 多发多收)
[1. 接收端](#1. 接收端)
[2. 发送端](#2. 发送端)
[三、TCP 通信:可靠的 "面向连接" 传输](#三、TCP 通信:可靠的 “面向连接” 传输)
[3.1 TCP 核心类与方法](#3.1 TCP 核心类与方法)
[3.2 TCP 一发一收](#3.2 TCP 一发一收)
[1. 服务器端](#1. 服务器端)
[2. 客户端](#2. 客户端)
[3.3 TCP 多发多收](#3.3 TCP 多发多收)
[1. 服务器端](#1. 服务器端)
[2. 客户端](#2. 客户端)
[3.4 支持多个客户端连接](#3.4 支持多个客户端连接)
[1. 线程处理类](#1. 线程处理类)
[2. 多客户端服务器端](#2. 多客户端服务器端)
[3.5 线程池优化](#3.5 线程池优化)
[1. 线程池优化的多客户端服务器](#1. 线程池优化的多客户端服务器)
[3.6 BS 架构原理](#3.6 BS 架构原理)
[1. HTTP 协议核心特点](#1. HTTP 协议核心特点)
[2. HTTP 请求 / 响应格式](#2. HTTP 请求 / 响应格式)
[3. 模拟 BS 架构:Java 服务器响应浏览器请求](#3. 模拟 BS 架构:Java 服务器响应浏览器请求)
[四、TCP vs UDP 核心区别](#四、TCP vs UDP 核心区别)
一、网络编程基础:
1.1 什么是网络编程?
网络编程的本质是跨设备的程序间数据传输------ 让运行在不同电脑、手机上的 Java 程序,通过网络(局域网 / 互联网)交换数据。
核心目标是:可靠、高效地传输数据。
1.2 两大架构:CS vs BS
| 架构 | 全称 | 核心特点 | 典型例子 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|---|---|
| CS | Client/Server (客户端 / 服务器) | 需安装独立客户端;服务器和客户端直接通信;可定制化程度高 | QQ、微信、王者荣耀、迅雷 | 1. 传输速度快; 2. 功能全面; 3. 安全性高 | 1. 客户端需下载安装,更新麻烦; 2. 开发维护成本高; 3. 占用设备空间 | 对体验、速度、功能要求高的场景 |
| BS | Browser/Server (浏览器 / 服务器) | 无需安装客户端;通过浏览器访问;基于 HTTP 协议 | 淘宝、百度、博客园、在线办公系统 | 1. 跨平台; 2. 无需更新; 3. 开发维护成本低 | 1. 依赖浏览器,功能受限; 2. 速度慢; 3. 安全性依赖浏览器和网络 | 对便捷性要求高、功能相对简单的场景 |
1.3 核心术语:IP、端口、协议
1.3.1 IP:互联网协议
- 定义:Internet Protocol,唯一标识网络中的一台设备,相当于现实中的 "家庭住址"。
- 格式 :
- IPv4:32 位二进制数,分成 4 段十进制(0-255),如192.168.1.1;
- IPv6:128 位二进制数,分成 8 段十六进制,如2001:0db8:85a3:0000:0000:8a2e:0370:7334。
- 常用 IP 实战说明 :
- 127.0.0.1:本地回环地址,代表 "本机",仅本机可访问,用于本地程序测试(无需联网);
- 0.0.0.0:绑定本机所有网卡的 IP,服务器用它表示 "监听所有网络接口的请求"(外网、内网都能访问);
- 内网 IP:如192.168.x.x、10.x.x.x,仅局域网内设备可访问,外网无法直接连接;
- 外网 IP:全球唯一,需通过路由器、公网服务器分配,外网设备可直接访问。
1.3.2 端口:
- 定义:唯一标识设备上的一个应用程序,相当于 "家庭住址的门牌号"------ 找到设备(IP)后,通过端口找到具体的程序。
- 范围:0-65535;
- 分类与实战注意 :
- 知名端口(0-1023):系统 / 常用服务占用,开发时避免使用(如 HTTP 的 80 端口、MySQL 的 3306 端口、SSH 的 22 端口);
- 动态端口(1024-65535):开发时可自由使用,但需注意:
- 避免使用常用软件的默认端口(如 Tomcat 的 8080、Nginx 的 80);
- 端口被占用时,会报BindException: Address already in use,需更换端口或关闭占用进程;
- 一个端口只能被一个应用程序占用,但一个应用可占用多个端口。
1.3.3 协议:
协议是设备间通信的 "约定",规定了数据的格式、传输方式、错误处理等。
Java 网络编程最核心的两个协议:
| 协议 | 连接性 | 可靠性 | 传输方式 | 数据大小限制 | 速度 | 传输过程 | 适用场景 |
|---|---|---|---|---|---|---|---|
| UDP | 无连接 | 不可靠(数据可能丢失、乱序) | 数据报(数据包独立发送) | 单次传输≤64KB | 快(无连接开销) | 1. 发送端直接封装数据为数据包;2. 无需建立连接,直接发送;3. 接收端接收数据包,不保证一定收到 | 即时通信(聊天、语音)、视频通话、广播通知、游戏数据(允许少量丢包) |
| TCP | 面向连接 | 可靠(数据不丢失、不重复、有序) | 字节流(数据连续传输) | 无限制(理论上) | 慢(需连接和确认开销) | 1. 三次握手建立连接;2. 字节流传输数据(需应用层处理边界);3. 四次挥手断开连接;4. 丢包重传、超时重连 | 文件传输、登录注册、网页访问、支付(要求数据可靠) |
补充:TCP 三次握手 / 四次挥手
- 三次握手(建立连接):像打电话 ------"喂,你在吗?"→"我在,你能听到吗?"→"能听到,开始说吧",确保双方收发正常;
- 四次挥手(断开连接):像挂电话 ------"我说完了"→"我知道你说完了,我再确认下"→"我确认完了"→"好的,挂了",确保数据传输完成。
二、UDP 通信:无连接的 "快速传输"
UDP 核心是 "数据报" 传输,无需建立连接,直接发送,适合对可靠性要求不高、追求速度的场景。下面补充核心类方法解析、缓冲区选择、常见问题等细节。
2.1 UDP 核心类与方法
| 类名 | 作用 | 核心方法 | 注意事项 |
|---|---|---|---|
DatagramSocket |
UDP 通信的 "Socket"(端口绑定、发送 / 接收数据包) | DatagramSocket():创建发送端 Socket(系统分配端口); DatagramSocket(int port):创建接收端 Socket(绑定指定端口); send(DatagramPacket p):发送数据包; receive(DatagramPacket p):接收数据包(阻塞); close():关闭 Socket,释放资源 |
1. 接收端必须绑定端口,否则发送端不知道发给哪个应用;2. 发送端可不用绑定端口,系统自动分配临时端口 |
DatagramPacket |
UDP 的 "数据包"(封装数据、目标 IP、目标端口) | DatagramPacket(byte[] buf, int length):创建接收用数据包(指定缓冲区); DatagramPacket(byte[] buf, int length, InetAddress addr, int port):创建发送用数据包(指定数据、目标 IP、端口); getData():获取数据包中的数据; getLength():获取有效数据长度; getAddress():获取发送端 IP(接收端用); getPort():获取发送端端口(接收端用) |
1. 缓冲区大小建议 1024-8192 字节(太小可能截断数据,太大浪费资源);2. 接收时getLength()获取实际数据长度,避免读取空字节 |
2.2 UDP 一发一收
核心步骤:
- 发送端(Client):创建
DatagramSocket→ 封装数据为DatagramPacket→ 发送数据;- 接收端(Server):创建
DatagramSocket并绑定端口→ 创建DatagramPacket接收数据→ 解析数据。
1. 接收端
java
import java.net.DatagramPacket;
import java.net.DatagramSocket;
/**
* UDP接收端:接收发送端的数据
*/
public class UDPServer {
public static void main(String[] args) throws Exception {
// 1. 创建接收端Socket,绑定端口(必须指定端口,否则发送端不知道发给哪个应用)
DatagramSocket socket = new DatagramSocket(8888);
// 2. 创建数据包,用于接收数据(缓冲区大小1024字节)
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
System.out.println("UDP接收端已启动,等待数据...");
// 3. 接收数据(阻塞方法,直到收到数据)
socket.receive(packet);
// 4. 解析数据
String data = new String(packet.getData(), 0, packet.getLength());
System.out.println("收到数据:" + data);
System.out.println("发送端IP:" + packet.getAddress().getHostAddress());
System.out.println("发送端端口:" + packet.getPort());
// 5. 关闭资源
socket.close();
}
}
2. 发送端
java
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/**
* UDP发送端:向接收端发送数据
*/
public class UDPClient {
public static void main(String[] args) throws Exception {
// 1. 创建发送端Socket(无需指定端口,系统自动分配)
DatagramSocket socket = new DatagramSocket();
// 2. 准备发送的数据
String sendData = "Hello UDP!我是发送端";
byte[] data = sendData.getBytes();
// 3. 封装数据包(数据+接收端IP+接收端端口)
InetAddress serverIP = InetAddress.getByName("127.0.0.1"); // 接收端IP(本地测试)
int serverPort = 8888; // 接收端端口(必须和接收端绑定的端口一致)
DatagramPacket packet = new DatagramPacket(data, data.length, serverIP, serverPort);
// 4. 发送数据
System.out.println("UDP发送端发送数据:" + sendData);
socket.send(packet);
// 5. 关闭资源
socket.close();
}
}
运行步骤:
- 先运行UDPServer(接收端);
- 再运行UDPClient(发送端);
- 接收端控制台输出:
java
UDP接收端已启动,等待数据...
收到数据:Hello UDP!我是发送端
发送端IP:127.0.0.1
发送端端口:54321(系统自动分配的端口)
2.3 UDP 多发多收
实际场景中,需要持续发送或接收数据,只需在 "一发一收" 的基础上添加循环即可。
1. 接收端
java
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class UDPMultiServer {
public static void main(String[] args) throws Exception {
DatagramSocket socket = new DatagramSocket(8888);
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
System.out.println("UDP多发多收接收端已启动,持续等待数据...");
while (true) { // 循环接收
socket.receive(packet); // 阻塞等待
String data = new String(packet.getData(), 0, packet.getLength());
System.out.println("收到[" + packet.getAddress().getHostAddress() + "]的数据:" + data);
// 若收到"exit",退出循环
if ("exit".equals(data)) {
System.out.println("接收端关闭...");
break;
}
}
socket.close();
}
}
2. 发送端
java
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;
public class UDPMultiClient {
public static void main(String[] args) throws Exception {
DatagramSocket socket = new DatagramSocket();
InetAddress serverIP = InetAddress.getByName("127.0.0.1");
int serverPort = 8888;
Scanner scanner = new Scanner(System.in);
System.out.println("UDP多发多收发送端已启动,输入exit退出...");
while (true) { // 循环发送
System.out.print("请输入发送的数据:");
String sendData = scanner.nextLine();
// 发送数据
byte[] data = sendData.getBytes();
DatagramPacket packet = new DatagramPacket(data, data.length, serverIP, serverPort);
socket.send(packet);
// 输入"exit",退出循环
if ("exit".equals(sendData)) {
System.out.println("发送端关闭...");
break;
}
}
scanner.close();
socket.close();
}
}
运行效果:
- 接收端持续监听,发送端输入多次数据,接收端实时接收;
- 输入 "exit",两端都关闭。
三、TCP 通信:可靠的 "面向连接" 传输
TCP 是 "面向连接协议",就像 "打电话"------ 先建立连接(三次握手),再传输数据,数据传输完成后断开连接(四次挥手),保证数据不丢失、不重复,适合对可靠性要求高的场景。
3.1 TCP 核心类与方法
| 类名 | 作用 | 核心方法 | 注意事项 |
|---|---|---|---|
ServerSocket |
TCP 服务器端 Socket(绑定端口、监听客户端连接) | ServerSocket(int port):绑定端口; accept():监听客户端连接(阻塞); close():关闭服务器 |
1. 端口被占用时无法启动;2. accept()返回客户端对应的Socket,一个客户端对应一个Socket |
Socket |
TCP 通信的 "连接对象"(客户端 / 服务器端都用,封装流) | Socket(String host, int port):客户端连接服务器(IP + 端口); getInputStream():获取输入流(读数据); getOutputStream():获取输出流(写数据); close():关闭连接 |
1. 客户端创建时直接连接服务器,连接失败抛异常;2. 流关闭后,Socket 也会关闭;3. 必须关闭流和 Socket,释放资源 |
InputStream/OutputStream |
字节流(读写数据) | read(byte[] b):读数据到字节数组; write(byte[] b):写字节数组到流; close():关闭流 |
1. read()阻塞,返回 - 1 表示流结束;2. 字节流读写中文易乱码,建议用字符流包装 |
BufferedReader/BufferedWriter |
字符缓冲流(优化读写效率,支持按行读写) | readLine():按行读数据(阻塞); write(String s):写字符串; newLine():换行; flush():刷新缓冲区(必须调用,否则数据可能未发送) |
1. 需用InputStreamReader/OutputStreamWriter包装字节流;2. readLine()返回 null 表示流结束;3. 必须调用flush(),否则缓冲区数据不会发送 |
3.2 TCP 一发一收
核心步骤:
- 服务器端(Server):创建
ServerSocket绑定端口→ 调用accept()等待客户端连接→ 获得Socket→ 通过流读写数据;- 客户端(Client):创建
Socket连接服务器(指定 IP 和端口)→ 通过流读写数据。
1. 服务器端
java
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* TCP服务器端:接收客户端数据并响应
*/
public class TCPServer {
public static void main(String[] args) throws Exception {
// 1. 创建服务器端Socket,绑定端口
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("TCP服务器端已启动,等待客户端连接...");
// 2. 等待客户端连接(阻塞方法,直到有客户端连接)
Socket socket = serverSocket.accept();
System.out.println("客户端已连接:" + socket.getInetAddress().getHostAddress());
// 3. 通过流接收客户端数据
InputStream in = socket.getInputStream();
byte[] buffer = new byte[1024];
int len = in.read(buffer); // 读取客户端数据
String clientData = new String(buffer, 0, len);
System.out.println("收到客户端数据:" + clientData);
// 4. 响应客户端(可选)
socket.getOutputStream().write("我已收到你的数据!".getBytes());
// 5. 关闭资源
in.close();
socket.close();
serverSocket.close();
}
}
2. 客户端
java
import java.io.InputStream;
import java.net.Socket;
/**
* TCP客户端:向服务器端发送数据并接收响应
*/
public class TCPClient {
public static void main(String[] args) throws Exception {
// 1. 创建客户端Socket,连接服务器(IP+端口)
Socket socket = new Socket("127.0.0.1", 9999);
System.out.println("TCP客户端已连接服务器...");
// 2. 向服务器发送数据
socket.getOutputStream().write("Hello TCP!我是客户端".getBytes());
// 3. 接收服务器响应
InputStream in = socket.getInputStream();
byte[] buffer = new byte[1024];
int len = in.read(buffer);
String serverData = new String(buffer, 0, len);
System.out.println("收到服务器响应:" + serverData);
// 4. 关闭资源
in.close();
socket.close();
}
}
运行效果:
- 服务器端输出:
java
TCP服务器端已启动,等待客户端连接...
客户端已连接:127.0.0.1
收到客户端数据:Hello TCP!我是客户端
- 客户端输出:
java
TCP客户端已连接服务器...
收到服务器响应:我已收到你的数据!
3.3 TCP 多发多收
和 UDP 类似,TCP 多发多收只需添加循环,但要注意:
read()方法是阻塞的,需要约定 "结束标记" 或关闭输出流,避免程序一直阻塞。
1. 服务器端
java
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class TCPMultiServer {
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("TCP多发多收服务器端已启动...");
Socket socket = serverSocket.accept();
InputStream in = socket.getInputStream();
byte[] buffer = new byte[1024];
while (true) {
int len = in.read(buffer); // 阻塞读取
if (len == -1) break; // 客户端关闭输出流,len=-1
String clientData = new String(buffer, 0, len);
System.out.println("收到客户端数据:" + clientData);
// 响应客户端
socket.getOutputStream().write(("已收到:" + clientData).getBytes());
}
// 关闭资源
in.close();
socket.close();
serverSocket.close();
}
}
2. 客户端
java
import java.io.InputStream;
import java.net.Socket;
import java.util.Scanner;
public class TCPMultiClient {
public static void main(String[] args) throws Exception {
Socket socket = new Socket("127.0.0.1", 9999);
Scanner scanner = new Scanner(System.in);
InputStream in = socket.getInputStream();
byte[] buffer = new byte[1024];
System.out.println("TCP多发多收客户端已启动,输入exit退出...");
while (true) {
System.out.print("请输入发送的数据:");
String sendData = scanner.nextLine();
// 发送数据
socket.getOutputStream().write(sendData.getBytes());
// 接收响应
int len = in.read(buffer);
String serverData = new String(buffer, 0, len);
System.out.println("服务器响应:" + serverData);
// 输入exit,关闭客户端
if ("exit".equals(sendData)) {
System.out.println("客户端关闭...");
break;
}
}
// 关闭资源(关闭输出流,服务器端read()会返回-1)
scanner.close();
in.close();
socket.close();
}
}
3.4 支持多个客户端连接
上面的 TCP 服务器端只能处理一个客户端,要支持多个客户端,需要为每个客户端分配一个独立线程 ------ 服务器端调用
accept()接收客户端后,启动线程处理该客户端的通信。
1. 线程处理类
java
import java.io.InputStream;
import java.net.Socket;
/**
* 线程类:处理单个客户端的TCP通信
*/
public class ClientHandler implements Runnable {
private Socket socket;
public ClientHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
String clientIP = socket.getInetAddress().getHostAddress();
System.out.println("客户端[" + clientIP + "]已连接");
InputStream in = socket.getInputStream();
byte[] buffer = new byte[1024];
while (true) {
int len = in.read(buffer);
if (len == -1) break;
String clientData = new String(buffer, 0, len);
System.out.println("收到[" + clientIP + "]的数据:" + clientData);
// 响应客户端
socket.getOutputStream().write(("已收到你的消息:" + clientData).getBytes());
}
// 关闭资源
in.close();
socket.close();
System.out.println("客户端[" + clientIP + "]已断开连接");
} catch (Exception e) {
e.printStackTrace();
}
}
}
2. 多客户端服务器端
java
import java.net.ServerSocket;
import java.net.Socket;
public class TCPMultiClientServer {
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("多客户端TCP服务器已启动,等待客户端连接...");
while (true) { // 循环接收客户端连接
Socket socket = serverSocket.accept(); // 阻塞等待
// 启动线程处理该客户端
new Thread(new ClientHandler(socket)).start();
}
}
}
运行效果:
- 启动服务器端后,可同时启动多个客户端,每个客户端发送的数据都会被服务器端接收并响应;
- 每个客户端的通信独立,互不干扰。
3.5 线程池优化
上面的线程版服务器端,每个客户端对应一个线程,若有 1000 个客户端,就会创建 1000 个线程,导致资源耗尽。线程池能复用线程,控制线程数量,是企业级开发的标准优化方案。
1. 线程池优化的多客户端服务器
java
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TCPThreadPoolServer {
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket(9999);
// 创建固定线程池,核心线程数为5(可根据需求调整)
ExecutorService threadPool = Executors.newFixedThreadPool(5);
System.out.println("线程池优化的TCP服务器已启动...");
while (true) {
Socket socket = serverSocket.accept();
// 线程池分配线程处理客户端
threadPool.submit(new ClientHandler(socket));
}
}
}
核心优势:
- **线程复用:**避免频繁创建 / 销毁线程,减少资源开销;
- **控制线程数量:**防止线程过多导致内存溢出;
- **提高响应速度:**线程池中有空闲线程时,直接复用处理新客户端。
线程池核心参数说明
- 核心线程数 :线程池长期维持的线程数,建议配置为
CPU核心数 * 2(如 4 核 CPU 配置 8 个); - 最大线程数:线程池可创建的最大线程数,超过核心线程数的线程会在空闲 60 秒后销毁;
- 任务队列:无空闲线程时,新任务会放入队列等待;
- 拒绝策略:队列满且达到最大线程数时,新任务的处理方式(如抛异常、丢弃任务)。
3.6 BS 架构原理
BS 架构本质是 "基于 HTTP 协议的 TCP 通信",浏览器是客户端,Web 服务器(如 Tomcat)是服务器端,通信流程如下:
- 浏览器(客户端)向 Web 服务器发送 HTTP 请求(基于 TCP);
- Web 服务器接收请求,处理后返回 HTTP 响应(包含网页数据);
- 浏览器解析响应,展示网页。
1. HTTP 协议核心特点
- 基于 TCP 协议,面向连接;
- 无状态(服务器不记住客户端信息,需用 Cookie/Session 维持状态);
- 请求 - 响应模式(浏览器发请求,服务器回响应)。
2. HTTP 请求 / 响应格式
| 类型 | 格式示例 | 说明 |
|---|---|---|
| HTTP 请求 | GET / HTTP/1.1\r\nHost: 127.0.0.1:8080\r\nUser-Agent: Chrome/114.0.0.0\r\n\r\n |
1. 首行:请求方法(GET/POST)+ 请求路径 + 协议版本;2. 请求头:键值对(如 Host、User-Agent);3. 空行:分隔请求头和请求体;4. 请求体:POST 请求才有的数据(如表单提交) |
| HTTP 响应 | HTTP/1.1 200 OK\r\nContent-Length: 100\r\nContent-Type: text/html;charset=UTF-8\r\n\r\n<html>...</html> |
1. 首行:协议版本 + 状态码(200 = 成功) + 状态描述;2. 响应头:键值对(如 Content-Type 指定返回数据类型);3. 空行:分隔响应头和响应体;4. 响应体:返回给浏览器的数据(如 HTML、CSS、JS) |
3. 模拟 BS 架构:Java 服务器响应浏览器请求
java
import java.io.BufferedWriter;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.Executors;
/**
* 模拟BS架构:浏览器访问Java服务器,返回HTML页面(详细版)
*/
public class BSServer {
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(8080);
System.out.println("BS服务器已启动,浏览器访问:http://127.0.0.1:8080");
while (true) {
Socket socket = serverSocket.accept();
// 线程池处理浏览器请求
Executors.newFixedThreadPool(5).submit(() -> {
try {
// 1. 解析浏览器请求(简化版:仅读取请求首行)
BufferedReader br = new BufferedReader(
new InputStreamReader(socket.getInputStream(), "UTF-8")
);
String requestLine = br.readLine(); // 读取请求首行(如GET / HTTP/1.1)
System.out.println("浏览器请求:" + requestLine);
// 2. 准备响应数据(HTML页面)
String html = "<!DOCTYPE html>" +
"<html lang='zh-CN'>" +
"<head><meta charset='UTF-8'><title>BS测试</title></head>" +
"<body style='text-align:center;margin-top:100px;'>" +
"<h1 style='color:blue;'>Hello BS!</h1>" +
"<p>这是Java服务器返回的HTML页面</p>" +
"<p>请求信息:" + requestLine + "</p>" +
"</body></html>";
// 3. 构建HTTP响应(必须符合格式)
String response =
"HTTP/1.1 200 OK\r\n" + // 状态码200=成功
"Content-Length: " + html.getBytes("UTF-8").length + "\r\n" + // 响应体长度
"Content-Type: text/html;charset=UTF-8\r\n" + // 数据类型:HTML,编码UTF-8
"Connection: close\r\n" + // 响应后关闭连接
"\r\n" + // 空行分隔响应头和响应体
html; // 响应体(HTML页面)
// 4. 发送响应给浏览器
BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(socket.getOutputStream(), "UTF-8")
);
bw.write(response);
bw.flush();
// 5. 关闭资源
br.close();
bw.close();
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
});
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行步骤:
- 启动BSServer;
- 打开浏览器,输入http://127.0.0.1:8080;
- 浏览器显示蓝色标题的 HTML 页面,服务器控制台打印浏览器的请求信息。
四、TCP vs UDP 核心区别
| 对比维度 | TCP | UDP |
|---|---|---|
| 连接性 | 面向连接(三次握手建立,四次挥手断开) | 无连接(直接发送,无需建立 / 断开) |
| 可靠性 | 可靠(丢包重传、超时重连、数据有序) | 不可靠(无重传、无确认,可能丢包 / 乱序) |
| 传输方式 | 字节流(数据连续,需应用层处理边界) | 数据报(数据包独立,自带边界) |
| 数据大小 | 无限制(理论上) | 单次传输≤64KB |
| 速度 | 慢(连接、确认、重传开销) | 快(无额外开销) |
| 核心类 | ServerSocket、Socket |
DatagramSocket、DatagramPacket |
| 流类型 | 字节流(需包装为字符流优化) | 无流,直接操作数据包 |
| 适用场景 | 文件传输、登录注册、网页访问、支付 | 即时聊天、视频通话、广播、游戏数据 |
| 常见问题 | 粘包(需约定数据边界)、连接超时 | 数据丢失、乱码(需统一编码) |
五、核心知识点总结
- 网络编程基础 :
- 架构:CS(客户端 / 服务器)、BS(浏览器 / 服务器);
- 核心术语:IP(设备标识)、端口(应用标识)、协议(TCP/UDP);
- TCP vs UDP:TCP 可靠连接,UDP 快速无连接。
- UDP 通信 :
- 核心类:DatagramSocket(Socket)、DatagramPacket(数据包);
- 特点:无连接、不可靠,适合多发多收、广播场景。
- TCP 通信 :
- 核心类:ServerSocket(服务器)、Socket(客户端);
- 特点:面向连接、可靠,通过流读写数据;
- 多客户端:线程 / 线程池处理,线程池是企业级优化方案。
- BS 架构:基于 HTTP 协议的 TCP 通信,浏览器是客户端,Web 服务器是服务器端。