Go 网络编程与 IO 模型

Go 网络编程 Java 程序员写网络服务,技术栈大概是这样的: // Java —— Netty 写一个 HTTP 服务 EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) { ch.pipeline() .addLast(new HttpServerCodec()) .addLast(new HttpObjectAggregator(65536)) .addLast(new SimpleChannelInboundHandler<FullHttpRequest>() { @Override protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest req) { // 业务逻辑... } }); } }); b.bind(8080).sync().channel().closeFuture().sync(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } 配置 EventLoopGroup、Channel Pipeline、Codec、Handler……对于一个简单的 HTTP 服务,一半代码在处理 Netty 的样板。 ...

二月 6, 2023 · 5 分钟 · 1011 字 · yaomingye

一次性讲明白 Filter、Interceptor、RequestAdvice、ResponseAdvice

一次性讲明白 Filter、Interceptor、RequestAdvice、ResponseAdvice:执行顺序与实际应用 一、从一个常见需求说起 开发一个 Web 接口,通常需要处理以下事情: 记录每个请求的耗时日志 校验登录态,未登录拒绝访问 对请求参数做预处理(比如解密、格式转换) 对响应结果做统一封装(比如统一返回 {code, msg, data} 格式) 这四个需求对应的正是四个组件: 需求 对应组件 执行位置 记录请求日志 Filter Servlet 容器层(最外层) 登录校验 Interceptor Spring MVC 层(Controller 前后) 请求参数预处理 RequestAdvice Controller 方法执行前 响应统一封装 ResponseAdvice Controller 方法执行后 这四个组件在一条请求链路中各自负责不同的阶段。先看一张总览图,建立位置感: flowchart TD %% 半暗底色 + 高亮描边:完美适配博客深色/浅色双主题 %% classDef process fill:#1e1e24,stroke:#6b7280,stroke-width:2px,color:#e5e7eb; classDef data fill:#052e16,stroke:#16a34a,stroke-width:2px,color:#bbf7d0,font-weight:bold; classDef startEnd fill:#701a4c,stroke:#e11d48,stroke-width:2.5px,color:#fce7f3,font-weight:bold; A[客户端请求] --> B[Filter] B --> C[DispatcherServlet] C --> D[Interceptor.preHandle] D --> E[RequestAdvice] E --> F[Controller] F --> G[ResponseAdvice] G --> H[Interceptor.postHandle] H --> I[Interceptor.afterCompletion] I --> J[Filter 返回] J --> K[客户端响应] class E,G data; class A,B,C,D,F,H,I,K process; class J startEnd; 这张图只需要记住一个核心原则: Filter 在最外层,Interceptor 在中间层,Advice 在最内层(紧贴 Controller)。 请求进来从外到内,响应出去从内到外。 ...

十月 9, 2022 · 7 分钟 · 1426 字 · yaomingye

CoAP 受限应用协议

📡 CoAP 受限应用协议:报文格式、通信模型与物联网实战全解析 从一个智能灯控场景说起 假设你正在开发一套智能路灯系统。每个路灯上有一颗低功耗 MCU(微控制器),通过 NB-IoT(窄带物联网)蜂窝网络上报状态、接收开关指令。MCU 的 RAM 只有 64KB,Flash 只有 256KB,网络带宽不到 100kbps,每月流量限额 30MB。 你能在这颗 MCU 上跑 HTTP 吗? 不能。 原因有三: HTTP 基于 TCP,TCP 三次握手 + TLS 握手需要至少 5 ~ 7 个往返(RTT),在 100kbps 窄带网络上耗时数秒 HTTP 头部是纯文本,一个 GET /status HTTP/1.1\r\nHost: ... 请求头轻松超过 200 字节,而传感器上报的有效数据可能只有 4 字节(一个温度值) TCP 连接要保持状态,MCU 内存不足以维护大量连接 CoAP (Constrained Application Protocol,受限应用协议)就是为这种场景设计的。它用 UDP 替代 TCP、用 4 字节定长二进制头部替代 HTTP 的文本头、用简单的重传机制替代 TCP 的复杂拥塞控制,让一颗 64KB RAM 的 MCU 也能参与到互联网架构中。 下面先用一段概念性代码感受 CoAP 的编程模型: ...

十月 5, 2022 · 11 分钟 · 2332 字 · yaomingye

Servlet 网络编程

