阶段:进阶篇 | 建议时长:3 天 | 前置:01-环境搭建与基础语法.md
学习目标
- 能对比 TCP 与 UDP 的特性与适用场景,讲清三次握手/四次挥手及「为什么三次」。
- 会写 Socket TCP 客户端/服务端小程序,会用 Java 11 HttpClient 发请求。
- 能画/说 BIO、NIO、AIO 的差异,解释 NIO 三大件与多路复用(select/poll/epoll)。
- 能解释零拷贝的两个核心系统调用及其收益。
- 能说出 Netty 的定位与高性能的四个原因。
核心知识点
TCP vs UDP
面试官想听:连接性、可靠性、有序性、速度四个维度对比 + 一个各自的典型应用。
| 维度 | TCP | UDP |
|---|---|---|
| 连接 | 面向连接(握手挥手) | 无连接,直接发 |
| 可靠性 | 可靠:确认重传、滑动窗口、流量/拥塞控制 | 尽力而为,可能丢包乱序 |
| 数据形态 | 字节流(无消息边界) | 数据报(有边界) |
| 速度/开销 | 头部 20 字节起,握手有延迟 | 头部 8 字节,低延迟 |
| 场景 | HTTP、文件传输、数据库连接 | DNS、视频直播、游戏、心跳广播 |
- TCP 可靠传输三板斧:确认应答(ACK)+ 超时重传保证不丢;序号 + 接收缓冲重排保证有序;滑动窗口限速(流量控制)+ 拥塞控制(慢启动、拥塞避免)保护网络。
三次握手 / 四次挥手
面试官想听:能画出双向确认的过程,并回答「为什么握手是三次、挥手是四次」两个为什么。
- 三次握手(建立连接):客户端发 SYN → 服务端回 SYN+ACK → 客户端再回 ACK,连接建立。
- 为什么三次:确认双方的收发能力都正常;两次无法让服务端确认「客户端能收到我的包」,同时防止历史(过期)SYN 重复建立连接。
- 四次挥手(断开连接):主动方 FIN → 对方 ACK → 对方(数据发完后)FIN → 主动方 ACK,进入 TIME_WAIT 等 2MSL 后关闭。
- 为什么四次:TCP 全双工,两个方向的关闭各自独立,ACK 和 FIN(被动方收到 FIN 后可能还有数据要发)不能合并,故 ACK、FIN 分开发送。
- TIME_WAIT 的意义:确保最后的 ACK 若丢失可重传、让旧报文在网络中消亡;大量 TIME_WAIT 常见于短连接服务,可调
tcp_tw_reuse优化。
HTTP 基础
- 请求报文:请求行(方法 URL 版本)+ 请求头 + 空行 + 请求体;响应报文:状态行 + 响应头 + 响应体。
- 常见状态码:200 成功、301/302 重定向、400 客户端请求错误、401 未认证、403 禁止、404 未找到、500 服务器内部错误、502 网关错误、504 网关超时。
- GET vs POST:语义上 GET 获取(幂等、参数在 URL、可缓存可收藏);POST 提交(不幂等、参数在 body);本质都是 TCP 上的报文,区别是约定而非硬性限制。
HTTPS 握手概念
- TLS 在 TCP 与 HTTP 之间:① 客户端请求并携带支持的加密套件;② 服务端返回证书;③ 客户端用内置 CA 验证证书,生成随机预主密钥并用服务端公钥加密发送;④ 双方据其算出对称密钥,之后用对称加密通信。
- 混合加密的原因:非对称加密安全但慢,只用来交换密钥;数据传输用快的对称加密。这一过程同时解决机密性与身份认证(证书)。
抓包工具为什么能解密 HTTPS?(Charles / Fiddler / mitmproxy 的原理):TLS 本可防中间人,但抓包工具做的是**「被信任的中间人」**——它自签一套根证书,诱导你把这张根证书装进系统/浏览器的信任库,之后:
- 客户端与抓包工具建立第一条 TLS 连接——握手时工具返回它自己签发的证书(而非真实网站证书);因为其根证书已被你信任,校验通过;
- 抓包工具再与真实服务器建立第二条 TLS 连接,用真实证书正常握手;
- 两条连接各自加解密,工具在中间明文转发——于是能看到全部请求与响应。
- 关键前提:根证书被目标设备信任。所以公司做流量审计,靠的正是 MDM 或手动安装企业根证书;反过来,手机/电脑上出现不明根证书,就是要警惕的风险信号(这也是为什么装了抓包证书后,部分金融 App 会拒绝运行——它们做了证书固定(certificate pinning),只认自己的证书链)。
- 防中间人的真正依据:浏览器验的是证书链能否追到系统信任的根 CA,以及域名是否匹配——攻击者拿不到真实域名的合法证书,伪造的又追不到受信根,就会弹「证书不受信任」警告。
Cookie / Session / Token
面试官想听:三者「存在哪、谁认谁」——Cookie 在浏览器、Session 在服务端、Token 在请求头里自带身份。
| Cookie | Session | Token(JWT) | |
|---|---|---|---|
| 存放位置 | 浏览器 | 服务端内存/存储 | 客户端(LocalStorage/Cookie/Header 均可) |
| 生命周期 | 会话级(关浏览器没)或设 Max-Age 持久 | 超时销毁(默认 30 分钟不访问失效) | payload 里 exp 自带过期时间 |
| 状态 | 只是载体 | 有状态:服务端要存 | 无状态:服务端只验签不存 |
- 传统登录流程:登录后服务端建 Session,把
JSESSIONID写进 Cookie 返回;之后浏览器自动带上 Cookie,服务端凭它找到 Session——所以 Session 依赖 Cookie 传递凭证(禁 Cookie 就得 URL 重写)。 - Token 流程:登录成功后服务端用密钥签发 JWT 返回,客户端存起来,之后每次请求放
Authorization: Bearer xxx头;服务端只验签、不查存储,天然方便水平扩容。 - 分布式下 Session 共享三种方案:粘滞会话(负载均衡按 IP 固定路由到同一台,简单但单点故障丢会话)、Session 复制(节点间互同步,节点多时流量爆炸)、集中存储(Session 统一放 Redis,主流做法——本质就是「把有状态外置成共享存储」)。
- 记忆口径:Cookie 是身份证的「挂绳」,Session 是柜台后面的档案,Token 是把档案信息签名后自己揣兜里。
JWT 的结构与校验
- 结构三段,用
.连接:header.payload.signature,前两段是 Base64URL 编码(不是加密!),任何人可解码看到内容——别把敏感信息放 payload。- header:
{"alg":"HS256","typ":"JWT"};payload:声明(exp过期、sub用户、自定义字段);signature:HMAC-SHA256(base64(header) + "." + base64(payload), 密钥)。
- header:
- 校验流程:服务端拆开三段,用同一个密钥重算签名比对,再看
exp是否过期。改一个字符签名就对不上,这就是防篡改原理。 - HS256(对称,一个密钥签发+校验,单体够用)vs RS256(非对称,私钥签发、公钥校验,适合多方验签/微服务,签发权不扩散)。
- 无状态的代价:签发出去就收不回,登出/踢人要么靠短过期 + Refresh Token 续期(accessToken 几分钟、refreshToken 几天,过期前用后者换新),要么引入 Redis 黑名单/版本号(又变回有状态)。
- 常见坑:
alg=none攻击(老库允许不验签,务必校验 alg 白名单)、密钥泄露、忽略 exp、把 JWT 当加密用。
操作系统基础(面试常顺手问)
面试官想听:这几个点是 IO/网络题的「顺手追问」,每题 1~3 行说清概念 + 一个口径。
- 进程 vs 线程:进程是资源分配的单位(独立虚拟地址空间),线程是CPU 调度的单位;一个 JVM(进程)里线程共享堆和方法区,各自有独立的栈、程序计数器、本地方法栈。切换成本:线程 < 进程(不用换页表/地址空间)。口径:进程是工厂,线程是工人。
- 进程间通信 IPC:管道(半双工、亲缘)、消息队列(解耦、有大小限制)、共享内存(最快——映射同一块物理页,不再经过内核拷贝,但要自己配信号量做同步)、信号量(计数、同步互斥)、Socket(可跨机,网络编程的基础)。
- 死锁四个必要条件:互斥、请求与保持(持有并等待)、不可剥夺、循环等待,四个同时成立才死锁;预防=破坏其中任意一个——一次性申请全部资源(破坏请求保持)、允许强占(破坏不可剥夺)、按全局顺序加锁(破坏循环等待)、
tryLock超时放弃。Java 层死锁详见并发篇。 - 虚拟内存与缺页中断:每个进程看到的是自己独占的连续虚拟地址空间,经页表映射到物理页;内存不够时把冷页换出到磁盘,访问不在内存的页触发缺页中断(Page Fault),内核调入后再继续。收益:内存超售(跑起来的比物理内存多)、隔离保护、按需加载。代价:一次缺页中断可能上百微秒到毫秒级(SSD)。
- 用户态 vs 内核态:CPU 特权级两种模式;应用代码在用户态,读网络、读磁盘、分配内存都要系统调用(或中断)陷入内核态执行,返回再切回——每次切换有保存/恢复寄存器和刷流水线的开销。这就是「为什么频繁小 IO 慢」的底层原因,也是零拷贝想省的东西。
- IO 多路复用(进程/线程视角):一个线程通过
select/poll/epoll一次看护成百上千个 fd,「谁就绪我处理谁」,从而用 1 个线程服务 N 个连接,替代「每连接阻塞等在内核里的一线一连接」——Java Selector 与 Netty/Nginx/Redis 的底层正是 epoll(详见下文 NIO 小节)。 - 零拷贝:
mmap/sendfile让数据不来回穿越用户态内核态(详见下文零拷贝小节);应用代表:Kafka(消息体透传不加工,天生适合)、Nginxsendfile on静态文件服务、JavaFileChannel.transferTo。 - fork 与写时复制(COW):fork 创建子进程时不复制物理内存,父子共享同一批物理页且只读,任一方写入才真正复制那一页(缺页中断触发)——所以 fork 大 JVM 进程很快,但随后写多了内存翻倍。延伸:Linux 线程是
clone()带CLONE_VM标志的轻量进程(共享地址空间),这也是「线程共享堆」的内核实现。 - 僵尸进程与孤儿进程:子进程退出后内核要保留它的退出状态供父进程
wait()读取,此时子进程已是「尸体」但仍在进程表占位——这就是僵尸进程(ZOMBIE,内存几乎不占但占 PID 表项,大量堆积会耗尽 PID)。父进程调用wait/waitpid(或忽略 SIGCHLD 自动回收)即消除。反过来,父进程先退出、子进程被 init/systemd 收养,则是孤儿进程,无害。排查:ps aux | grep Z,ps的 STAT 列出现Z;成因多为父进程没处理 SIGCHLD。 - 多线程加速比与 Amdahl 定律:4 核跑 4 线程理论上 100 秒 → 25 秒,实测 28 秒,多出的 3 秒来自不可并行的部分 + 并行开销(线程创建/销毁、锁竞争与同步、伪共享导致的缓存失效、任务划分与结果合并、冷启动与调度延迟)。Amdahl 定律给出上界:加速比 = 1 / (串行占比 + 并行占比/核数),串行占比 5% 时哪怕核数无穷大,加速比也封顶在 20 倍。这解释了「加核不等于线性变快」。
- CPU 占用可以超过 100%:
top里单核显示 100%,一个 JVM 进程是多线程的,其占用是所有线程在多个核上之和——380% 即约 3.8 个核满负荷,通常是有多线程并行计算(GC 线程、业务线程池、并行流),也可能是死循环 + 多线程。定位:top -Hp <pid>看哪个线程高 →printf '%x' <tid>转十六进制 →jstack中按 nid 找到对应栈帧(详见《01-Java高频八股速查》的排查套路)。 - TCP 慢启动与拥塞控制:刚建连时不知道链路容量,cwnd(拥塞窗口)从小开始按指数增长——每经过一个 RTT 翻倍(1→2→4→8…),直到达到慢启动阈值
ssthresh或检测到丢包:超时则 cwnd 归 1 重新慢启动,三个重复 ACK则进入快恢复(cwnd 减半 + 拥塞避免,此后每个 RTT 只加 1,线性增长)。所以「慢启动不慢、拥塞避免才慢」是经典反直觉点。
Socket 编程 demo
// 服务端:accept 阻塞等连接,每连接一个线程处理(BIO)
try (ServerSocket server = new ServerSocket(8080)) {
while (true) {
Socket socket = server.accept(); // 阻塞点 1
new Thread(() -> {
try (BufferedReader in = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(socket.getOutputStream(), true)) {
String line;
while ((line = in.readLine()) != null) { // 阻塞点 2
out.println("echo: " + line);
}
} catch (IOException ignored) { }
}).start();
}
}
// 客户端
try (Socket s = new Socket("127.0.0.1", 8080);
PrintWriter out = new PrintWriter(s.getOutputStream(), true)) {
out.println("hello");
}
- UDP:
DatagramSocket+DatagramPacket,send/receive收发数据报,无需连接。 - Java 11 标准
HttpClient:支持 HTTP/2、同步send与异步sendAsync(返回 CompletableFuture):
// Java 11 标准 HttpClient:支持 HTTP/2、同步 send 与异步 sendAsync
HttpClient client = HttpClient.newHttpClient();
HttpRequest req = HttpRequest.newBuilder(URI.create("http://localhost:8080/api/health")).build();
HttpResponse<String> resp = client.send(req, HttpResponse.BodyHandlers.ofString()); // 同步
client.sendAsync(req, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body) // 异步
.thenAccept(System.out::println);
BIO / NIO / AIO 对比
面试官想听:用「等谁」来区分——BIO 等数据、NIO 等就绪事件、AIO 等完成通知;再补连接数与线程数的关系。
| 模型 | 全称 | 阻塞点 | 线程模型 | 适用 |
|---|---|---|---|---|
| BIO | 同步阻塞 | accept/read/write 全阻塞 | 一个连接一个线程 | 连接少且固定 |
| NIO | 同步非阻塞 | 线程轮询 Selector 等就绪事件 | 一个线程管理多个 Channel | 连接多、请求轻(高并发核心) |
| AIO | 异步非阻塞 | 发起后立即返回,完成后回调 | 系统完成 I/O 再通知 | 理论最优,Linux 实现不成熟,实际少用 |
- 记忆口径:BIO/NIO/AIO 的本质差异在「等待阶段用户线程做不做事」:BIO 死等、NIO 不阻塞但需主动轮询事件、AIO 注册回调由内核完成。
- 「同步/异步」看是谁完成真正的 I/O 拷贝(用户进程 = 同步,内核通知 = 异步);「阻塞/非阻塞」看发起调用后线程是否挂起。
NIO 三大件:Channel / Buffer / Selector
面试官想听:能讲一个完整事件循环:注册 → select 轮询 → 判断事件类型 → 读写,并注意 flip。
- Channel(通道):双向数据通道(Stream 是单向的),
SocketChannel/ServerSocketChannel/FileChannel,配合 Buffer 读写。 - Buffer(缓冲区):
ByteBuffer核心参数position/limit/capacity;写完读之前必须flip()(limit 置为 position、position 归零),clear()重写、rewind()重读。 - Selector(多路复用器):一个线程注册多个 Channel,
select()阻塞到任一 Channel 就绪,返回SelectionKey集合(OP_ACCEPT/OP_READ/OP_WRITE/OP_CONNECT),就绪才分配线程读写——把「每连接一线程」变成「每就绪事件一处理」。
Selector selector = Selector.open();
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking(false); // NIO 必须非阻塞
ssc.register(selector, SelectionKey.OP_ACCEPT);
while (selector.select() > 0) { // 轮询就绪事件
for (SelectionKey key : selector.selectedKeys()) {
if (key.isAcceptable()) ((ServerSocketChannel) key.channel()).accept();
// ... 处理 OP_READ 等,处理后 remove key
}
}
- 多路复用底层:select(fd 数组遍历,1024 上限)、poll(链表,无上限但仍 O(n) 遍历)、epoll(红黑树 + 就绪链表 + 事件回调,O(1),Linux 2.6+,Netty/Nginx 依赖它)。一句话:select/poll 是遍历所有 fd 问谁好了,epoll 是内核把好的 fd 主动报给你。
epoll 的 LT 与 ET(高频追问):
| 模式 | 触发方式 | 行为 | 风险 |
|---|---|---|---|
| LT(水平触发,默认) | 只要 fd 的读缓冲区还有数据就持续通知 | 这次没读完,下次 epoll_wait 还会再报 | 不会丢事件,写代码简单 |
| ET(边缘触发) | 只在状态发生变化时通知一次 | 通知后必须一次读到 EAGAIN(读空),否则剩余数据不再上报 | 漏读即事件永远丢失;必须配非阻塞 fd + 循环读,否则最后一次 read 会阻塞住整个线程 |
- 选型口径:LT 省心、适合通用场景;ET 通知次数少、在高并发下略省开销,但代码必须严格「非阻塞 + 循环读到 EAGAIN」。Netty 默认 LT(
EpollChannelOption可切 ET),Nginx 用 ET。 - 面试记法:LT = 「没处理完就一直催」,ET = 「只喊一次,你得自己干完」。
连接状态排查:TIME_WAIT 与 CLOSE_WAIT(ss -ant / netstat -ant 判读):
- TIME_WAIT 在主动关闭方:四次挥手最后一步,等待 2MSL(通常 60s)才真正释放。作用有二:确保最后的 ACK 能重传(对方没收到 ACK 会重发 FIN)、让本次连接的迟到报文在网络中消散,避免污染复用同一四元组的新连接。大量 TIME_WAIT 说明本机是主动关闭方且短连接多 → 解决:连接池/长连接复用、
tcp_tw_reuse(客户端侧安全)。 - CLOSE_WAIT 在被动关闭方:收到了对方的 FIN 并回了 ACK,但本进程还没调用
close()——即流量已经结束、socket 却没被应用释放。大量 CLOSE_WAIT 一定是应用代码 bug(忘了关流/连接,异常路径漏了finally,或连接池未回收),内核参数调优没用,要回去改代码。 - 一句区分:TIME_WAIT 是「正常的等待」,CLOSE_WAIT 堆积是「应用没关连接」。排查用
ss -antp看归属进程与 fd,配合lsof -p <pid> | grep CLOSE_WAIT定位泄漏点。
Reactor 模式
- 核心思想:I/O 多路复用 + 事件分发——Reactor 线程监听事件,分发给 Handler 处理。
- 单 Reactor 单线程(Redis 6 前)、单 Reactor 多线程(工作线程池处理业务)、主从 Reactor 多线程(accept 由主 Reactor,I/O 由子 Reactor 组)——Netty 默认主从模式(bossGroup + workerGroup)。
零拷贝
面试官想听:传统读发要 4 次拷贝 4 次上下文切换,mmap/sendfile 把它降到 2 次甚至 0 次 CPU 拷贝。
- 传统方式
read + write:磁盘 → 内核缓冲 → 用户缓冲(应用)→ Socket 缓冲 → 网卡,4 次拷贝、4 次用户态内核态切换。 mmap(内存映射):用户空间与内核缓冲共享映射,减少一次拷贝;sendfile:数据直接在内核内从文件缓冲流向 Socket 缓冲,用户态零参与;网卡支持 gather 时连 Socket 缓冲拷贝都省掉(DMA 直接收集),仅 2 次拷贝且全在内核。- Java 对应:
FileChannel.transferTo()即 sendfile;Kafka 高吞吐、RocketMQ、Nginxsendfile on都是受益者。适用条件:不需要在应用层修改数据,需要加工时仍要走用户态。
Netty 定位与为什么快
面试官想听:Netty = 封装好 NIO 的异步事件驱动网络框架,快在四点:Reactor 线程模型、epoll、零拷贝、池化内存。
- 定位:屏蔽 JDK NIO 复杂 API 与空轮询 bug(epoll bug),提供统一的异步事件驱动编程模型;Dubbo/gRPC/ElasticSearch 等的通信底座。
- 高性能四要点:① 主从 Reactor 多线程模型(boss 收连接、worker 处理 I/O);② 基于 epoll 多路复用;③ ByteBuf 池化(PooledByteBufAllocator,减少 GC)与 CompositeByteBuf 等零拷贝(逻辑组合而非物理拷贝);④ 无锁串行化设计——消息在同一个 Channel 上始终由同一线程处理,避免锁竞争。
- 组件速记:
Channel(连接)、EventLoop/EventLoopGroup(线程+多路复用循环)、ChannelPipeline(Handler 责任链,解码/业务/编码)、ByteBuf(增强 Buffer)。
动手实践
任务 1:BIO 多线程聊天室
- 要求:扩展上文 demo:服务端维护
CopyOnWriteArrayList<PrintWriter>,收到消息广播给所有在线客户端;客户端开两个线程(一个读控制台发送、一个收服务端消息)。 - 验收标准:开 3 个终端同时对话,消息全量可见;一个客户端断开不影响其他(异常处理 + 移除失效 writer);能说出连接数一多该模型的瓶颈(线程暴涨、内存占用)。
任务 2:缓冲流拷贝性能对比
- 要求:用 IO 章节(04-异常-IO-反射-注解.md)的任务 2 结论做基准;再用
FileChannel.transferTo()拷贝同一个 ≥ 100MB 的文件并计时。 - 验收标准:三组数据(单字节、8KB 缓冲数组、transferTo)对比;transferTo 明显更快;能解释它走的是 sendfile 零拷贝路径。
任务 3(可选):Netty echo server
- 要求:引入
io.netty:netty-all,实现 boss/worker 两 EventLoopGroup 的 echo 服务;自定义SimpleChannelInboundHandler<ByteBuf>原样返回;telnet 测试。 - 验收标准:客户端发送什么返回什么;能指出 pipeline 中 handler 的处理顺序;说出该 demo 对应 Reactor 的哪一种模型。
高频面试题
Q:说说 TCP 三次握手和四次挥手。⭐
- 握手:SYN → SYN+ACK → ACK;目的是确认双方收发能力、同步初始序列号、防止历史连接误建。
- 挥手:FIN → ACK → FIN → ACK;因 TCP 全双工,被动方 ACK 与 FIN 不能合并(可能还有数据要发)。
- 主动方最后进入 TIME_WAIT 等 2MSL:保证最后的 ACK 丢失可重传、旧报文自然消亡。
Q:为什么握手是三次而不是两次?
- 两次时服务端无法确认「客户端能收到自己的包」,连接的发送通道未获确认。
- 同时无法阻止一个在网络中滞留的旧 SYN 再次建立无效连接。
- 三次是确认双方收发能力的最小次数。
Q:TCP 如何保证可靠传输?
- 序号 + 确认应答(ACK),超时或收到重复 ACK 触发重传。
- 接收缓冲区对乱序数据重排。
- 滑动窗口做流量控制(不压垮接收方),慢启动/拥塞避免做拥塞控制(不压垮网络);校验和检查数据损坏。
Q:BIO、NIO、AIO 的区别?⭐
- BIO 同步阻塞:accept/read 全阻塞,一个连接一线程,连接多时线程暴涨。
- NIO 同步非阻塞:Selector 多路复用,一个线程监听多个 Channel 的就绪事件,就绪才读写;「同步」指数据拷贝仍由用户线程发起完成。
- AIO 异步非阻塞:发起后立即返回,内核完成 I/O 后回调通知;Linux 实现不成熟,实际生产多用 NIO。
- 一句话:BIO 等数据、NIO 等事件、AIO 等通知。
Q:select、poll、epoll 的区别?一句话概括。
- select:固定大小 fd 数组,每次全量遍历 O(n),有 1024 上限。
- poll:链表突破数量上限,但仍 O(n) 拷贝与遍历。
- epoll:红黑树管理 fd + 就绪链表回调,只返回就绪的 fd,O(1);一句话——前两者是「挨个问谁好了」,epoll 是「好了的主动排队」。
Q:什么是零拷贝?⭐
- 传统 read+write:4 次拷贝(磁盘→内核缓冲→用户缓冲→Socket 缓冲→网卡)+ 4 次上下文切换。
- sendfile 让数据全程在内核态流转,CPU 拷贝降为 0~1 次、切换降为 2 次;mmap 用映射减少一次拷贝。
- Java 里
FileChannel.transferTo即 sendfile;Kafka/RocketMQ/Nginx 借此实现高吞吐;前提是应用不需要改数据。
Q:Netty 为什么高性能?⭐
- 主从 Reactor 多线程模型:bossGroup 收连接,workerGroup 处理 I/O,业务不阻塞 I/O 线程。
- 底层基于 epoll 等 I/O 多路复用,并修复了 JDK NIO 的空轮询 bug。
- ByteBuf 池化 + 复合缓冲的零拷贝,减少内存分配与 GC。
- 单 Channel 事件无锁串行化到同一 EventLoop,避免锁竞争;pipeline 责任链灵活编解码。
Q:HTTP 常见状态码?GET 和 POST 的区别?
- 2xx 成功、301/302 重定向、400 参数错误、401 未认证、403 无权限、404 不存在、500 服务器错误、502/504 网关错误。
- GET 获取资源:幂等、参数在 URL、可缓存;POST 提交数据:参数在 body、一般不幂等。
- 面试加分:安全层面 GET 会被浏览器历史/日志记录,敏感数据应用 POST + HTTPS。
推荐资料
| 资料 | 说明 | 链接 |
|---|---|---|
| 廖雪峰 Java 网络编程 | Socket/UDP/邮件主线教程 | https://liaoxuefeng.com/books/java/network/index.html |
| pdai IO/NIO/AIO 总览 | 三种模型与 NIO 三大件体系图 | https://pdai.tech/md/java/io/java-io-overview.html |
| pdai 零拷贝 | mmap/sendfile 原理与图解 | https://pdai.tech/md/java/io/java-io-nio-zerocopy.html |
| JavaGuide IO 模型 | BIO/NIO/AIO 面试向对比 | https://javaguide.cn/java/io/io-model.html |
| JavaGuide NIO 基础 | Channel/Buffer/Selector 细节 | https://javaguide.cn/java/io/nio-basis.html |
| JavaGuide IO 基础 | IO 流体系与序列化复习 | https://javaguide.cn/java/io/io-basis.html |