1) 两类常见网络架构
通常把网络应用的通信架构分为两大类(最常见的划分):
A. Client — Server(客户端/服务器,集中式)
-
结构:一个或多个客户端向一个或多个集中式服务器发起请求;服务器负责认证、存储、业务处理。
-
特点:
-
中心化管理(便于权限、监控、数据一致性)。
-
容易扩展为负载均衡 + 后端微服务。
-
-
优点:方便维护、统一安全策略、数据集中。
-
缺点:单点或多点故障、扩展需水平扩容/负载均衡、延迟可能较高。
-
典型场景:Web(HTTP REST)、企业后台、数据库访问、RPC 服务(gRPC)。
B. Peer-to-Peer(点对点,去中心化)
-
结构:每个节点既可以是客户端也可以是服务器,节点之间直接通信。
-
特点:
-
去中心化,节点间共享负载。
-
需要处理 NAT 穿透、节点发现、一致性问题。
-
-
优点:可扩展性强、去中心化、无单点瓶颈。
-
缺点:实现更复杂(发现、搜索、数据一致性),安全控制更难。
-
典型场景:文件共享(BT/Torrent)、分布式存储、区块链节点、部分 P2P 聊天/视频通话(结合 STUN/TURN)。
附加:在网络编程维度上,也常把通信模型分成“阻塞 IO(BIO)/线程每连接”与“非阻塞 IO(NIO)/事件驱动”两类实现风格(这影响架构选型,见下文注意事项)。
2) 网络编程“三要素”(Address、Port、Protocol)
常用的表述是:地址(Address) + 端口(Port) + 协议(Protocol) —— 这三样决定了一次网络通信能否建立和如何通信。
要素 A:地址(Address)
含义:主机标识符 — IPv4(例如 192.168.1.10)、IPv6(例如 2001:db8::1)或 DNS 名称(example.com)。
Java 常用 API:
-
java.net.InetAddress:解析主机名、判断本机地址。-
InetAddress.getByName(String host):解析 hostname →InetAddress(阻塞,可能做 DNS 查询)。 -
InetAddress.getLocalHost():返回本机地址(可能受主机名配置影响)。
-
-
绑定接口时可用
InetAddress指定网卡。
重要参数 / 注意点:
-
IPv4 vs IPv6:注意字节长度和文本表示;跨网段、双栈主机需考虑优先策略。
-
DNS 解析延迟:
getByName会做 DNS 查找,可能阻塞,应避免在主应用主线程中进行大量同步 DNS 查询(可缓存结果或异步)。 -
多网卡/多地址:一台主机可能有多个 IP(多网卡),Server 绑定
0.0.0.0表示监听所有接口。 -
NAT / 内网与公网:在内网中,地址可能是私网地址(需要公网访问需做端口映射/反向代理)。
常见场景:
-
InetAddress用于连接前解析、做白名单校验或获取本机 IP。
要素 B:端口(Port)
含义:主机上的逻辑端点,范围 0 ~ 65535(0 表示系统分配临时端口,0~1023 常为系统/特权端口)。
Java 常用 API / 操作:
-
ServerSocket(int port):服务端绑定端口(参数port= 0 表示让系统分配可用端口)。 -
Socket(String host, int port):客户端连接目标主机和端口。 -
DatagramSocket(int port):UDP 绑定端口。
重要参数 / 注意点:
-
backlog(等待队列长度):
new ServerSocket(port, backlog),影响在高并发下拥塞时连接队列的长度,默认值平台不同。 -
端口冲突:若端口被占用,会抛
BindException。 -
SO_REUSEADDR:服务器快速重启时,是否允许重用地址(在 Java 中ServerSocket.setReuseAddress(true))。 -
临时(ephemeral)端口:客户端通常使用系统分配的短期端口用于 outbound 连接。
-
防火墙/NAT:端口必须在防火墙或路由器中开放/映射才可被远程访问。
常见场景:
-
常驻服务使用固定端口(HTTP :80/443 或自定义端口)。
-
客户端一般不指定端口(或使用 0),让系统分配。
要素 C:协议(Protocol)
分层说明:
-
传输层协议(TCP、UDP、(SCTP/QUIC…))决定可靠性、连接性、顺序性。
-
应用层协议(HTTP、WebSocket、gRPC、MQTT 等)定义数据格式、语义与握手流程。
Java 对应 API:
-
TCP(流式):
Socket/ServerSocket/SocketChannel/AsynchronousSocketChannel -
UDP(报文):
DatagramSocket/DatagramPacket/DatagramChannel -
HTTP/HTTPS:
java.net.http.HttpClient(Java11+)、Servlet、Spring Web -
WebSocket:
javax.websocket(服务器端)或java.net.http.WebSocket(客户端) -
gRPC/MQTT:第三方库(gRPC-Java、Eclipse Paho 等)
选择协议时的关键考量(影响使用场景):
-
是否需要 可靠传输(丢包重传 / 顺序保证)?→ TCP
-
是否需要 低延迟、允许丢包(实时语音/视频、游戏位置更新)?→ UDP
-
是否需要 浏览器兼容?→ HTTP/WebSocket
-
是否需要 轻量、省带宽、QoS(消息等级)?→ MQTT(物联网)
-
是否需要 多路复用、流控制、压缩、RPC?→ HTTP/2 + gRPC 或 HTTP/3(QUIC)
3) 不同协议详解:场景 / 优缺点 / Java 支持
下面先给一张简表(便于比对),再逐个详述与示例。
| 协议/范式 | 层级 | 典型场景 | 优点 | 缺点 | Java 支持 / 备注 |
|---|---|---|---|---|---|
| TCP | 传输层 | Web(HTTP/HTTPS)、文件、数据库、RPC | 可靠、顺序、拥塞控制 | 建立连接开销、延迟、头部开销 | Socket / ServerSocket / NIO |
| UDP | 传输层 | 游戏状态、VoIP、DNS、视频流 | 低延迟、无连接、开销小 | 不可靠、可能乱序/丢包 | DatagramSocket / DatagramChannel |
| SCTP | 传输层 | 电信信令(multi-stream) | 多流、消息边界、可靠 | 支持较少、部署复杂 | Java 有 com.sun.nio.sctp(非所有 JDK) |
| QUIC/HTTP3 | 传输层+应用层(基于 UDP) | Web 性能优化、低延迟HTTP | 连接迁移、0-RTT、减少握手 | 新兴,生态/平台依赖 | Java 标准库有限,第三方实现 |
| HTTP/HTTPS | 应用层(基于 TCP) | 浏览器、REST API、微服务 | 广泛支持、可缓存、TLS安全 | 文本/头部开销、长连接需管理 | HttpClient、Servlet、Spring |
| WebSocket | 应用层(基于 HTTP 升级 → TCP) | 实时双向消息(聊天、游戏) | 双向、低延迟、保持连接 | 需要心跳、连接管理 | javax.websocket / java.net.http.WebSocket |
| gRPC(HTTP2 + Protobuf) | 应用层 | 微服务 RPC、高效二进制 | IDL、流、双向、性能好 | 学习成本、需要 proto | gRPC-Java 库 |
| MQTT | 应用层(基于 TCP) | IoT 设备(弱网) | 轻量、QoS、会话持久 | 中间件依赖(Broker) | Eclipse Paho、HiveMQ |
| CoAP | 应用层(基于 UDP) | 物联网 | 轻量、适合受限设备 | UDP 的可靠性挑战 | 有 Java 实现 |
详解:TCP(可靠流)
场景:几乎所有需要可靠传输的场合:HTTP/HTTPS、文件传输、数据库连接、RPC 等。
优点:
-
可靠:丢包重传、校验、确认 ACK。
-
保序:发送顺序与接收顺序一致。
-
拥塞控制/流量控制。
缺点 / 注意:
-
连接建立(三次握手)与断开(四次挥手)有开销。
-
TCP 是字节流,没有消息边界(需要应用层自己做消息分帧)。
-
注意:必须设计协议,例如“长度前缀”或特殊分隔符(
\n)来分割消息。
-
-
延迟在高丢包环境下会增加(重传)。
-
Nagle 算法会合并小包(
TCP_NODELAY可禁用)。
Java 示例(简洁 TCP 服务/客户端):服务端:
// ServerSocket 说明:ServerSocket(int port, int backlog, InetAddress bindAddr)
// - port: 监听端口
// - backlog: 未处理连接队列长度(系统可能忽略)
try (ServerSocket server = new ServerSocket(8080)) {
while (true) {
Socket client = server.accept(); // 阻塞,返回客户端 Socket
// 推荐交由线程池处理,避免 per-connection 线程耗尽资源
handleClient(client);
}
}
客户端:
// Socket(String host, int port)
// 新建 socket 会尝试建立连接(阻塞),可用 Socket#connect(SocketAddress endpoint, int timeout) 指定超时
try (Socket s = new Socket()) {
s.connect(new InetSocketAddress("server.example.com", 8080), 3000); // 3s 超时
OutputStream out = s.getOutputStream();
InputStream in = s.getInputStream();
// 注意:TCP 是流式,读/写需做好边界处理
}
实战建议:
-
对每个 socket 设置 read / write 超时:
socket.setSoTimeout(ms)(读阻塞超时)。 -
使用线程池(或 NIO)处理大量并发连接,防止线程数爆炸。
-
协议设计采用长度前缀或帧协议(避免粘包/拆包)。
-
关闭 socket 时确保
close()在 finally 中执行。
详解:UDP(无连接、报文)
场景:实时性优先、允许丢包的场合:流媒体、实时游戏位置更新、DNS。
优点:
-
发送/接收开销小、无连接。
-
可以并行发送到多个目标(广播/组播)。
缺点 / 注意:
-
不保证交付、不保证顺序、不保证不重复。
-
报文大小受 MTU 限制(通常不要超过 1500 字节),过大可能被分片或丢弃。
-
无内置拥塞控制(需要应用层处理)。
Java 示例(UDP 发送/接收):
发送:
DatagramSocket socket = new DatagramSocket(); // 绑定到任意可用端口
byte[] data = "hello".getBytes(StandardCharsets.UTF_8);
DatagramPacket packet = new DatagramPacket(data, data.length, InetAddress.getByName("server.example.com"), 9999);
socket.send(packet);
socket.close();
接收:
DatagramSocket socket = new DatagramSocket(9999); // 绑定端口
byte[] buf = new byte[2048];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
socket.receive(packet); // 阻塞直到收到报文或超时
int len = packet.getLength();
String msg = new String(packet.getData(), 0, len, StandardCharsets.UTF_8);
socket.close();
实战建议:
-
选择合理的报文大小(避免 IP 分片)。
-
使用心跳/重传策略(如果需要更可靠)。
-
如果要求可靠性多于 UDP 提供,考虑在 UDP 之上实现 ack 重传或直接使用 TCP/QUIC。
其它协议/技术(简要)
-
WebSocket:浏览器和服务器间双向实时通信,适合聊天/实时推送。注意心跳、断线重连策略。
-
HTTP/2 + gRPC:适合微服务 RPC(多路复用、流优先级、压缩),但需要学习 proto 与处理流式 RPC。
-
MQTT:物联网设备通讯,低带宽、QoS、会话保持、Broker 中心化。
-
QUIC (HTTP/3):基于 UDP 的新型传输,减少握手延迟、连接可迁移(移动场景优势)。在 Java 中生态在快速发展,标准库支持有限(需第三方实现)。
-
SCTP:提供多流、消息边界、适合电信/信令,但部署/支持度低。
4) Java 中常见网络编程模型(实现风格/架构选择)
当你实现 server 时,常见模型有:
-
传统阻塞 IO(BIO):
ServerSocket.accept()阻塞、每个连接用一个线程处理。-
简单易实现,但在高并发下线程资源消耗大。
-
适合低并发或连接数受限的场景。
-
-
非阻塞 IO(NIO,Selector):单线程/少量线程使用
Selector管理多个SocketChannel。-
优点:支持大量并发连接(少线程)。
-
缺点:编程复杂(状态管理、缓冲区、读写半包问题)。
-
Java API:
ServerSocketChannel、SocketChannel、Selector。
-
-
异步 IO(AIO / NIO.2):
AsynchronousSocketChannel,回调/Future 驱动。-
编码更复杂,但能实现高性能异步处理。
-
-
事件驱动 + 线程池(Reactor/Proactor):如 Netty、Vert.x、Akka 等框架实现了成熟模型(推荐在真实业务使用这些库)。
建议:
-
不要手工写低级 NIO 框架,优先使用成熟库(Netty)来处理高并发场景,它封装了协议解析、拆包/粘包、连接管理、线程模型等复杂逻辑。
5) 常见实战注意事项(摘要)
-
边界/粘包处理(TCP):采用长度前缀、定界符或更复杂的帧协议(例如 protobuf 的 varint-length framing)。
-
超时设置:连接与读写超时(
connect(timeout)、socket.setSoTimeout())。 -
资源关闭:
try-with-resources或 finally 中 close socket/streams。 -
线程模型:避免每连接一线程;使用线程池或 NIO/Netty。
-
缓冲区大小:
socket.setReceiveBufferSize()/setSendBufferSize()可影响吞吐。 -
TCP 选项:
TCP_NODELAY(禁用 Nagle,降低延迟但增包数量)、SO_KEEPALIVE(保活)。 -
安全:使用 TLS(
SSLSocket、SSLContext或 Java HttpClient/TLS)保护数据;证书管理、信任链、CRL/OCSP 注意。 -
网络环境:考虑 NAT、负载均衡、代理、分布式追踪、连接迁移(移动场景)。
-
序列化/编码:JSON 方便但大;protobuf 更高效;选择要考虑兼容性与性能。
-
日志与监控:连接数、超时、吞吐、RTT、错误要有监控报警。
6) 几个实用代码片段(完整、注释详尽)
TCP(简易 echo server + client)—— 把输入回写给客户端(注意:生产环境应用线程池)
服务端(阻塞示例):
// TCP Echo Server (blocking). 小型测试示例。
// 参数说明:ServerSocket(port, backlog, bindAddr)
// - port: 监听端口
// - backlog: 连接队列长度(系统可能忽略)
// 推荐在真实系统中使用 thread pool 替换每连接新线程
import java.io.*;
import java.net.*;
public class TcpEchoServer {
public static void main(String[] args) throws IOException {
int port = 9090;
try (ServerSocket server = new ServerSocket(port)) {
System.out.println("Echo server listening on port " + port);
while (true) {
Socket client = server.accept(); // 阻塞直到有连接
// 每个连接启用线程处理(示例用法;生产中用线程池)
new Thread(() -> handle(client)).start();
}
}
}
private static void handle(Socket client) {
try (Socket s = client;
BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream(), "UTF-8"));
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(s.getOutputStream(), "UTF-8"))) {
String line;
while ((line = in.readLine()) != null) { // 以 \n 换行作为边界
System.out.println("recv: " + line);
out.write("ECHO: " + line + "\n");
out.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端:
import java.io.*;
import java.net.*;
public class TcpEchoClient {
public static void main(String[] args) throws IOException {
try (Socket socket = new Socket("localhost", 9090);
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"));
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"))) {
out.write("hello server\n");
out.flush();
String resp = in.readLine();
System.out.println("server responded: " + resp);
}
}
}
UDP(报文)示例:发送/接收
发送:
import java.net.*;
import java.nio.charset.StandardCharsets;
public class UdpSender {
public static void main(String[] args) throws Exception {
try (DatagramSocket ds = new DatagramSocket()) {
byte[] data = "ping".getBytes(StandardCharsets.UTF_8);
DatagramPacket p = new DatagramPacket(data, data.length, InetAddress.getByName("localhost"), 9999);
ds.send(p);
System.out.println("sent ping");
}
}
}
接收:
import java.net.*;
import java.nio.charset.StandardCharsets;
public class UdpReceiver {
public static void main(String[] args) throws Exception {
try (DatagramSocket ds = new DatagramSocket(9999)) {
byte[] buf = new byte[1024];
DatagramPacket p = new DatagramPacket(buf, buf.length);
ds.receive(p); // 阻塞
String s = new String(p.getData(), 0, p.getLength(), StandardCharsets.UTF_8);
System.out.println("received: " + s + " from " + p.getAddress() + ":" + p.getPort());
}
}
}
NIO 简短示例(Selector 模型:单线程监听多个连接)
仅示意,生产建议使用 Netty:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
public class NioEchoServer {
public static void main(String[] args) throws IOException {
try (Selector selector = Selector.open();
ServerSocketChannel ssc = ServerSocketChannel.open()) {
ssc.bind(new InetSocketAddress(7070));
ssc.configureBlocking(false);
ssc.register(selector, SelectionKey.OP_ACCEPT);
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (true) {
selector.select(); // 阻塞直到有事件
for (SelectionKey key : selector.selectedKeys()) {
selector.selectedKeys().remove(key);
if (key.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel client = server.accept();
client.configureBlocking(false);
client.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
SocketChannel client = (SocketChannel) key.channel();
buffer.clear();
int read = client.read(buffer);
if (read == -1) {
client.close();
} else {
buffer.flip();
client.write(buffer); // 回显
}
}
}
}
}
}
}
7) 小结 -> 如何选择
-
要可靠、业务语义复杂、需要事务性 → 选 TCP + 上层协议(HTTP/gRPC)。
-
要低延迟、允许丢包(语音/游戏) → 选 UDP(或基于 UDP 的 QUIC)。
-
浏览器端双向 → 选 WebSocket(或 SSE/HTTP2 流)。
-
物联网弱网、轻量传输 → 选 MQTT/CoAP。
-
高并发长连接 → 使用 NIO / Netty + 线程池 + 连接管理(心跳、限速、断线重连)。
92



被折叠的 条评论
为什么被折叠?