🌐 Servlet 网络编程:从 HTTP 协议到 RESTful API、过滤器链、监听器与 Tomcat 部署全解析 1. 问题切入:不用 Spring,如何写一个 HTTP API? 假设你要开发一个用户管理的 RESTful API,要求: 支持 JSON 格式的增删改查 对每个请求打印访问日志 校验请求头中的认证 Token 处理跨域请求 如果使用 Spring Boot,一个 @RestController 就解决了。但 Spring MVC 的底层是什么?DispatcherServlet、FilterChain、HandlerInterceptor 这些概念是怎么来的? 这篇博客将用纯 Servlet 实现上述所有需求,让你理解 Spring MVC 底层的每一块砖。 在开始之前,先看最终效果 —— 一个纯 Servlet 实现的用户 API: // GET /api/users → 查询所有用户(JSON) // GET /api/users/1 → 查询单个用户(JSON) // POST /api/users → 创建用户(JSON请求体) // PUT /api/users/1 → 更新用户(JSON请求体) // DELETE /api/users/1 → 删除用户 2. Servlet 是什么 Servlet(Server Applet,服务端小程序)是 Java EE 规范中定义的一套 服务器端 HTTP 处理接口。它不是独立运行的程序,而是运行在 Servlet 容器(如 Tomcat)中,由容器管理其生命周期,并调用其方法来处理 HTTP 请求。 ...

九月 25, 2022 · 12 分钟 · 2428 字 · yaomingye

NIO 性能调优

NIO 性能调优:零拷贝、直接内存与线上问题排查全解析 1 ⚡ 问题切入:文件服务器 CPU 100%,网络带宽却没用满 一个典型的文件下载服务,使用传统 Java I/O 实现: // 传统文件传输:将磁盘文件发送给客户端 public static void sendFile(Socket socket, String filepath) throws IOException { FileInputStream fis = new FileInputStream(filepath); BufferedInputStream bis = new BufferedInputStream(fis); OutputStream os = socket.getOutputStream(); byte[] buf = new byte[8192]; int len; while ((len = bis.read(buf)) != -1) { os.write(buf, 0, len); // 每次循环:内核→用户→内核→网卡 } bis.close(); fis.close(); } 这段代码能工作,但投入生产后出现异常现象:4 核 CPU 全部 100%,但千兆网卡只用了 600Mbps。理论上这台机器完全可以跑满千兆,为什么 CPU 先成了瓶颈? ...

九月 16, 2022 · 9 分钟 · 1755 字 · yaomingye

Netty

Netty:Reactor 线程模型、Pipeline 责任链与四个必写示例全解析 1 ⚡ 问题切入:原生 NIO 能工作,但你敢上生产吗? 在上一篇 Java NIO 博客中,我们手写了一个 NIO EchoServer——单线程管理多个连接,Selector 封装 epoll。这段代码在演示环境中运行良好,但如果直接部署到生产环境,会遇到四个棘手问题: // 原生 NIO EchoServer 的核心循环(看似正确,实则隐患重重) while (true) { selector.select(); for (SelectionKey key : selector.selectedKeys()) { if (key.isAcceptable()) { SocketChannel client = ssc.accept(); client.configureBlocking(false); client.register(selector, SelectionKey.OP_READ); } else if (key.isReadable()) { SocketChannel client = (SocketChannel) key.channel(); ByteBuffer buf = ByteBuffer.allocate(1024); int len = client.read(buf); // 问题1: 读到半包怎么办? buf.flip(); // 问题2: 忘了 flip 直接炸 client.write(buf); // 问题3: 写不出去谁管? buf.clear(); // 问题4: clear 还是 compact? } keyIterator.remove(); } } 原生 NIO 的四大生产痛点: ...

九月 15, 2022 · 13 分钟 · 2657 字 · yaomingye

Java NIO

Java NIO:epoll 多路复用、Buffer 机制与单线程高并发全解析 1 ⚡ 问题切入:一个线程如何管理 10000 个连接? 在经典的 BIO (Blocking I/O,阻塞 I/O)模型下,每个 Socket 连接需要分配一个独立线程。当 accept() 返回一个新连接,就启动一个线程去 read() —— 这个线程在数据到达之前会一直阻塞,CPU 时间被白白浪费在线程上下文切换上。 // 传统 BIO:一个连接一个线程(不可行) ServerSocket server = new ServerSocket(8080); while (true) { Socket client = server.accept(); // 阻塞等待连接 new Thread(() -> { InputStream in = client.getInputStream(); byte[] buf = new byte[1024]; in.read(buf); // 阻塞等待数据 // 处理数据... }).start(); } 问题:如果有 10,000 个连接,就需要 10,000 个线程。每个 Java 线程默认栈大小约 1MB,仅线程栈就消耗 10GB 内存,而且 CPU 绝大多数时间都在做线程切换而非真正处理数据。这就是著名的 C10K 问题 (Client 10,000 Problem)。 ...

九月 14, 2022 · 17 分钟 · 3496 字 · yaomingye

Linux IO 模型

Linux IO 模型:阻塞、非阻塞、多路复用与异步 IO 全解析 1 ⚡ 问题切入:一个后端开发者必须回答的问题 假设你在面试中被问到: “一台 4 核 8GB 的服务器,为什么能支撑 10 万个并发连接?” 答案的关键不在于 CPU 有多快、内存有多大,而在于 IO 模型 。如果每个连接用一个线程、每个线程做阻塞 IO,10 万连接就需要 10 万个线程——每个线程消耗约 1MB 栈空间,仅线程栈就占 100GB 内存,4 核 CPU 也根本无法调度这么多线程。 真正让高并发成为可能的,是 非阻塞 IO 和 IO 多路复用 (I/O Multiplexing,单个线程同时监听多个 IO 事件)。Nginx、Redis、Netty 的高性能都建立在正确的 IO 模型选择之上。 这篇博客从操作系统层面讲解 Linux 五大 IO 模型,聚焦于"数据如何从网卡/磁盘到达你的程序",为后续理解 Java NIO、Netty、Kafka 等框架打下理论基础。 2 💻 硬件架构:一次 IO 操作经历了什么 在讨论 IO 模型之前,必须先理解一次 IO 操作涉及哪些硬件组件以及数据如何流转。 如上图所示,一次典型的网络 IO 读取,数据经过以下路径: ...

九月 13, 2022 · 9 分钟 · 1736 字 · yaomingye

RandomAccessFile 文件指针与任意位置读写

RandomAccessFile 文件指针与任意位置读写:多线程分段下载实现 1 ⚡ 问题切入:流式读写的局限 前面介绍的 FileInputStream / FileOutputStream 都是 顺序流 (Sequential Stream),只能从文件头开始,一个字节接一个字节地读写。如果只想读文件的第 1000 个字节,顺序流必须先把前面 999 个字节读完(或调用 skip()),效率很低。 如果要实现以下场景,顺序流就完全不够用了: 修改一个 1GB 文件的中间某几个字节 在文件末尾追加日志,同时读取文件头部的元数据 多线程分段下载文件,每个线程负责不同区间的数据块 这些场景需要 随机读写 ——可以自由移动文件指针到任意位置,像操作数组下标一样操作文件。Java 提供了 RandomAccessFile 来实现这个能力。 // 随机读:直接跳到第 1000 字节开始读 RandomAccessFile raf = new RandomAccessFile("data.bin", "r"); raf.seek(1000); // 指针移到 position=1000 int b = raf.read(); // 读第 1000 个字节 System.out.printf("第1000字节: 0x%02X%n", b); raf.close(); 跳过前面 999 个字节,直达目标位置——这就是 RandomAccessFile 的价值。 2 🎯 核心概念:文件指针 2.1 ❓ 什么是文件指针 文件指针 (File Pointer)是 RandomAccessFile 内部维护的一个 long 类型偏移量(Offset),表示 下一次读写操作将发生的字节位置 。它类似于数组的下标索引,但作用对象是文件。 ...

九月 12, 2022 · 8 分钟 · 1611 字 · yaomingye

Java IO 进阶

Java IO 进阶:基本类型 IO、打印流与对象序列化实用指南 1 🎮 问题场景:从保存游戏分数说起 假设你正在开发一个本地小游戏,需要把玩家的最高分(int)、胜率(double)和昵称(String)保存到文件,下次启动时读回来。用前面学过的 FileWriter 写文本当然可以,但你需要手动处理类型转换——写的时候 int → String,读的时候 String → int,格式稍微不一致就解析失败。 有没有办法直接把 int 按固定 4 字节的二进制格式写入,读的时候也按 int 原样读出?这就是 基本类型 IO 要解决的问题。 更进一步,如果整个游戏状态是一个复杂的 Java 对象(玩家信息、关卡进度、道具列表),能不能 一键保存整个对象,一键读回 ?这就是 对象序列化 要解决的问题。 本篇覆盖 Java IO 的第四个进阶阶段,依次讲解三种机制: 机制 用途 一句话描述 DataInputStream / DataOutputStream 读写基本类型和字符串 按固定字节数的二进制格式读写,必须按序操作 PrintStream / PrintWriter 格式化文本输出 不抛异常,日常 System.out.println() 就在用它 ObjectInputStream / ObjectOutputStream 对象序列化与反序列化 一键将整个对象转为字节流保存或传输 2 💾 基本类型 IO:DataOutputStream / DataInputStream 2.1 ❓ 是什么 DataOutputStream 和 DataInputStream 是 装饰器流 (包装已有的 OutputStream / InputStream),提供直接读写 Java 基本类型和 String 的能力。数据以 平台无关的二进制格式 写入——比如 writeInt(42) 始终写 4 个字节,无论在什么操作系统上,读出来的值都不变。 ...

九月 9, 2022 · 5 分钟 · 937 字 · yaomingye
Cat Radio