• 文件浏览器
  • 000 《Folly 库知识框架》 001 《folly::Utility 权威指南》 002 《folly::Preprocessor 权威指南》 003 《Folly::Traits 权威指南:C++ 元编程的基石》 004 《Folly::ScopeGuard 权威指南:C++ 作用域资源管理利器》 005 《Folly Singleton 权威指南:从入门到精通》 007 《Folly Dynamic.h 权威指南:C++ 动态类型实战》 008 《Folly Optional.h 权威指南:从入门到精通》 009 《Folly Expected.h 权威指南》 010 《Folly Try.h 权威指南:C++ 异常处理的现代实践》 011 《Folly Variant.h 权威指南》 012 《folly::Vector 权威指南: 深度探索与实践》 013 《Folly Map 权威指南:从入门到精通》 014 《Folly Set 权威指南》 015 《Folly SmallVector 权威指南》 016 《Folly Allocator.h 权威指南:C++ 高性能内存管理深度解析》 017 《Folly Foreach.h 权威指南:从入门到精通》 018 《folly/futures 权威指南:Future 和 Promise 深度解析与实战》 019 《Folly Executor 权威指南:从入门到精通 (Folly Executor: The Definitive Guide from Beginner to Expert)》 020 《深入浅出 Folly Fibers:FiberManager 和 Fiber 权威指南》 021 《folly EventBase.h 编程权威指南》 022 《Folly Baton.h 权威指南:C++ 高效线程同步实战》 023 《深入探索 folly/Synchronized.h:并发编程的基石 (In-depth Exploration of folly/Synchronized.h: The Cornerstone of Concurrent Programming)》 024 《folly/SpinLock.h 权威指南:原理、应用与最佳实践》 025 《Folly SharedMutex.h 权威指南:原理、应用与实战》 026 《Folly AtomicHashMap.h 权威指南:从入门到精通》 027 《Folly/IO 权威指南:高效网络编程实战》 028 《folly/Uri.h 权威指南 (Folly/Uri.h: The Definitive Guide)》 029 《Folly String.h 权威指南:深度解析、实战应用与高级技巧》 030 《folly/Format.h 权威指南 (The Definitive Guide to folly/Format.h)》 031 《Folly Conv.h 权威指南:C++ 高效类型转换详解》 032 《folly/Unicode.h 权威指南:深入探索与实战应用》 033 《folly/json.h 权威指南》 034 《Folly Regex.h 权威指南:从入门到精通 (Folly Regex.h: The Definitive Guide from Beginner to Expert)》 035 《Folly Clock.h 权威指南:系统、实战与深度解析》 036 《folly/Time.h 权威指南:C++ 时间编程实战》 037 《Folly Chrono.h 权威指南》 038 《Folly ThreadName.h 权威指南:系统线程命名深度解析与实战》 039 《Folly OptionParser.h 权威指南》 040 《C++ Range.h 实战指南:从入门到专家》 041 《Folly File.h 权威指南:从入门到精通》 042 《Folly/xlog.h 权威指南:从入门到精通》 043 《Folly Trace.h 权威指南:从入门到精通 (Folly Trace.h: The Definitive Guide from Beginner to Expert)》 044 《Folly Demangle.h 权威指南:C++ 符号反解的艺术与实践 (Folly Demangle.h: The Definitive Guide to C++ Symbol Demangling)》 045 《folly/StackTrace.h 权威指南:原理、应用与最佳实践 (folly/StackTrace.h Definitive Guide: Principles, Applications, and Best Practices)》 046 《Folly Test.h 权威指南:C++ 单元测试实战 (Folly Test.h: The Definitive Guide to C++ Unit Testing in Practice)》 047 《《Folly Benchmark.h 权威指南 (Folly Benchmark.h: The Definitive Guide)》》 048 《Folly Random.h 权威指南:C++随机数生成深度解析》 049 《Folly Numeric.h 权威指南》 050 《Folly Math.h 权威指南:从入门到精通 (Folly Math.h: The Definitive Guide from Beginner to Expert)》 051 《Folly FBMath.h 权威指南:从入门到精通 (Folly FBMath.h: The Definitive Guide - From Beginner to Expert)》 052 《Folly Cursor.h 权威指南:高效数据读取与解析 (Folly Cursor.h Authoritative Guide: Efficient Data Reading and Parsing)》 053 《Folly与Facebook Thrift权威指南:从入门到精通 (Folly and Facebook Thrift: The Definitive Guide from Beginner to Expert)》 054 《Folly CPUThreadPoolExecutor.h 权威指南:原理、实践与高级应用》 055 《Folly HardwareConcurrency.h 权威指南:系统级并发编程基石》

    027 《Folly/IO 权威指南:高效网络编程实战》


    作者Lou Xiao, gemini创建时间2025-04-17 02:31:03更新时间2025-04-17 02:31:03

    🌟🌟🌟本文案由Gemini 2.0 Flash Thinking Experimental 01-21创作,用来辅助学习知识。🌟🌟🌟

    书籍大纲

    ▮▮▮▮ 1. chapter 1: 序章:Folly/IO 概览 (Introduction to Folly/IO)
    ▮▮▮▮▮▮▮ 1.1 Folly/IO 的诞生背景与设计哲学 (Background and Design Philosophy of Folly/IO)
    ▮▮▮▮▮▮▮ 1.2 Folly/IO 在现代网络编程中的地位 (The Role of Folly/IO in Modern Network Programming)
    ▮▮▮▮▮▮▮ 1.3 Folly/IO 的核心组件与功能导览 (Overview of Core Components and Features of Folly/IO)
    ▮▮▮▮▮▮▮ 1.4 Folly/IO 的优势:高性能、异步、零拷贝 (Advantages of Folly/IO: High Performance, Asynchronous, Zero-Copy)
    ▮▮▮▮▮▮▮ 1.5 本书的结构与内容概要 (Structure and Content Summary of this Book)
    ▮▮▮▮ 2. chapter 2: IOBuf:高效的数据缓冲区 (IOBuf: Efficient Data Buffer)
    ▮▮▮▮▮▮▮ 2.1 IOBuf 的基本概念与结构 (Basic Concepts and Structure of IOBuf)
    ▮▮▮▮▮▮▮ 2.2 IOBuf 的内存管理:零拷贝的实现原理 (Memory Management of IOBuf: Principles of Zero-Copy Implementation)
    ▮▮▮▮▮▮▮ 2.3 IOBuf 的链式结构:数据拼接与分割 (Chained Structure of IOBuf: Data Concatenation and Segmentation)
    ▮▮▮▮▮▮▮ 2.4 IOBuf 的常用 API 详解 (Detailed Explanation of Common IOBuf APIs)
    ▮▮▮▮▮▮▮▮▮▮▮ 2.4.1 创建 IOBuf (Creating IOBuf)
    ▮▮▮▮▮▮▮▮▮▮▮ 2.4.2 IOBuf 的数据访问与操作 (Data Access and Operations of IOBuf)
    ▮▮▮▮▮▮▮▮▮▮▮ 2.4.3 IOBuf 的内存管理 API (Memory Management APIs of IOBuf)
    ▮▮▮▮▮▮▮ 2.5 实战代码:使用 IOBuf 构建高效的数据处理管道 (Practical Code: Building Efficient Data Processing Pipelines with IOBuf)
    ▮▮▮▮ 3. chapter 3: 异步 Socket 编程 (Asynchronous Socket Programming)
    ▮▮▮▮▮▮▮ 3.1 异步编程模型与 EventBase (Asynchronous Programming Model and EventBase)
    ▮▮▮▮▮▮▮ 3.2 AsyncSocket:异步 TCP Socket API (AsyncSocket: Asynchronous TCP Socket API)
    ▮▮▮▮▮▮▮ 3.3 AsyncServerSocket:异步 TCP 服务端 Socket API (AsyncServerSocket: Asynchronous TCP Server Socket API)
    ▮▮▮▮▮▮▮ 3.4 UDP Socket 与 Unix Domain Socket 支持 (UDP Socket and Unix Domain Socket Support)
    ▮▮▮▮▮▮▮ 3.5 AsyncSocket 的事件回调机制 (Event Callback Mechanism of AsyncSocket)
    ▮▮▮▮▮▮▮ 3.6 实战代码:构建高性能 TCP 客户端与服务端 (Practical Code: Building High-Performance TCP Client and Server)
    ▮▮▮▮ 4. chapter 4: AsyncTransport:传输层抽象 (AsyncTransport: Transport Layer Abstraction)
    ▮▮▮▮▮▮▮ 4.1 AsyncTransport 的设计理念与优势 (Design Philosophy and Advantages of AsyncTransport)
    ▮▮▮▮▮▮▮ 4.2 AsyncTransportWrapper:基于 AsyncSocket 的传输层封装 (AsyncTransportWrapper: Transport Layer Encapsulation Based on AsyncSocket)
    ▮▮▮▮▮▮▮ 4.3 自定义 AsyncTransport 的实现 (Implementation of Custom AsyncTransport)
    ▮▮▮▮▮▮▮ 4.4 AsyncTransport 的高级应用场景 (Advanced Application Scenarios of AsyncTransport)
    ▮▮▮▮ 5. chapter 5: Request/Response 抽象:构建网络服务 (Request/Response Abstraction: Building Network Services)
    ▮▮▮▮▮▮▮ 5.1 Request 和 Response 的抽象模型 (Abstract Model of Request and Response)
    ▮▮▮▮▮▮▮ 5.2 基于 Request/Response 构建简单的网络协议 (Building Simple Network Protocols Based on Request/Response)
    ▮▮▮▮▮▮▮ 5.3 HTTP 请求与响应的抽象与应用 (Abstraction and Application of HTTP Request and Response)
    ▮▮▮▮▮▮▮ 5.4 实战代码:构建简单的 HTTP 服务端 (Practical Code: Building a Simple HTTP Server)
    ▮▮▮▮ 6. chapter 6: SSL/TLS 安全通信 (SSL/TLS Secure Communication)
    ▮▮▮▮▮▮▮ 6.1 SSLContext 与 SSLOptions:SSL/TLS 配置 (SSLContext and SSLOptions: SSL/TLS Configuration)
    ▮▮▮▮▮▮▮ 6.2 在 AsyncSocket 中启用 SSL/TLS (Enabling SSL/TLS in AsyncSocket)
    ▮▮▮▮▮▮▮ 6.3 基于 OpenSSL 或 BoringSSL 的实现细节 (Implementation Details Based on OpenSSL or BoringSSL)
    ▮▮▮▮▮▮▮ 6.4 SSL/TLS 的性能考量与优化 (Performance Considerations and Optimization of SSL/TLS)
    ▮▮▮▮ 7. chapter 7: Cursor 与 IOBufQueue:高效数据处理工具 (Cursor and IOBufQueue: Efficient Data Processing Tools)
    ▮▮▮▮▮▮▮ 7.1 Cursor:IOBuf 中的高效数据读写 (Cursor: Efficient Data Read and Write in IOBuf)
    ▮▮▮▮▮▮▮ 7.2 IOBufQueue:网络数据缓存与分片处理 (IOBufQueue: Network Data Buffering and Fragmentation Handling)
    ▮▮▮▮▮▮▮ 7.3 Cursor 与 IOBufQueue 的组合应用 (Combined Application of Cursor and IOBufQueue)
    ▮▮▮▮▮▮▮ 7.4 实战代码:使用 Cursor 和 IOBufQueue 解析网络协议 (Practical Code: Parsing Network Protocols Using Cursor and IOBufQueue)
    ▮▮▮▮ 8. chapter 8: 高级主题与实战案例 (Advanced Topics and Practical Case Studies)
    ▮▮▮▮▮▮▮ 8.1 Folly/IO 的性能调优与最佳实践 (Performance Tuning and Best Practices of Folly/IO)
    ▮▮▮▮▮▮▮ 8.2 Folly/IO 与其他 Folly 库的集成 (Integration of Folly/IO with Other Folly Libraries)
    ▮▮▮▮▮▮▮ 8.3 基于 Folly/IO 构建复杂网络应用案例 (Case Studies of Building Complex Network Applications Based on Folly/IO)
    ▮▮▮▮▮▮▮▮▮▮▮ 8.3.1 构建高性能代理服务器 (Building a High-Performance Proxy Server)
    ▮▮▮▮▮▮▮▮▮▮▮ 8.3.2 构建分布式消息队列系统 (Building a Distributed Message Queue System)
    ▮▮▮▮▮▮▮ 8.4 Folly/IO 的未来发展趋势展望 (Future Development Trends of Folly/IO)
    ▮▮▮▮ 9. chapter 9: 总结与展望 (Summary and Outlook)
    ▮▮▮▮▮▮▮ 9.1 Folly/IO 的核心价值回顾 (Review of the Core Value of Folly/IO)
    ▮▮▮▮▮▮▮ 9.2 Folly/IO 在未来网络编程中的应用前景 (Application Prospects of Folly/IO in Future Network Programming)
    ▮▮▮▮▮▮▮ 9.3 持续学习 Folly/IO 的资源与建议 (Resources and Suggestions for Continuous Learning of Folly/IO)


    1. chapter 1: 序章:Folly/IO 概览 (Introduction to Folly/IO)

    1.1 Folly/IO 的诞生背景与设计哲学 (Background and Design Philosophy of Folly/IO)

    在互联网技术日新月异的今天,构建高性能、高可靠性的网络服务已成为一项核心挑战。面对海量数据和高并发请求,传统的同步阻塞 I/O 模型逐渐显露出其局限性。为了应对这些挑战,Facebook 开源了其内部广泛使用的 C++ 库 Folly(Facebook Open Source Library)。Folly/IO 作为 Folly 库的重要组成部分,应运而生,旨在提供一套高效、灵活且易于使用的 I/O 解决方案,以满足现代网络编程的需求。

    Folly/IO 的诞生并非偶然,而是 Facebook 在其大规模、高负载的在线服务实践中不断探索和积累的成果。在构建诸如社交网络、即时通讯等复杂应用时,Facebook 的工程师们深刻体会到传统 I/O 模型的不足,以及异步非阻塞 I/O 模型的优势。因此,Folly/IO 从一开始就定位于提供异步非阻塞 I/O(Asynchronous Non-blocking I/O) 的解决方案,并围绕以下设计哲学展开:

    高性能(High Performance): 这是 Folly/IO 最核心的设计目标之一。为了实现高性能,Folly/IO 采用了多种技术手段,包括:
    ▮▮▮▮ⓑ 零拷贝(Zero-Copy): 最大程度地减少数据在内核空间和用户空间之间的拷贝,以及用户空间内部的拷贝,从而降低 CPU 开销,提高数据处理效率。IOBuf 是零拷贝特性的关键实现。
    ▮▮▮▮ⓒ 异步非阻塞 I/O: 利用操作系统的 I/O 多路复用机制(I/O Multiplexing Mechanism),如 epoll (Linux)、kqueue (macOS, FreeBSD) 等,实现单线程处理多并发连接,避免线程切换的开销,提高并发处理能力。AsyncSocketEventBase 是异步非阻塞 I/O 的核心组件。
    ▮▮▮▮ⓓ 高效的数据结构: 例如,IOBuf 采用链式结构,方便数据的拼接和分割,减少内存碎片,提高内存利用率。IOBufQueue 则用于高效地缓存和处理接收到的网络数据。

    易用性(Ease of Use): 尽管追求高性能,Folly/IO 并没有牺牲易用性。相反,它致力于提供简洁、清晰的 API,降低开发者的学习成本和使用门槛。
    ▮▮▮▮ⓑ 清晰的抽象: Folly/IO 对底层的 I/O 操作进行了高度抽象,例如 AsyncTransport 抽象了传输层,RequestResponse 抽象了请求和响应,使得开发者可以更专注于业务逻辑的实现,而无需过多关注底层的 I/O 细节。
    ▮▮▮▮ⓒ 完善的文档和示例: Folly 社区提供了相对完善的文档和示例代码,帮助开发者快速上手和使用 Folly/IO。本书也将致力于提供系统、全面的指南,帮助读者深入理解和应用 Folly/IO。

    灵活性(Flexibility): Folly/IO 提供了丰富的组件和接口,支持各种网络协议和应用场景。
    ▮▮▮▮ⓑ 协议无关性(Protocol Agnostic): Folly/IO 的核心组件,如 IOBufAsyncSocketAsyncTransport 等,并不限定于特定的网络协议。开发者可以基于这些组件构建各种自定义协议,或者集成已有的协议,如 HTTP、TCP、UDP 等。
    ▮▮▮▮ⓒ 可扩展性(Extensibility): Folly/IO 的架构设计具有良好的可扩展性。开发者可以根据自身需求,自定义 AsyncTransportRequest/Response 模型等,扩展 Folly/IO 的功能。

    可靠性(Reliability): 作为 Facebook 内部广泛使用的库,Folly/IO 经历了长时间、大规模的线上环境考验,具有很高的可靠性和稳定性。
    ▮▮▮▮ⓑ 完善的测试: Folly 社区拥有完善的单元测试和集成测试体系,保证了 Folly/IO 的代码质量和稳定性。
    ▮▮▮▮ⓒ 持续的维护和更新: Folly 社区活跃,持续对 Folly/IO 进行维护和更新,修复 bug,增加新功能,提升性能。

    总而言之,Folly/IO 的诞生背景是现代网络编程对高性能、高并发、低延迟的迫切需求。其设计哲学围绕高性能、易用性、灵活性和可靠性展开,旨在为开发者提供一套强大的工具,构建高效、稳定的网络服务。

    1.2 Folly/IO 在现代网络编程中的地位 (The Role of Folly/IO in Modern Network Programming)

    在现代网络编程领域,Folly/IO 扮演着越来越重要的角色。随着互联网应用的日益复杂和用户规模的不断扩大,对网络服务的性能和可扩展性提出了更高的要求。传统的同步阻塞 I/O 模型在处理高并发连接时面临诸多挑战,而 Folly/IO 提供的异步非阻塞 I/O 解决方案,恰好能够有效地应对这些挑战。

    Folly/IO 在现代网络编程中的地位主要体现在以下几个方面:

    高性能网络应用的基础设施: Folly/IO 提供的 IOBufAsyncSocketAsyncTransport 等核心组件,为构建高性能网络应用奠定了坚实的基础。无论是开发高性能的服务器、客户端,还是构建复杂的网络中间件,Folly/IO 都能提供强大的支持。许多知名的开源项目和商业产品都采用了 Folly/IO 作为其网络通信的基础库。

    异步编程的典范: Folly/IO 深入实践了异步编程模型,并提供了完善的异步编程 API。通过 EventBase、回调函数、Promise/Future 等机制,Folly/IO 使得异步编程更加简洁、高效、易于管理。学习和使用 Folly/IO,可以帮助开发者深入理解异步编程的精髓,掌握异步编程的最佳实践。

    零拷贝技术的实践者: 零拷贝技术是提升网络 I/O 性能的关键手段。Folly/IO 的 IOBuf 库是零拷贝技术的优秀实践,它通过精巧的内存管理和数据结构设计,最大程度地减少数据拷贝,提升数据处理效率。研究和应用 IOBuf,可以帮助开发者深入理解零拷贝技术的原理和应用,掌握高性能数据处理的关键技巧。

    现代 C++ 网络编程的代表: Folly/IO 是一个典型的现代 C++ 库,它充分利用了 C++11/14/17 等新标准提供的特性,如 std::movestd::unique_ptrlambda 表达式等,代码风格现代、简洁、高效。学习和使用 Folly/IO,可以帮助开发者提升现代 C++ 网络编程的技能,掌握现代 C++ 的最佳实践。

    Facebook 开源生态的重要组成部分: Folly/IO 是 Folly 库的重要组成部分,而 Folly 库又是 Facebook 开源生态的核心项目之一。Folly 库包含了大量的通用 C++ 组件,涵盖了并发、容器、字符串处理、时间处理、配置管理等多个方面。Folly/IO 与 Folly 库的其他组件可以很好地协同工作,共同构建强大的应用程序。

    总而言之,Folly/IO 在现代网络编程中占据着重要的地位。它不仅是一个高性能、高可靠性的网络 I/O 库,更是一种先进的网络编程思想和技术的体现。掌握 Folly/IO,对于提升网络编程能力,构建高性能网络应用,具有重要的意义。

    1.3 Folly/IO 的核心组件与功能导览 (Overview of Core Components and Features of Folly/IO)

    Folly/IO 库由多个核心组件构成,这些组件协同工作,共同提供了强大的异步非阻塞 I/O 功能。理解这些核心组件及其功能,是深入学习和应用 Folly/IO 的关键。本节将对 Folly/IO 的核心组件进行概览,并简要介绍它们的主要功能。

    IOBuf: IOBuf 是 Folly/IO 中最基础、最重要的组件之一,它是一个高效的数据缓冲区,用于管理网络数据。
    核心功能:
    ▮▮▮▮⚝ 零拷贝: IOBuf 的设计目标是实现零拷贝,减少数据拷贝开销,提高数据处理效率。
    ▮▮▮▮⚝ 链式结构: IOBuf 采用链式结构,可以方便地拼接和分割数据,灵活地管理内存。
    ▮▮▮▮⚝ 内存管理: IOBuf 提供了精细的内存管理 API,可以控制内存的分配和释放,避免内存泄漏和碎片。
    ▮▮▮▮⚝ 数据访问: IOBuf 提供了多种数据访问方式,可以方便地读取和写入数据。

    EventBase: EventBase 是 Folly/IO 中异步事件处理的核心组件,它负责事件循环的管理和事件的调度。
    核心功能:
    ▮▮▮▮⚝ 事件循环: EventBase 维护一个事件循环,不断地监听和处理各种事件,如 I/O 事件、定时器事件、信号事件等。
    ▮▮▮▮⚝ 事件注册与调度: 开发者可以将各种事件注册到 EventBase 中,EventBase 负责事件的调度和回调函数的执行。
    ▮▮▮▮⚝ 线程管理: EventBase 可以运行在单线程或多线程环境中,支持多线程并发处理。

    AsyncSocketAsyncServerSocket: AsyncSocketAsyncServerSocket 是 Folly/IO 提供的异步 Socket API,用于进行网络通信。
    核心功能:
    ▮▮▮▮⚝ 异步 TCP/UDP/Unix Domain Socket: 支持 TCP、UDP、Unix Domain Socket 等多种协议。
    ▮▮▮▮⚝ 非阻塞 I/O 操作: 提供非阻塞的 connectacceptreadwrite 等 I/O 操作。
    ▮▮▮▮⚝ 事件回调: 通过回调函数机制,处理 Socket 的各种事件,如连接建立、数据接收、连接关闭等。
    ▮▮▮▮⚝ SSL/TLS 支持: 可以集成 SSL/TLS 库,实现安全的网络通信。

    AsyncTransportAsyncTransportWrapper: AsyncTransport 是 Folly/IO 提供的异步传输层抽象,用于构建各种网络协议。AsyncTransportWrapper 是基于 AsyncSocketAsyncTransport 实现。
    核心功能:
    ▮▮▮▮⚝ 传输层抽象: AsyncTransport 抽象了底层的传输细节,提供统一的接口,方便构建各种网络协议。
    ▮▮▮▮⚝ 协议封装: 可以在 AsyncTransport 之上封装各种协议,如 HTTP、SPDY、Thrift 等。
    ▮▮▮▮⚝ 可扩展性: 开发者可以自定义 AsyncTransport 的实现,扩展 Folly/IO 的传输层功能。

    RequestResponse: RequestResponse 是 Folly/IO 提供的请求和响应抽象,用于构建网络服务。
    核心功能:
    ▮▮▮▮⚝ 请求/响应模型: 抽象了网络服务中常见的请求和响应模型,方便构建各种网络服务。
    ▮▮▮▮⚝ 协议无关性: RequestResponse 的抽象并不限定于特定的协议,可以用于构建各种协议的网络服务。
    ▮▮▮▮⚝ 易于扩展: 开发者可以自定义 RequestResponse 的实现,扩展 Folly/IO 的网络服务功能。

    SSLContextSSLOptions: SSLContextSSLOptions 是 Folly/IO 提供的 SSL/TLS 配置组件,用于实现安全的网络通信。
    核心功能:
    ▮▮▮▮⚝ SSL/TLS 配置: 提供了丰富的 SSL/TLS 配置选项,如证书、密钥、加密算法等。
    ▮▮▮▮⚝ OpenSSL/BoringSSL 支持: 支持 OpenSSL 和 BoringSSL 等主流 SSL/TLS 库。
    ▮▮▮▮⚝ 安全通信: 可以方便地在 AsyncSocket 中启用 SSL/TLS,实现安全的网络通信。

    Cursor: Cursor 是 Folly/IO 提供的游标类,用于在 IOBuf 中高效地读取和写入数据。
    核心功能:
    ▮▮▮▮⚝ 高效数据读写: Cursor 提供了高效的 readwrite 操作,可以方便地在 IOBuf 中移动和访问数据。
    ▮▮▮▮⚝ 零拷贝访问: Cursor 可以实现零拷贝的数据访问,避免不必要的数据拷贝。

    IOBufQueue: IOBufQueue 是 Folly/IO 提供的 IOBuf 队列,用于缓存接收到的网络数据,方便处理分片数据。
    核心功能:
    ▮▮▮▮⚝ 数据缓存: IOBufQueue 可以缓存接收到的网络数据,应对网络数据分片和延迟到达的情况。
    ▮▮▮▮⚝ 分片处理: IOBufQueue 提供了方便的 API,用于处理分片数据,例如,可以等待接收到完整的数据包再进行处理。

    以上列举的是 Folly/IO 的核心组件及其主要功能。在后续的章节中,我们将逐一深入地介绍这些组件,并通过实战代码演示它们的应用。

    1.4 Folly/IO 的优势:高性能、异步、零拷贝 (Advantages of Folly/IO: High Performance, Asynchronous, Zero-Copy)

    Folly/IO 之所以能在现代网络编程中占据重要地位,并被广泛应用于高性能网络应用开发,主要归功于其显著的优势。这些优势可以概括为三个关键词:高性能(High Performance)异步(Asynchronous)零拷贝(Zero-Copy)

    高性能(High Performance): 高性能是 Folly/IO 最核心的优势之一。Folly/IO 通过多种技术手段,最大程度地提升网络 I/O 的性能。
    异步非阻塞 I/O: 采用异步非阻塞 I/O 模型,利用操作系统的 I/O 多路复用机制,单线程即可处理大量并发连接,避免了传统同步阻塞 I/O 模型中线程切换的开销,提高了并发处理能力和吞吐量。
    零拷贝技术: IOBuf 库的设计充分考虑了零拷贝,减少了数据在内核空间和用户空间,以及用户空间内部的拷贝次数,降低了 CPU 开销,提升了数据处理效率。
    高效的数据结构和算法: Folly/IO 内部使用了许多高效的数据结构和算法,例如,IOBuf 的链式结构、IOBufQueue 的队列实现、Cursor 的高效数据访问等,这些都为高性能提供了保障。
    精细的内存管理: IOBuf 提供了精细的内存管理 API,可以控制内存的分配和释放,避免内存泄漏和碎片,提高内存利用率,从而间接地提升性能。

    异步(Asynchronous): 异步是 Folly/IO 的另一个重要优势。Folly/IO 提供了完善的异步编程 API,使得开发者可以方便地构建异步非阻塞的网络应用。
    EventBase 异步事件循环: EventBase 提供了强大的异步事件循环机制,可以管理各种事件,并异步地执行回调函数。
    Promise/Future 异步编程: Folly 库提供了 PromiseFuture,可以方便地进行异步编程,处理异步操作的结果。Folly/IO 可以与 Promise/Future 很好地集成,构建更加清晰、易于管理的异步代码。
    回调函数机制: AsyncSocket 等组件广泛使用了回调函数机制,处理异步事件,使得异步编程更加灵活和高效。
    易于理解和使用: Folly/IO 的异步 API 设计简洁、清晰,易于理解和使用,降低了异步编程的门槛。

    零拷贝(Zero-Copy): 零拷贝是 Folly/IO 的一个关键特性,也是其高性能的重要保障。Folly/IO 通过 IOBuf 库,实现了多种零拷贝技术。
    IOBuf 共享内存: IOBuf 可以共享内存,多个 IOBuf 可以指向同一块物理内存,减少内存拷贝。
    splice 系统调用: 在支持 splice 系统调用的平台上,Folly/IO 可以利用 splice 实现零拷贝的数据传输,例如,将数据从 Socket 接收缓冲区直接传输到文件描述符,或者从一个 Socket 传输到另一个 Socket,无需经过用户空间。
    sendfile 系统调用: 类似于 splicesendfile 系统调用也可以实现零拷贝的文件传输,Folly/IO 在某些场景下会使用 sendfile 提升性能.
    减少用户空间拷贝: IOBuf 的链式结构和 Cursor 的高效数据访问,也旨在减少用户空间内部的数据拷贝。

    除了以上三个核心优势,Folly/IO 还具有其他一些优点,例如:

    跨平台性(Cross-Platform): Folly/IO 可以在 Linux、macOS、Windows 等多个平台上运行。
    良好的社区支持(Good Community Support): Folly 是 Facebook 开源的项目,拥有活跃的社区,提供了相对完善的文档和示例,并且持续维护和更新。
    与 Folly 库其他组件的良好集成: Folly/IO 可以与 Folly 库的其他组件,如并发库、容器库、字符串处理库等,很好地协同工作,构建更强大的应用程序。

    综上所述,Folly/IO 以其高性能、异步、零拷贝等显著优势,成为现代网络编程的有力工具,被广泛应用于各种高性能网络应用的开发中。

    1.5 本书的结构与内容概要 (Structure and Content Summary of this Book)

    本书旨在成为一本全面、深入、实用的 Folly/IO 权威指南,帮助读者从入门到精通,掌握 Folly/IO 的核心概念、技术和应用。本书结构清晰,内容循序渐进,理论与实践相结合,力求让读者不仅理解 Folly/IO 的原理,更能将其应用于实际项目中。

    本书共分为九个章节,结构如下:

    第一章:序章:Folly/IO 概览 (当前章节)
    ⚝ 本章作为全书的开篇,首先介绍了 Folly/IO 的诞生背景、设计哲学,以及其在现代网络编程中的地位。然后,概览了 Folly/IO 的核心组件和主要功能,最后总结了 Folly/IO 的优势,并对本书的结构和内容进行了概要介绍,为读者构建 Folly/IO 的整体认知框架。

    第二章:IOBuf:高效的数据缓冲区
    ⚝ 本章深入剖析 Folly/IO 的核心组件 IOBuf。从 IOBuf 的基本概念、结构入手,详细讲解 IOBuf 的内存管理、零拷贝实现原理、链式结构以及常用 API。并通过实战代码,演示如何使用 IOBuf 构建高效的数据处理管道。

    第三章:异步 Socket 编程
    ⚝ 本章聚焦于 Folly/IO 的异步 Socket API。首先介绍异步编程模型和 EventBase 的概念,然后详细讲解 AsyncSocketAsyncServerSocket 的 API 和事件回调机制。同时,也会涉及 UDP Socket 和 Unix Domain Socket 的支持。最后,通过实战代码,演示如何构建高性能的 TCP 客户端和服务器。

    第四章:AsyncTransport:传输层抽象
    ⚝ 本章深入探讨 AsyncTransport 传输层抽象。从 AsyncTransport 的设计理念和优势入手,讲解 AsyncTransportWrapper 的实现原理和使用方法。并进一步探讨如何自定义 AsyncTransport,以及 AsyncTransport 的高级应用场景。

    第五章:Request/Response 抽象:构建网络服务
    ⚝ 本章介绍 Folly/IO 提供的 RequestResponse 抽象。讲解请求和响应的抽象模型,以及如何基于 Request/Response 构建简单的网络协议。并通过 HTTP 请求和响应的抽象与应用为例,演示如何使用 Request/Response 构建网络服务。最后,通过实战代码,构建一个简单的 HTTP 服务端。

    第六章:SSL/TLS 安全通信
    ⚝ 本章专注于网络安全通信,介绍如何使用 Folly/IO 实现 SSL/TLS 安全通信。详细讲解 SSLContextSSLOptions 的配置,以及如何在 AsyncSocket 中启用 SSL/TLS。同时,也会探讨基于 OpenSSL 或 BoringSSL 的实现细节,以及 SSL/TLS 的性能考量与优化。

    第七章:Cursor 与 IOBufQueue:高效数据处理工具
    ⚝ 本章介绍 Folly/IO 提供的两个高效数据处理工具:CursorIOBufQueue。详细讲解 CursorIOBuf 中的高效数据读写,以及 IOBufQueue 在网络数据缓存和分片处理中的应用。并通过实战代码,演示如何使用 CursorIOBufQueue 解析网络协议。

    第八章:高级主题与实战案例
    ⚝ 本章深入探讨 Folly/IO 的高级主题,并结合实战案例,进一步提升读者的应用能力。内容包括 Folly/IO 的性能调优与最佳实践,Folly/IO 与其他 Folly 库的集成,以及基于 Folly/IO 构建复杂网络应用案例,例如高性能代理服务器和分布式消息队列系统。最后,展望 Folly/IO 的未来发展趋势。

    第九章:总结与展望
    ⚝ 本章作为全书的结尾,对 Folly/IO 的核心价值进行回顾,展望 Folly/IO 在未来网络编程中的应用前景。并为读者提供持续学习 Folly/IO 的资源和建议,引导读者深入探索 Folly/IO 的世界。

    通过本书的学习,读者将全面掌握 Folly/IO 的各项核心技术,并具备使用 Folly/IO 构建高性能、高可靠性网络应用的能力。本书既适合初学者入门学习 Folly/IO,也适合有一定经验的工程师深入研究和应用 Folly/IO。希望本书能成为读者学习 Folly/IO 的得力助手,助力读者在网络编程领域取得更大的成就。

    END_OF_CHAPTER

    2. chapter 2: IOBuf:高效的数据缓冲区 (IOBuf: Efficient Data Buffer)

    2.1 IOBuf 的基本概念与结构 (Basic Concepts and Structure of IOBuf)

    在现代网络编程和高性能计算领域,高效的数据处理是至关重要的。Folly/IO 库中的 IOBuf (Input/Output Buffer) 组件,正是为了解决这一需求而设计的核心数据结构。IOBuf 不仅仅是一个简单的字节缓冲区,而是一个功能强大、高度优化的数据容器,它旨在提升网络数据处理的效率和性能。

    IOBuf 的核心目标是提供一种高效的方式来管理和操作数据,尤其是在网络 I/O 场景中。它被设计为支持零拷贝 (zero-copy) 操作,减少不必要的数据复制,从而降低 CPU 消耗,提高数据处理速度。此外,IOBuf 还支持链式结构,方便处理分散的数据块,并优化了内存管理,以适应高并发、大数据量的网络应用。

    从概念上讲,IOBuf 可以被视为一个智能指针,它指向一块或多块连续的内存区域,并记录了这些内存区域的起始位置、有效数据长度等信息。与传统的字节数组或 std::vector<char> 相比,IOBuf 提供了更丰富的功能和更高的性能。

    IOBuf 的基本结构主要包含以下几个关键组成部分:

    数据指针 (Data Pointers)IOBuf 内部维护了多个指针,用于跟踪数据在内存中的位置。
    ▮▮▮▮ⓑ data(): 指向 IOBuf 中有效数据的起始位置。
    ▮▮▮▮ⓒ writableData(): 指向可写入数据的起始位置,通常与 data() 相同,但在某些情况下可能有所不同,例如在预留空间的情况下。
    ▮▮▮▮ⓓ prev()next(): 用于链式 IOBuf 结构,prev() 指向上一个 IOBufnext() 指向下一个 IOBuf。对于独立的 IOBuf,这两个指针可能为空。

    区域信息 (Region Information)IOBuf 记录了有效数据区域的相关信息,以便进行数据访问和操作。
    ▮▮▮▮ⓑ length(): 表示 IOBuf 中有效数据的长度,即从 data() 指针开始的有效字节数。
    ▮▮▮▮ⓒ capacity(): 表示 IOBuf 占用的总内存空间大小,包括已使用和未使用的空间。
    ▮▮▮▮ⓓ headroom(): 表示 IOBuf 数据起始位置之前的可用空间,用于向前扩展数据。
    ▮▮▮▮ⓔ tailroom(): 表示 IOBuf 数据末尾之后的可用空间,用于向后扩展数据。

    内存管理 (Memory Management)IOBuf 采用了精细的内存管理策略,以支持零拷贝和高效的数据操作。
    ▮▮▮▮ⓑ 引用计数 (Reference Counting): IOBuf 内部使用了引用计数来管理底层内存的生命周期。多个 IOBuf 可以共享同一块内存区域,只有当最后一个引用释放时,内存才会被真正释放。
    ▮▮▮▮ⓒ 可变性 (Mutability): IOBuf 可以是可变的 (mutable) 或不可变的 (immutable)。可变的 IOBuf 允许修改其内容,而不可变的 IOBuf 则不允许修改,这有助于数据安全和共享。

    通过这些精巧的设计,IOBuf 能够实现以下关键特性:

    高效的内存利用: 通过共享内存和延迟复制等技术,减少内存分配和复制的开销。
    零拷贝支持: 允许在不同的 IOBuf 之间共享数据,避免不必要的数据复制,提高性能。
    灵活的数据操作: 提供了丰富的 API,支持数据的拼接、分割、截取、移动等操作。
    链式结构: 方便处理分散的数据块,例如网络协议中的分段数据。

    总而言之,IOBufFolly/IO 库的核心组件,它为高效的网络编程提供了坚实的基础。理解 IOBuf 的基本概念和结构,是深入学习和应用 Folly/IO 的关键第一步。在接下来的章节中,我们将更深入地探讨 IOBuf 的内存管理、API 使用以及实战应用。

    2.2 IOBuf 的内存管理:零拷贝的实现原理 (Memory Management of IOBuf: Principles of Zero-Copy Implementation)

    零拷贝 (Zero-Copy) 是一种旨在减少数据在内存中复制次数的技术,尤其在高性能网络编程中至关重要。传统的数据处理流程通常涉及多次数据拷贝,例如从内核空间到用户空间,再从用户空间的一个缓冲区到另一个缓冲区。这些拷贝操作会消耗大量的 CPU 时间和内存带宽,成为性能瓶颈。IOBuf 的设计核心之一就是最大程度地减少数据拷贝,实现零拷贝或接近零拷贝的数据传输和处理。

    IOBuf 实现零拷贝的关键原理主要体现在以下几个方面:

    共享内存 (Shared Memory)IOBuf 允许多个 IOBuf 对象共享同一块底层内存缓冲区。当需要复制或传递数据时,IOBuf 并不总是进行实际的内存复制,而是通过创建新的 IOBuf 对象,并让其指向相同的内存区域来实现。这种方式避免了数据的物理复制,提高了效率。

    引用计数 (Reference Counting):为了安全地共享内存,IOBuf 使用了引用计数机制。每个 IOBuf 对象都维护一个引用计数器,记录有多少个 IOBuf 正在共享同一块内存。当创建一个新的 IOBuf 共享内存时,引用计数器会增加;当一个 IOBuf 对象被销毁或不再引用共享内存时,引用计数器会减少。只有当引用计数器降为零时,底层内存才会被真正释放。这确保了内存的有效管理,避免了悬 dangling 指针和内存泄漏。

    写时复制 (Copy-on-Write, COW):在某些情况下,即使多个 IOBuf 共享同一块内存,也可能需要对数据进行修改。为了避免修改影响到其他共享相同内存的 IOBufIOBuf 采用了写时复制策略。当需要修改一个共享内存的 IOBuf 时,IOBuf 不会直接修改原始内存,而是先创建一个原始内存的副本,并在副本上进行修改。这样,原始的 IOBuf 保持不变,而修改后的数据则存储在新的内存区域中。写时复制延迟了实际的内存复制操作,只有在真正需要修改数据时才会发生复制,从而提高了效率。

    分段 (Fragmentation) 与聚集 (Gathering)IOBuf 支持链式结构,可以将数据分散存储在多个不连续的内存块中。这种分段的特性允许 IOBuf 直接使用网络协议栈接收到的数据缓冲区,而无需将数据复制到连续的内存空间。同时,IOBuf 也支持聚集操作,可以将多个分散的 IOBuf 链合并成一个逻辑上连续的数据流,方便上层应用处理。分段和聚集技术减少了数据在内存中的移动和重组,进一步提升了零拷贝的效率。

    CursorIOBufQueue: Folly/IO 提供了 CursorIOBufQueue 等工具,与 IOBuf 协同工作,进一步优化数据处理流程。Cursor 允许在 IOBuf 中高效地读取和写入数据,而无需进行额外的内存拷贝。IOBufQueue 用于缓存接收到的网络数据,并支持分片数据的处理,使得网络数据的接收和处理更加高效。

    零拷贝的优势

    使用 IOBuf 和零拷贝技术,可以带来显著的性能提升,尤其是在高吞吐量、低延迟的网络应用中。其主要优势包括:

    减少 CPU 消耗: 零拷贝避免了不必要的数据复制操作,降低了 CPU 在数据拷贝上的开销,使得 CPU 可以更多地用于实际的数据处理和业务逻辑。
    提高内存带宽利用率: 减少内存拷贝意味着减少了内存带宽的占用,使得内存带宽可以更有效地用于数据传输和访问。
    降低延迟: 数据拷贝操作会引入延迟,零拷贝技术可以减少数据处理的延迟,提高应用的响应速度。
    提升系统吞吐量: 通过减少 CPU 和内存开销,零拷贝技术可以提升系统的整体吞吐量,使得系统能够处理更多的并发连接和更大的数据流量。

    总结来说,IOBuf 通过共享内存、引用计数、写时复制、分段聚集等多种技术手段,实现了高效的内存管理和零拷贝的数据处理能力。这使得 Folly/IO 成为构建高性能网络应用的理想选择。理解 IOBuf 的零拷贝原理,有助于我们更好地利用 Folly/IO 库,并设计出更高效的网络应用。

    2.3 IOBuf 的链式结构:数据拼接与分割 (Chained Structure of IOBuf: Data Concatenation and Segmentation)

    在网络编程中,数据往往不是以连续的块到达的,而是可能被分成多个片段 (fragments) 传输。此外,在处理复杂协议时,可能需要将多个不同的数据块拼接在一起,或者将一个大的数据块分割成多个小的部分。IOBuf 的链式结构 (Chained Structure) 正是为了高效地处理这些场景而设计的。

    IOBuf 的链式结构允许将多个 IOBuf 对象链接在一起,形成一个链表 (linked list)。每个 IOBuf 节点可以指向下一个 IOBuf 节点,从而构成一个逻辑上连续的数据流。这种链式结构具有以下优点:

    高效的数据拼接 (Concatenation):当需要将多个 IOBuf 对象拼接成一个更大的数据块时,无需进行实际的数据复制。只需要简单地将这些 IOBuf 对象链接在一起,形成一个链表即可。例如,如果已经有 IOBuf AIOBuf B,要将它们拼接成 IOBuf C,只需要让 IOBuf Anext 指针指向 IOBuf BIOBuf C 就成为了 AB 的链表表示。这种拼接操作的时间复杂度接近 \(O(1)\),非常高效。

    灵活的数据分割 (Segmentation):与拼接类似,数据分割也可以在链式结构中高效地完成。当需要将一个大的 IOBuf 分割成多个小的 IOBuf 时,可以通过调整 IOBuf 链表的指针来实现。例如,要将一个 IOBuf 链表从中间位置分割成两个链表,只需要断开链表中间的连接,并设置新的链表头和链表尾即可。分割操作同样避免了数据的物理复制,提高了效率。

    零拷贝的数据操作: 链式结构与 IOBuf 的零拷贝特性相结合,可以实现更高级的数据操作。例如,在网络协议解析中,可以根据协议格式将接收到的数据分割成多个 IOBuf 链表,每个链表代表协议的不同部分 (如头部、负载等)。这些链表可以独立地传递和处理,而无需进行额外的数据拷贝。

    适应分段数据: 网络协议,特别是 TCP 协议,数据传输是基于数据流的,数据包可能会被分段。IOBuf 链式结构可以很好地适应这种分段数据。接收到的每个数据包可以封装成一个 IOBuf,然后链接到 IOBufQueue 中。上层应用可以从 IOBufQueue 中按顺序取出 IOBuf 进行处理,而无需关心数据是否分段。

    链式结构的应用场景

    IOBuf 的链式结构在网络编程中有着广泛的应用场景:

    网络协议解析: 在解析网络协议时,通常需要将接收到的数据流分割成协议头部、负载等部分。链式 IOBuf 可以方便地表示和操作这些协议的不同部分。
    HTTP 消息处理: HTTP 消息由头部和 body 组成,body 部分可能很大,并且可能分块传输 (chunked transfer encoding)。链式 IOBuf 可以有效地处理 HTTP 消息,特别是 body 部分。
    文件传输: 在文件传输场景中,文件数据可能被分成多个块进行传输。链式 IOBuf 可以用于管理这些数据块,并支持高效的拼接和传输。
    日志处理: 日志数据通常是追加写入的,并且可能来自不同的来源。链式 IOBuf 可以用于缓存和拼接日志数据,方便批量写入或传输。

    链式结构的操作

    IOBuf 提供了丰富的 API 来操作链式结构,包括:

    clone(): 克隆一个 IOBuf 链表,新的链表与原始链表共享底层数据缓冲区,但链表结构是独立的。
    copy(): 复制一个 IOBuf 链表,新的链表拥有独立的底层数据缓冲区,数据被完全复制。
    prependChain()appendChain(): 将一个 IOBuf 链表添加到另一个 IOBuf 链表的头部或尾部,实现链表的拼接。
    split(): 将一个 IOBuf 链表在指定位置分割成两个链表。
    coalesce(): 将一个 IOBuf 链表合并成一个或少数几个连续的 IOBuf,减少链表节点的数量,提高数据访问的连续性。

    通过灵活运用 IOBuf 的链式结构和相关 API,可以构建高效、可扩展的网络数据处理系统。链式结构不仅提高了数据拼接和分割的效率,也为实现更复杂的数据操作和协议处理提供了基础。在后续章节中,我们将通过实战代码进一步演示如何使用链式 IOBuf 构建高效的网络应用。

    2.4 IOBuf 的常用 API 详解 (Detailed Explanation of Common IOBuf APIs)

    IOBuf 提供了丰富的 API 供开发者使用,以便进行各种数据操作和内存管理。本节将详细介绍 IOBuf 的常用 API,并将其分为创建 API、数据访问与操作 API 以及内存管理 API 三个方面进行讲解。

    2.4.1 创建 IOBuf (Creating IOBuf)

    创建 IOBuf 对象是使用 IOBuf 的第一步。Folly/IO 提供了多种创建 IOBuf 的方式,以适应不同的场景需求。

    IOBuf::create(size_t size): 分配一个新的、可变的 IOBuf,并分配指定大小的内存缓冲区。这是最基本的创建 IOBuf 的方法。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/io/IOBuf.h>
    2
    3 int main() {
    4 // 创建一个大小为 1024 字节的 IOBuf
    5 auto iobuf = folly::IOBuf::create(1024);
    6 // 此时 iobuf->capacity() 为 1024, iobuf->length() 为 0
    7 return 0;
    8 }

    IOBuf::wrapBuffer(const void* data, size_t size, IOBuf::CopyOptions options = IOBuf::CopyOptions::copy()): 将已有的内存缓冲区 (例如,C 风格的数组或 std::string 的数据) 包装 (wrap) 成 IOBuf。可以指定是否复制数据。

    IOBuf::CopyOptions::copy() (默认): 复制数据到新的 IOBuf 中。
    IOBuf::CopyOptions::noCopy(): 不复制数据,IOBuf 直接引用原始内存缓冲区。注意: 使用 noCopy 时,需要确保原始内存缓冲区的生命周期长于 IOBuf 对象,并且在 IOBuf 使用期间不会被修改。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/io/IOBuf.h>
    2 #include <string>
    3
    4 int main() {
    5 std::string str = "Hello, IOBuf!";
    6 // 复制 string 的数据到新的 IOBuf
    7 auto iobuf1 = folly::IOBuf::wrapBuffer(str.data(), str.size());
    8 // iobuf1 拥有独立的数据副本
    9
    10 char buffer[] = "World!";
    11 // 不复制 buffer 的数据,IOBuf 直接引用 buffer
    12 auto iobuf2 = folly::IOBuf::wrapBuffer(buffer, sizeof(buffer) - 1, folly::IOBuf::CopyOptions::noCopy());
    13 // iobuf2 引用 buffer 的内存,buffer 的生命周期需要保证
    14 return 0;
    15 }

    IOBuf::copyBuffer(const void* data, size_t size): 类似于 wrapBuffer,但始终复制数据到新的 IOBuf 中。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/io/IOBuf.h>
    2
    3 int main() {
    4 const char* data = "Copy me!";
    5 // 复制 data 到新的 IOBuf
    6 auto iobuf = folly::IOBuf::copyBuffer(data, strlen(data));
    7 return 0;
    8 }

    IOBuf::moveBuffer(std::unique_ptr<uint8_t[]> buffer, size_t size): 将一个 std::unique_ptr 管理的内存缓冲区的所有权转移给 IOBufIOBuf 将负责管理缓冲区的生命周期。这种方式避免了数据复制,并且可以有效地管理动态分配的内存。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/io/IOBuf.h>
    2 #include <memory>
    3
    4 int main() {
    5 // 分配一块内存
    6 std::unique_ptr<uint8_t[]> buffer(new uint8_t[256]);
    7 // ... 在 buffer 中写入数据 ...
    8 // 将 buffer 的所有权转移给 IOBuf
    9 auto iobuf = folly::IOBuf::moveBuffer(std::move(buffer), 256);
    10 // iobuf 现在拥有 buffer 的内存,当 iobuf 销毁时,buffer 的内存也会被释放
    11 return 0;
    12 }

    IOBuf::allocate(size_t size): 分配一个新的、可变的 IOBuf,并预留指定大小的容量,但初始长度为 0。与 create() 不同,allocate() 创建的 IOBuf 初始状态为空,需要后续写入数据。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/io/IOBuf.h>
    2
    3 int main() {
    4 // 分配一个容量为 512 字节的 IOBuf
    5 auto iobuf = folly::IOBuf::allocate(512);
    6 // 此时 iobuf->capacity() 为 512, iobuf->length() 为 0
    7 return 0;
    8 }

    IOBuf::chain(std::vector<std::unique_ptr<IOBuf>> children): 将多个 IOBuf 对象链接成一个链表。通常用于将分散的数据块组合成一个逻辑上的连续数据流。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/io/IOBuf.h>
    2 #include <vector>
    3 #include <memory>
    4
    5 int main() {
    6 auto iobuf1 = folly::IOBuf::copyBuffer("Part 1", 6);
    7 auto iobuf2 = folly::IOBuf::copyBuffer("Part 2", 6);
    8 std::vector<std::unique_ptr<folly::IOBuf>> iobufs;
    9 iobufs.push_back(std::move(iobuf1));
    10 iobufs.push_back(std::move(iobuf2));
    11 // 将 iobuf1 和 iobuf2 链接成一个链表
    12 auto chained_iobuf = folly::IOBuf::chain(std::move(iobufs));
    13 // chained_iobuf 现在是一个链表,包含 "Part 1" 和 "Part 2" 的数据
    14 return 0;
    15 }

    2.4.2 IOBuf 的数据访问与操作 (Data Access and Operations of IOBuf)

    创建 IOBuf 后,需要访问和操作 IOBuf 中存储的数据。IOBuf 提供了多种方法来实现数据访问和操作,包括直接指针访问、Cursor 类以及各种数据操作 API。

    直接指针访问: IOBuf 提供了 data()writableData() 方法来获取指向数据起始位置的指针。可以使用这些指针直接访问和修改 IOBuf 中的数据。注意: 直接指针访问需要谨慎使用,确保不会越界访问,并且在修改数据时要考虑 IOBuf 的可变性。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/io/IOBuf.h>
    2 #include <cstring>
    3
    4 int main() {
    5 auto iobuf = folly::IOBuf::create(32);
    6 // 获取可写数据指针
    7 uint8_t* data = iobuf->writableData();
    8 // 写入数据
    9 std::strcpy(reinterpret_cast<char*>(data), "Hello, pointer!");
    10 // 设置 IOBuf 的长度
    11 iobuf->append(std::strlen(reinterpret_cast<char*>(data)));
    12 // 获取只读数据指针
    13 const uint8_t* read_data = iobuf->data();
    14 // 打印数据
    15 printf("Data: %s\n", reinterpret_cast<const char*>(read_data));
    16 return 0;
    17 }

    Cursor: Cursor 类是 Folly/IO 提供的用于在 IOBuf 中高效读写数据的工具。Cursor 可以跟踪 IOBuf 中的当前位置,并提供各种读取和写入方法,例如 readBE<T>() (大端读取)、readLE<T>() (小端读取)、writeBE<T>() (大端写入)、writeLE<T>() (小端写入) 等。Cursor 可以处理跨越多个 IOBuf 节点的数据读取,简化了链式 IOBuf 的数据操作。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/io/IOBuf.h>
    2 #include <folly/io/Cursor.h>
    3
    4 int main() {
    5 auto iobuf = folly::IOBuf::create(16);
    6 folly::io::Cursor cursor(iobuf.get());
    7 // 写入一个 32 位大端整数
    8 cursor.writeBE<uint32_t>(0x12345678);
    9 // 写入一个字符串
    10 cursor.writeFixedString("Hello", 5);
    11
    12 // 重置 Cursor 到 IOBuf 起始位置
    13 cursor.reset();
    14 // 读取 32 位大端整数
    15 uint32_t value = cursor.readBE<uint32_t>();
    16 // 读取字符串
    17 std::string str = cursor.readFixedString(5).toString();
    18
    19 printf("Value: 0x%X, String: %s\n", value, str.c_str());
    20 return 0;
    21 }

    数据操作 API: IOBuf 提供了丰富的 API 来进行各种数据操作,例如:

    append(size_t len): 增加 IOBuf 的有效数据长度,通常在向 IOBuf 写入数据后调用。
    prepend(size_t len): 在 IOBuf 的头部预留空间,并增加有效数据长度,用于向前扩展数据。
    advance(size_t len): 向前移动数据起始位置指针,相当于丢弃 IOBuf 头部的数据。
    retreat(size_t len): 向后移动数据起始位置指针,相当于恢复之前丢弃的头部数据。
    trimStart(size_t len): 从 IOBuf 头部截断指定长度的数据。
    trimEnd(size_t len): 从 IOBuf 尾部截断指定长度的数据。
    copyDataTo(void* buffer, size_t offset, size_t len): 将 IOBuf 中的数据复制到指定的缓冲区。
    fill(uint8_t value, size_t offset, size_t len): 用指定的值填充 IOBuf 中的数据区域。

    这些 API 提供了灵活的数据操作能力,可以满足各种数据处理需求。

    2.4.3 IOBuf 的内存管理 API (Memory Management APIs of IOBuf)

    IOBuf 的内存管理 API 主要用于控制 IOBuf 的内存分配、释放和共享,以及进行内存相关的操作,例如复制、克隆等。

    clone(): 创建一个新的 IOBuf 对象,与原始 IOBuf 共享相同的底层数据缓冲区。引用计数会增加。克隆操作是轻量级的,因为它不涉及数据的物理复制。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/io/IOBuf.h>
    2
    3 int main() {
    4 auto iobuf1 = folly::IOBuf::copyBuffer("Original", 8);
    5 // 克隆 iobuf1
    6 auto iobuf2 = iobuf1->clone();
    7 // iobuf1 和 iobuf2 共享相同的底层数据缓冲区
    8 return 0;
    9 }

    copy(): 创建一个新的 IOBuf 对象,并将原始 IOBuf 中的数据复制到新的 IOBuf 中。复制操作会分配新的内存缓冲区,并将数据完全复制过去。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/io/IOBuf.h>
    2
    3 int main() {
    4 auto iobuf1 = folly::IOBuf::copyBuffer("Original", 8);
    5 // 复制 iobuf1
    6 auto iobuf2 = iobuf1->copy();
    7 // iobuf2 拥有 iobuf1 数据的独立副本
    8 return 0;
    9 }

    detachBuffer(): 分离 IOBuf 与其底层数据缓冲区的关联。调用 detachBuffer() 后,IOBuf 将不再拥有底层缓冲区的内存管理权,缓冲区的生命周期需要由外部管理。通常与 wrapBuffer(..., IOBuf::CopyOptions::noCopy()) 配合使用,用于将 IOBuf 对外部内存的引用权转移出去。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/io/IOBuf.h>
    2
    3 int main() {
    4 char buffer[] = "External Buffer";
    5 auto iobuf = folly::IOBuf::wrapBuffer(buffer, sizeof(buffer) - 1, folly::IOBuf::CopyOptions::noCopy());
    6 // 分离 IOBuf 与 buffer 的关联
    7 iobuf->detachBuffer();
    8 // 现在 buffer 的生命周期需要外部保证
    9 return 0;
    10 }

    isShared(): 检查 IOBuf 是否与其他 IOBuf 共享底层数据缓冲区。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/io/IOBuf.h>
    2
    3 int main() {
    4 auto iobuf1 = folly::IOBuf::create(16);
    5 auto iobuf2 = iobuf1->clone();
    6 auto iobuf3 = iobuf1->copy();
    7
    8 printf("iobuf1 shared: %d\n", iobuf1->isShared()); // 可能为 false,取决于具体实现
    9 printf("iobuf2 shared: %d\n", iobuf2->isShared()); // true,与 iobuf1 共享
    10 printf("iobuf3 shared: %d\n", iobuf3->isShared()); // false,独立副本
    11 return 0;
    12 }

    unique(): 检查 IOBuf 是否是底层数据缓冲区的唯一拥有者 (引用计数为 1)。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/io/IOBuf.h>
    2
    3 int main() {
    4 auto iobuf1 = folly::IOBuf::create(16);
    5 auto iobuf2 = iobuf1->clone();
    6
    7 printf("iobuf1 unique: %d\n", iobuf1->unique()); // false,因为 iobuf2 共享
    8 printf("iobuf2 unique: %d\n", iobuf2->unique()); // false,因为 iobuf1 共享
    9
    10 iobuf2.reset(); // 释放 iobuf2
    11 printf("iobuf1 unique: %d\n", iobuf1->unique()); // true,现在 iobuf1 是唯一拥有者
    12 return 0;
    13 }

    掌握这些常用的 IOBuf API,可以帮助开发者灵活地创建、访问、操作和管理 IOBuf 对象,从而构建高效的网络应用程序。在实际应用中,需要根据具体的场景选择合适的 API,并注意内存管理和数据安全。

    2.5 实战代码:使用 IOBuf 构建高效的数据处理管道 (Practical Code: Building Efficient Data Processing Pipelines with IOBuf)

    为了更好地理解 IOBuf 的应用,我们来看一个实战代码示例,演示如何使用 IOBuf 构建一个高效的数据处理管道 (data processing pipeline)。假设我们需要构建一个简单的日志处理管道,该管道从文件中读取日志数据,对日志进行过滤和转换,并将处理后的日志写入另一个文件。

    代码示例

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/io/IOBuf.h>
    2 #include <folly/io/Cursor.h>
    3 #include <folly/File.h>
    4 #include <folly/String.h>
    5 #include <iostream>
    6 #include <fstream>
    7
    8 using namespace folly;
    9 using namespace std;
    10
    11 // 日志过滤器:只保留包含 "ERROR" 关键词的日志
    12 bool logFilter(const std::string& logLine) {
    13 return logLine.find("ERROR") != std::string::npos;
    14 }
    15
    16 // 日志转换器:在日志行前添加时间戳
    17 std::string logTransformer(const std::string& logLine) {
    18 time_t now = time(0);
    19 tm* ltm = localtime(&now);
    20 char timestamp[20];
    21 sprintf(timestamp, "%04d-%02d-%02d %02d:%02d:%02d ", 1900 + ltm->tm_year, 1 + ltm->tm_mon, ltm->tm_mday, ltm->tm_hour, ltm->tm_min, ltm->tm_sec);
    22 return std::string(timestamp) + logLine;
    23 }
    24
    25 int main() {
    26 const char* inputFilePath = "input.log";
    27 const char* outputFilePath = "output.log";
    28
    29 // 1. 读取日志文件到 IOBuf
    30 File inputFile(inputFilePath, O_RDONLY);
    31 if (!inputFile.isValid()) {
    32 cerr << "Failed to open input file: " << inputFilePath << endl;
    33 return 1;
    34 }
    35 IOBufQueue inputQueue;
    36 size_t fileSize = inputFile.size();
    37 ssize_t bytesRead = inputQueue.appendFromFile(inputFile, fileSize);
    38 if (bytesRead != fileSize) {
    39 cerr << "Failed to read input file completely." << endl;
    40 return 1;
    41 }
    42 inputFile.close();
    43 cout << "Read " << bytesRead << " bytes from input file." << endl;
    44
    45 // 2. 处理 IOBufQueue 中的日志数据
    46 IOBufQueue outputQueue;
    47 Cursor cursor(inputQueue.front()); // 使用 Cursor 遍历 IOBufQueue
    48 while (!cursor.isAtEnd()) {
    49 auto lineBuf = cursor.nextLine(); // 逐行读取
    50 if (lineBuf) {
    51 std::string logLine = lineBuf->moveToFbString().toStdString(); // IOBuf to std::string
    52 if (logFilter(logLine)) { // 应用过滤器
    53 std::string transformedLine = logTransformer(logLine); // 应用转换器
    54 outputQueue.append(IOBuf::copyBuffer(transformedLine)); // 转换后的日志写入 outputQueue
    55 }
    56 } else {
    57 break; // 读取完毕
    58 }
    59 }
    60 cout << "Processed log data, filtered and transformed." << endl;
    61
    62 // 3. 将处理后的日志从 IOBufQueue 写入输出文件
    63 ofstream outputFile(outputFilePath, ofstream::binary);
    64 if (!outputFile.is_open()) {
    65 cerr << "Failed to open output file: " << outputFilePath << endl;
    66 return 1;
    67 }
    68 outputQueue.appendToFileDescriptor(outputFile.rdbuf()->fd()); // IOBufQueue to file descriptor
    69 outputFile.close();
    70 cout << "Wrote processed log data to output file." << endl;
    71
    72 return 0;
    73 }

    代码说明

    1. 读取日志文件: 使用 folly::File 打开输入日志文件,并使用 IOBufQueue::appendFromFile() 将文件内容高效地读取到 IOBufQueue 中。IOBufQueue 可以有效地管理从文件读取的数据,并支持分段数据的处理。
    2. 处理日志数据: 使用 folly::io::Cursor 遍历 IOBufQueue 中的数据。Cursor::nextLine() 方法可以逐行读取日志数据,并返回一个 IOBuf 指针,指向包含一行日志的 IOBuf。将 IOBuf 转换为 std::string 后,应用日志过滤器 logFilter() 和日志转换器 logTransformer()。如果日志行通过过滤器,则将转换后的日志行写入到 outputQueue 中。
    3. 写入输出文件: 使用 IOBufQueue::appendToFileDescriptor()outputQueue 中的数据高效地写入到输出日志文件中。appendToFileDescriptor() 方法可以直接将 IOBufQueue 中的数据写入文件描述符,避免了不必要的数据拷贝。

    代码亮点

    高效的文件 I/O: 使用 folly::FileIOBufQueue::appendFromFile() 以及 IOBufQueue::appendToFileDescriptor() 实现高效的文件读写操作,减少了数据拷贝。
    零拷贝的数据处理: 在整个数据处理管道中,尽可能地使用了 IOBuf 的零拷贝特性。例如,从文件读取的数据直接存储在 IOBufQueue 中,日志行的分割和转换操作也尽量避免了不必要的数据复制。
    Cursor 的高效遍历: 使用 Cursor 类遍历 IOBufQueue,可以高效地读取和操作 IOBuf 中的数据,特别是处理链式 IOBuf 时,Cursor 的优势更加明显。
    IOBufQueue 的分片处理: IOBufQueue 可以有效地缓存和管理分片的数据,使得数据处理管道可以更好地适应网络数据流或分段文件数据。

    通过这个实战代码示例,我们可以看到 IOBuf 及其相关工具 (如 CursorIOBufQueue) 在构建高效数据处理管道中的作用。IOBuf 的零拷贝特性、链式结构以及丰富的 API,使得开发者可以构建高性能、低延迟的数据处理应用。在实际的网络编程和数据处理场景中,合理地利用 IOBuf,可以显著提升应用的性能和效率。

    END_OF_CHAPTER

    3. chapter 3: 异步 Socket 编程 (Asynchronous Socket Programming)

    3.1 异步编程模型与 EventBase (Asynchronous Programming Model and EventBase)

    在深入探索 Folly/IO 的异步 Socket 编程之前,理解异步编程模型 (Asynchronous Programming Model) 的基本概念至关重要。传统的同步编程 (Synchronous Programming) 模式中,操作是顺序执行的,每个操作都会阻塞线程,直到操作完成。这种模式在处理高并发和 IO 密集型任务时效率低下,因为线程在等待 IO 操作完成时会被白白浪费掉。

    异步编程 则是一种非阻塞的并发编程模式。在异步编程中,当发起一个耗时的操作(例如网络 IO)时,程序不会阻塞等待操作完成,而是立即返回并继续执行后续的任务。当操作完成时,系统会通知程序,程序再通过回调 (Callback) 或者 Promise/Future 等机制来处理操作的结果。这种模式可以充分利用 CPU 资源,提高程序的并发性和响应速度,尤其适合处理网络编程中的高并发连接和数据传输。

    Folly/IO 异步编程的核心是 EventBaseEventBase 可以被看作是一个事件循环管理器,它负责监听各种事件(例如 Socket 可读、可写事件,定时器事件等),并在事件发生时,调度相应的回调函数 (Callback Function) 来处理这些事件。

    EventBase 的主要职责
    ▮▮▮▮ⓑ 事件监听 (Event Monitoring)EventBase 负责监听文件描述符 (File Descriptor, FD) 上的各种事件,例如读事件、写事件、异常事件等。在网络编程中,这些文件描述符通常是 Socket。EventBase 底层通常使用 epoll (Linux)、kqueue (macOS, FreeBSD)、iocp (Windows) 等高效的 IO 多路复用机制来实现事件监听。
    ▮▮▮▮ⓒ 事件调度 (Event Dispatching):当被监听的文件描述符上发生事件时,EventBase 会负责调度与该事件关联的回调函数。这意味着当 Socket 变为可读时,EventBase 会调用预先注册的读回调函数;当 Socket 变为可写时,EventBase 会调用写回调函数。
    ▮▮▮▮ⓓ 定时器管理 (Timer Management)EventBase 还提供了定时器功能。可以向 EventBase 注册定时器事件,并指定定时器到期后需要执行的回调函数。这对于实现超时机制、周期性任务等非常有用。
    ▮▮▮▮ⓔ 任务队列 (Task Queue)EventBase 维护一个任务队列,允许在 EventBase 线程中执行任意的任务。可以使用 EventBase::runInLoopEventBase::runImmediatelyOrRunInLoop 等方法向任务队列提交任务。这对于线程安全地访问和操作 EventBase 相关的对象非常重要。

    EventBase 的工作原理
    ⚝ 初始化:创建一个 EventBase 对象,并选择合适的 IO 多路复用机制(通常由 Folly/IO 自动选择最佳机制)。
    ⚝ 事件注册:将需要监听事件的文件描述符以及对应的回调函数注册到 EventBase 中。例如,对于一个 AsyncSocket,需要注册读回调、写回调、连接回调、关闭回调等。
    ⚝ 事件循环:EventBase 进入事件循环 (Event Loop)。在循环中,EventBase 监听所有注册的文件描述符上的事件,并等待事件的发生。
    ⚝ 事件处理:当某个文件描述符上发生事件时,EventBase 找到与该事件关联的回调函数,并在 EventBase 线程中执行该回调函数。
    ⚝ 循环往复:处理完事件后,EventBase 再次回到事件循环,继续监听新的事件。

    EventBase 的优势
    ▮▮▮▮⚝ 高效的 IO 多路复用EventBase 底层使用高效的 IO 多路复用机制,例如 epollkqueueiocp,可以同时监听大量的文件描述符,而不会随着文件描述符数量的增加而线性降低性能。
    ▮▮▮▮⚝ 单线程异步EventBase 通常运行在单线程中,避免了多线程编程中的锁竞争和上下文切换开销,提高了程序的性能和效率。
    ▮▮▮▮⚝ 易于使用:Folly/IO 提供了易于使用的 AsyncSocketAsyncServerSocket 等 API,基于 EventBase 实现了异步网络编程,简化了异步编程的复杂性。

    理解 EventBase 是理解 Folly/IO 异步网络编程的关键。后续章节中,我们将看到 AsyncSocketAsyncServerSocket 等组件都是构建在 EventBase 之上的,利用 EventBase 提供的事件循环和回调机制来实现高效的异步 IO 操作。

    3.2 AsyncSocket:异步 TCP Socket API (AsyncSocket: Asynchronous TCP Socket API)

    AsyncSocket 是 Folly/IO 提供的用于客户端 TCP 连接的异步 Socket API。它基于 EventBase 构建,提供了非阻塞的、事件驱动的网络编程接口,使得开发者可以构建高性能的 TCP 客户端应用。AsyncSocket 封装了底层的 Socket 操作,并提供了简洁的回调接口,使得异步 TCP 编程更加容易和高效。

    AsyncSocket 的核心特性
    ▮▮▮▮ⓑ 异步连接 (Asynchronous Connection)AsyncSocket 的连接操作是非阻塞的。当调用 connect() 方法发起连接时,函数会立即返回,而实际的连接操作会在后台异步进行。连接成功或失败会通过回调函数通知应用程序。
    ▮▮▮▮ⓒ 异步发送和接收 (Asynchronous Send and Receive)AsyncSocket 的发送 (write()) 和接收 (read()) 操作也是异步的。当调用 write() 发送数据时,数据会被写入到发送缓冲区,并立即返回。实际的数据发送会由 EventBase 调度,并在 Socket 可写时异步完成。同样,read() 操作也是异步的,当 Socket 变为可读时,EventBase 会调用读回调函数来处理接收到的数据。
    ▮▮▮▮ⓓ 事件驱动 (Event-Driven)AsyncSocket 的所有操作都是事件驱动的。例如,连接成功事件、数据可读事件、数据可写事件、连接关闭事件、错误事件等都会触发相应的回调函数。应用程序只需要关注这些事件,并在回调函数中处理相应的逻辑。
    ▮▮▮▮ⓔ 零拷贝支持 (Zero-Copy Support)AsyncSocketIOBuf 紧密集成,可以利用 IOBuf 的零拷贝特性,减少数据拷贝开销,提高网络传输效率。

    AsyncSocket 的常用 API
    构造函数 (Constructor)

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 AsyncSocket::AsyncSocket(EventBase* evb);

    ▮▮▮▮⚝ 创建一个 AsyncSocket 对象,需要传入一个 EventBase 指针,用于将 AsyncSocket 绑定到指定的 EventBase 上。所有的事件都将由这个 EventBase 实例进行调度和处理。

    连接 (connect)

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 void connect(
    2 std::unique_ptr<AsyncSocket::ConnectCallback> callback,
    3 const folly::SocketAddress& address,
    4 std::chrono::milliseconds timeout = std::chrono::milliseconds(0),
    5 const folly::SocketAddress& bindAddress = folly::SocketAddress());

    ▮▮▮▮⚝ 发起异步连接。
    ▮▮▮▮⚝ callback: 一个 AsyncSocket::ConnectCallback 接口的实现类,用于接收连接结果的回调。连接成功时会调用 connectSuccess() 方法,连接失败时会调用 connectErr() 方法。
    ▮▮▮▮⚝ address: 目标服务器的 SocketAddress,包括 IP 地址和端口号。
    ▮▮▮▮⚝ timeout: 连接超时时间,默认为 0 表示永不超时。
    ▮▮▮▮⚝ bindAddress: 可选的本地绑定地址。

    写入数据 (write)

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 void write(
    2 std::unique_ptr<WriteCallback> callback,
    3 std::shared_ptr<IOBuf> buf,
    4 WriteFlags flags = WriteFlags::NONE);

    ▮▮▮▮⚝ 异步发送数据。
    ▮▮▮▮⚝ callback: 一个 AsyncSocket::WriteCallback 接口的实现类,用于接收写操作完成的回调。数据发送成功后会调用 writeSuccess() 方法,发送失败会调用 writeErr() 方法。
    ▮▮▮▮⚝ buf: 要发送的数据,封装在 IOBuf 中。
    ▮▮▮▮⚝ flags: 写操作标志,例如 WriteFlags::NONEWriteFlags::CORK

    读取数据 (read)

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 void setReadCB(ReadCallback* callback);

    ▮▮▮▮⚝ 设置读回调函数。当 Socket 变为可读时,EventBase 会调用 ReadCallback 接口的实现类中的方法来处理接收到的数据。
    ▮▮▮▮⚝ callback: 一个 AsyncSocket::ReadCallback 接口的实现类。ReadCallback 接口通常包含 onDataAvailable()onReadErr() 方法。

    关闭连接 (close)

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 void close();
    2 void close(int error);

    ▮▮▮▮⚝ 关闭连接。第一个 close() 方法是正常关闭连接,第二个 close(int error) 方法可以指定一个错误码来关闭连接,通常用于表示连接异常关闭。

    设置关闭回调 (setCloseCallback)

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 void setCloseCallback(CloseCallback* callback);

    ▮▮▮▮⚝ 设置关闭回调函数。当连接被关闭时(无论是本地关闭还是远端关闭),EventBase 会调用 CloseCallback 接口实现类中的 on ক্লোজ() 方法。
    ▮▮▮▮⚝ callback: 一个 AsyncSocket::CloseCallback 接口的实现类。

    设置错误回调 (setErrorCallback)

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 void setErrorCallback(ErrorCallback* callback);

    ▮▮▮▮⚝ 设置错误回调函数。当 AsyncSocket 发生错误时,例如连接错误、读写错误等,EventBase 会调用 ErrorCallback 接口实现类中的 onError() 方法。
    ▮▮▮▮⚝ callback: 一个 AsyncSocket::ErrorCallback 接口的实现类。

    AsyncSocket 的使用示例

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/io/async/AsyncSocket.h>
    2 #include <folly/io/async/EventBase.h>
    3 #include <folly/io/IOBuf.h>
    4 #include <folly/SocketAddress.h>
    5 #include <iostream>
    6
    7 using namespace folly;
    8 using namespace std;
    9
    10 class MyConnectCallback : public AsyncSocket::ConnectCallback {
    11 public:
    12 void connectSuccess(AsyncSocket* socket) noexcept override {
    13 cout << "Connected to server!" << endl;
    14 socket_ = socket;
    15 // 连接成功后,设置读回调
    16 socket_->setReadCB(new MyReadCallback(socket_));
    17 }
    18 void connectErr(AsyncSocket* socket, const exception& ex) noexcept override {
    19 cerr << "Connection error: " << ex.what() << endl;
    20 delete socket;
    21 }
    22 AsyncSocket* getSocket() const { return socket_; }
    23 private:
    24 AsyncSocket* socket_ = nullptr;
    25 };
    26
    27 class MyWriteCallback : public AsyncSocket::WriteCallback {
    28 public:
    29 void writeSuccess() noexcept override {
    30 cout << "Data sent successfully!" << endl;
    31 }
    32 void writeErr(size_t bytesWritten, const exception& ex) noexcept override {
    33 cerr << "Write error: " << ex.what() << endl;
    34 }
    35 };
    36
    37 class MyReadCallback : public AsyncSocket::ReadCallback {
    38 public:
    39 MyReadCallback(AsyncSocket* sock) : socket_(sock) {}
    40 void onDataAvailable(AsyncSocket* socket) noexcept override {
    41 // 读取数据
    42 unique_ptr<IOBuf> buf;
    43 while ((buf = socket->recv()) != nullptr) {
    44 cout << "Received data: " << string((const char*)buf->data(), buf->length()) << endl;
    45 }
    46 }
    47 void onReadErr(AsyncSocket* socket, const exception& ex) noexcept override {
    48 cerr << "Read error: " << ex.what() << endl;
    49 delete this; // 重要:在回调函数中管理自身的生命周期
    50 }
    51 void readEOF(AsyncSocket* socket) noexcept override {
    52 cout << "Server closed connection." << endl;
    53 delete this; // 重要:在回调函数中管理自身的生命周期
    54 socket->close();
    55 }
    56 private:
    57 AsyncSocket* socket_;
    58 };
    59
    60
    61 int main() {
    62 EventBase evb;
    63 AsyncSocket* sock = new AsyncSocket(&evb);
    64 MyConnectCallback* connectCallback = new MyConnectCallback();
    65 sock->connect(unique_ptr<MyConnectCallback>(connectCallback), SocketAddress::fromHostPort("127.0.0.1", 8080));
    66
    67 // 连接成功后,发送数据
    68 evb.runInLoop([&]() {
    69 if (connectCallback->getSocket() != nullptr) {
    70 AsyncSocket* connectedSocket = connectCallback->getSocket();
    71 string message = "Hello from client!";
    72 unique_ptr<IOBuf> writeBuf = IOBuf::copyBuffer(message);
    73 connectedSocket->write(unique_ptr<MyWriteCallback>(new MyWriteCallback()), std::move(writeBuf));
    74 }
    75 }, std::chrono::seconds(1)); // 延迟 1 秒后发送数据
    76
    77 evb.loopForever();
    78 return 0;
    79 }

    代码解释
    ⚝ 创建 EventBase 实例 evb
    ⚝ 创建 AsyncSocket 实例 sock,并关联到 evb
    ⚝ 创建 MyConnectCallback 实例 connectCallback,用于处理连接结果的回调。
    ⚝ 调用 sock->connect() 发起异步连接到 127.0.0.1:8080
    ⚝ 使用 evb.runInLoop() 在事件循环中延迟 1 秒后执行发送数据的操作。
    ⚝ 在连接成功的回调 connectSuccess() 中,设置读回调 MyReadCallback
    ⚝ 在 MyReadCallbackonDataAvailable() 方法中,读取并打印接收到的数据。
    ⚝ 进入 evb.loopForever() 事件循环,等待事件发生并处理。

    这个示例展示了 AsyncSocket 的基本用法,包括异步连接、发送数据、接收数据和事件回调处理。在实际应用中,需要根据具体的业务逻辑实现更完善的回调函数和错误处理机制。

    3.3 AsyncServerSocket:异步 TCP 服务端 Socket API (AsyncServerSocket: Asynchronous TCP Server Socket API)

    AsyncServerSocket 是 Folly/IO 提供的用于服务端 TCP 监听的异步 Socket API。它同样基于 EventBase 构建,用于创建异步的 TCP 服务端,可以高效地处理大量的并发连接。AsyncServerSocket 负责监听指定的端口,并在有新的客户端连接请求到达时,异步地接受连接,并创建新的 AsyncSocket 对象来处理客户端连接。

    AsyncServerSocket 的核心特性
    ▮▮▮▮ⓑ 异步监听 (Asynchronous Listening)AsyncServerSocket 的监听操作是非阻塞的。调用 bind()listen() 方法后,服务端开始异步监听指定的端口。
    ▮▮▮▮ⓒ 异步接受连接 (Asynchronous Accept):当有新的客户端连接请求到达时,AsyncServerSocket 会异步地接受连接,并创建一个新的 AsyncSocket 对象来代表这个客户端连接。接受连接的过程是非阻塞的,不会阻塞服务端的事件循环。
    ▮▮▮▮ⓓ 连接回调 (Connection Callback):当 AsyncServerSocket 成功接受一个新的客户端连接时,会通过连接回调函数 (Connection Callback Function) 通知应用程序。应用程序需要在连接回调函数中处理新的客户端连接,例如设置读写回调,开始数据交互等。
    ▮▮▮▮ⓔ 高并发处理 (High Concurrency Handling)AsyncServerSocket 基于 EventBase 的异步事件驱动模型,可以高效地处理大量的并发连接,适用于构建高性能的 TCP 服务端应用。

    AsyncServerSocket 的常用 API
    构造函数 (Constructor)

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 AsyncServerSocket::AsyncServerSocket(EventBase* evb);

    ▮▮▮▮⚝ 创建一个 AsyncServerSocket 对象,需要传入一个 EventBase 指针,用于将 AsyncServerSocket 绑定到指定的 EventBase 上。

    绑定地址 (bind)

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 void bind(const folly::SocketAddress& address);

    ▮▮▮▮⚝ 绑定服务端监听的地址和端口。
    ▮▮▮▮⚝ address: 服务端的 SocketAddress,指定监听的 IP 地址和端口号。例如,SocketAddress::fromHostPort("0.0.0.0", 8080) 表示监听所有网卡的 8080 端口。

    开始监听 (listen)

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 void listen(int backlog = 128);

    ▮▮▮▮⚝ 开始监听绑定的地址和端口。
    ▮▮▮▮⚝ backlog: 监听队列的最大长度,用于控制等待接受的连接数。

    设置连接回调 (setConnectionAcceptCallback)

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 void setConnectionAcceptCallback(AcceptCallback* callback);

    ▮▮▮▮⚝ 设置连接接受回调函数。当 AsyncServerSocket 接受到一个新的客户端连接时,EventBase 会调用 AcceptCallback 接口实现类中的 connectionAccepted() 方法。
    ▮▮▮▮⚝ callback: 一个 AsyncServerSocket::AcceptCallback 接口的实现类。AcceptCallback 接口通常包含 connectionAccepted() 方法,用于处理新接受的连接。

    关闭监听 (close)

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 void close();

    ▮▮▮▮⚝ 关闭服务端的监听 Socket。

    AsyncServerSocket 的使用示例

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/io/async/AsyncServerSocket.h>
    2 #include <folly/io/async/EventBase.h>
    3 #include <folly/io/async/AsyncSocket.h>
    4 #include <folly/io/IOBuf.h>
    5 #include <folly/SocketAddress.h>
    6 #include <iostream>
    7
    8 using namespace folly;
    9 using namespace std;
    10
    11 class MyAcceptCallback : public AsyncServerSocket::AcceptCallback {
    12 public:
    13 EventBase* getEventBase() const { return evb_; }
    14 void setEventBase(EventBase* evb) { evb_ = evb; }
    15 void connectionAccepted(
    16 std::unique_ptr<AsyncSocket> sock,
    17 const folly::SocketAddress& peerAddress) noexcept override {
    18 cout << "Accepted connection from " << peerAddress.getAddressStr() << ":" << peerAddress.getPort() << endl;
    19 // 为新的连接设置读回调
    20 sock->setReadCB(new MyServerReadCallback(sock.get()));
    21 // 保存 socket,防止析构
    22 sockets_.push_back(std::move(sock));
    23 }
    24
    25 private:
    26 EventBase* evb_;
    27 vector<unique_ptr<AsyncSocket>> sockets_; // 保存 socket
    28 };
    29
    30
    31 class MyServerReadCallback : public AsyncSocket::ReadCallback {
    32 public:
    33 MyServerReadCallback(AsyncSocket* sock) : socket_(sock) {}
    34 void onDataAvailable(AsyncSocket* socket) noexcept override {
    35 unique_ptr<IOBuf> buf;
    36 while ((buf = socket->recv()) != nullptr) {
    37 cout << "Received from client: " << string((const char*)buf->data(), buf->length()) << endl;
    38 // Echo back to client
    39 socket->write(nullptr, std::move(buf)); // 使用 nullptr 作为 WriteCallback,表示不需要回调
    40 }
    41 }
    42 void onReadErr(AsyncSocket* socket, const exception& ex) noexcept override {
    43 cerr << "Read error from client: " << ex.what() << endl;
    44 delete this;
    45 }
    46 void readEOF(AsyncSocket* socket) noexcept override {
    47 cout << "Client closed connection." << endl;
    48 delete this;
    49 socket->close();
    50 }
    51
    52 private:
    53 AsyncSocket* socket_;
    54 };
    55
    56
    57 int main() {
    58 EventBase evb;
    59 AsyncServerSocket serverSocket(&evb);
    60 MyAcceptCallback* acceptCallback = new MyAcceptCallback();
    61 acceptCallback->setEventBase(&evb);
    62 serverSocket.setConnectionAcceptCallback(acceptCallback);
    63
    64 serverSocket.bind(SocketAddress::fromHostPort("0.0.0.0", 8080));
    65 serverSocket.listen();
    66
    67 cout << "Server listening on port 8080..." << endl;
    68 evb.loopForever();
    69 return 0;
    70 }

    代码解释
    ⚝ 创建 EventBase 实例 evb
    ⚝ 创建 AsyncServerSocket 实例 serverSocket,并关联到 evb
    ⚝ 创建 MyAcceptCallback 实例 acceptCallback,用于处理新的连接请求。
    ⚝ 调用 serverSocket.setConnectionAcceptCallback() 设置连接回调。
    ⚝ 调用 serverSocket.bind() 绑定监听地址为 0.0.0.0:8080
    ⚝ 调用 serverSocket.listen() 开始监听。
    ⚝ 在 MyAcceptCallbackconnectionAccepted() 方法中,当有新的连接到达时,创建 MyServerReadCallback 实例作为新连接的读回调,并保存 AsyncSocket 对象。
    ⚝ 在 MyServerReadCallbackonDataAvailable() 方法中,读取客户端发送的数据,并回显给客户端。
    ⚝ 进入 evb.loopForever() 事件循环,等待事件发生并处理。

    这个示例展示了 AsyncServerSocket 的基本用法,包括绑定地址、开始监听、接受连接和处理客户端数据。通过 AsyncServerSocketAsyncSocket 的配合使用,可以构建一个简单的异步 TCP 服务端。

    3.4 UDP Socket 与 Unix Domain Socket 支持 (UDP Socket and Unix Domain Socket Support)

    除了 TCP Socket,Folly/IO 的 folly/io/async 库也提供了对 UDP Socket (User Datagram Protocol Socket)Unix Domain Socket 的支持。虽然本章主要关注 TCP 异步 Socket 编程,但了解 Folly/IO 对 UDP 和 Unix Domain Socket 的支持也是很有价值的。

    UDP Socket 支持
    Folly/IO 提供了 AsyncUDPSocket 类,用于进行异步 UDP 通信。AsyncUDPSocket 的使用方式与 AsyncSocket 类似,也是基于 EventBase 的异步事件驱动模型。

    关键类: folly/io/async/AsyncUDPSocket.h
    核心特性:
    ▮▮▮▮⚝ 无连接 (Connectionless): UDP 是无连接的协议,AsyncUDPSocket 不需要像 AsyncSocket 那样进行连接操作。
    ▮▮▮▮⚝ 异步发送和接收: AsyncUDPSocket 提供了异步的 send()recv() 方法,以及相应的回调机制。
    ▮▮▮▮⚝ 广播和组播 (Broadcast and Multicast): AsyncUDPSocket 支持 UDP 广播和组播功能。

    常用 API:
    ▮▮▮▮⚝ AsyncUDPSocket(EventBase* evb): 构造函数。
    ▮▮▮▮⚝ bind(const SocketAddress& address): 绑定本地地址和端口。
    ▮▮▮▮⚝ send(const SocketAddress& address, std::unique_ptr<IOBuf> buf, SendCallback* callback = nullptr): 异步发送 UDP 数据报到指定地址。
    ▮▮▮▮⚝ recv(ReadCallback* callback): 设置接收回调函数,异步接收 UDP 数据报。

    Unix Domain Socket 支持
    Folly/IO 也支持 Unix Domain Socket (UDS),也称为 IPC Socket (Inter-Process Communication Socket)。Unix Domain Socket 允许在同一台主机上的不同进程之间进行高效的通信,而无需经过网络协议栈。Folly/IO 提供了 AsyncServerSocketAsyncSocket 来支持 Unix Domain Socket。

    关键特性:
    ▮▮▮▮⚝ 本地进程通信 (Local Process Communication): Unix Domain Socket 主要用于同一主机上的进程间通信。
    ▮▮▮▮⚝ 高效性: UDS 通信效率通常比 TCP loopback 高,因为它避免了网络协议栈的开销。
    ▮▮▮▮⚝ 安全性: UDS 可以使用文件系统权限进行访问控制。

    使用方法:
    ▮▮▮▮⚝ 使用 folly::SocketAddress::fromPath() 创建 Unix Domain Socket 地址。
    ▮▮▮▮⚝ AsyncServerSocketAsyncSocket 的 API 与 TCP Socket 基本相同,只需在 bind()connect() 方法中使用 UDS 地址即可。

    示例 (Unix Domain Socket Server):

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/io/async/AsyncServerSocket.h>
    2 #include <folly/io/async/EventBase.h>
    3 #include <folly/io/async/AsyncSocket.h>
    4 #include <folly/io/IOBuf.h>
    5 #include <folly/SocketAddress.h>
    6 #include <iostream>
    7 #include <string>
    8 #include <unistd.h> // for unlink
    9
    10 using namespace folly;
    11 using namespace std;
    12
    13 const string udsPath = "/tmp/folly_uds_example.sock";
    14
    15 class UDSAcceptCallback : public AsyncServerSocket::AcceptCallback {
    16 public:
    17 void connectionAccepted(
    18 std::unique_ptr<AsyncSocket> sock,
    19 const folly::SocketAddress& peerAddress) noexcept override {
    20 cout << "Accepted UDS connection" << endl;
    21 sock->setReadCB(new UDSServerReadCallback(sock.get()));
    22 sockets_.push_back(std::move(sock));
    23 }
    24 private:
    25 vector<unique_ptr<AsyncSocket>> sockets_;
    26 };
    27
    28 class UDSServerReadCallback : public AsyncSocket::ReadCallback {
    29 public:
    30 UDSServerReadCallback(AsyncSocket* sock) : socket_(sock) {}
    31 void onDataAvailable(AsyncSocket* socket) noexcept override {
    32 unique_ptr<IOBuf> buf;
    33 while ((buf = socket->recv()) != nullptr) {
    34 cout << "Received from UDS client: " << string((const char*)buf->data(), buf->length()) << endl;
    35 socket->write(nullptr, std::move(buf));
    36 }
    37 }
    38 void readEOF(AsyncSocket* socket) noexcept override {
    39 cout << "UDS client closed connection." << endl;
    40 delete this;
    41 socket->close();
    42 }
    43 void onReadErr(AsyncSocket* socket, const exception& ex) noexcept override {
    44 cerr << "UDS read error: " << ex.what() << endl;
    45 delete this;
    46 }
    47 private:
    48 AsyncSocket* socket_;
    49 };
    50
    51
    52 int main() {
    53 EventBase evb;
    54 AsyncServerSocket serverSocket(&evb);
    55 UDSAcceptCallback* acceptCallback = new UDSAcceptCallback();
    56 serverSocket.setConnectionAcceptCallback(acceptCallback);
    57
    58 // Unlink existing socket file if it exists
    59 unlink(udsPath.c_str());
    60
    61 serverSocket.bind(SocketAddress::fromPath(udsPath));
    62 serverSocket.listen();
    63
    64 cout << "UDS Server listening on path: " << udsPath << endl;
    65 evb.loopForever();
    66 return 0;
    67 }

    示例 (Unix Domain Socket Client):

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/io/async/AsyncSocket.h>
    2 #include <folly/io/async/EventBase.h>
    3 #include <folly/io/IOBuf.h>
    4 #include <folly/SocketAddress.h>
    5 #include <iostream>
    6
    7 using namespace folly;
    8 using namespace std;
    9
    10 const string udsPath = "/tmp/folly_uds_example.sock";
    11
    12 class UDSConnectCallback : public AsyncSocket::ConnectCallback {
    13 public:
    14 void connectSuccess(AsyncSocket* socket) noexcept override {
    15 cout << "Connected to UDS server!" << endl;
    16 socket->setReadCB(new UDSClientReadCallback());
    17 string message = "Hello from UDS client!";
    18 socket->write(nullptr, IOBuf::copyBuffer(message));
    19 }
    20 void connectErr(AsyncSocket* socket, const exception& ex) noexcept override {
    21 cerr << "UDS connection error: " << ex.what() << endl;
    22 delete socket;
    23 }
    24 };
    25
    26 class UDSClientReadCallback : public AsyncSocket::ReadCallback {
    27 public:
    28 void onDataAvailable(AsyncSocket* socket) noexcept override {
    29 unique_ptr<IOBuf> buf;
    30 while ((buf = socket->recv()) != nullptr) {
    31 cout << "Received from UDS server: " << string((const char*)buf->data(), buf->length()) << endl;
    32 }
    33 }
    34 void readEOF(AsyncSocket* socket) noexcept override {
    35 cout << "UDS server closed connection." << endl;
    36 delete this;
    37 socket->close();
    38 }
    39 void onReadErr(AsyncSocket* socket, const exception& ex) noexcept override {
    40 cerr << "UDS read error: " << ex.what() << endl;
    41 delete this;
    42 }
    43 };
    44
    45
    46 int main() {
    47 EventBase evb;
    48 AsyncSocket* sock = new AsyncSocket(&evb);
    49 UDSConnectCallback* connectCallback = new UDSConnectCallback();
    50 sock->connect(unique_ptr<UDSConnectCallback>(connectCallback), SocketAddress::fromPath(udsPath));
    51
    52 evb.loopForever();
    53 return 0;
    54 }

    这两个示例展示了如何使用 AsyncServerSocketAsyncSocket 进行 Unix Domain Socket 通信。UDP Socket 的使用方式也类似,只需使用 AsyncUDPSocket 类和 UDP Socket 地址即可。

    3.5 AsyncSocket 的事件回调机制 (Event Callback Mechanism of AsyncSocket)

    AsyncSocket 的核心在于其事件回调机制 (Event Callback Mechanism)。这种机制使得应用程序能够以非阻塞的方式处理 Socket 事件,例如连接建立、数据接收、数据发送、连接关闭和错误发生等。理解 AsyncSocket 的事件回调机制是高效使用 Folly/IO 进行异步网络编程的关键。

    AsyncSocket 通过一系列回调接口 (Callback Interface) 来通知应用程序各种 Socket 事件。应用程序需要实现这些回调接口,并在回调函数中编写相应的事件处理逻辑。

    AsyncSocket 的主要回调接口

    ConnectCallback: 用于处理连接事件。
    ▮▮▮▮⚝ connectSuccess(AsyncSocket* socket) noexcept: 连接成功时调用。参数 socket 是已连接的 AsyncSocket 对象。
    ▮▮▮▮⚝ connectErr(AsyncSocket* socket, const exception& ex) noexcept: 连接失败时调用。参数 ex 包含了连接失败的异常信息。

    ReadCallback: 用于处理读事件。
    ▮▮▮▮⚝ onDataAvailable(AsyncSocket* socket) noexcept: 当 Socket 变为可读,即有数据到达时调用。应用程序需要在该回调函数中调用 socket->recv() 来读取数据。
    ▮▮▮▮⚝ onReadErr(AsyncSocket* socket, const exception& ex) noexcept: 读取数据发生错误时调用。参数 ex 包含了读取错误的异常信息。
    ▮▮▮▮⚝ readEOF(AsyncSocket* socket) noexcept: 当远端关闭连接,即收到 EOF (End-of-File) 时调用。表示连接已关闭,不再有数据可读。

    WriteCallback: 用于处理写事件。
    ▮▮▮▮⚝ writeSuccess() noexcept: 数据发送成功时调用。表示之前调用 socket->write() 发送的数据已经成功写入到 Socket 发送缓冲区。
    ▮▮▮▮⚝ writeErr(size_t bytesWritten, const exception& ex) noexcept: 数据发送发生错误时调用。参数 bytesWritten 表示已经成功写入的字节数,ex 包含了写入错误的异常信息。

    CloseCallback: 用于处理连接关闭事件。
    ▮▮▮▮⚝ onClose(AsyncSocket* socket) noexcept: 当连接被关闭时调用,无论是本地主动关闭还是远端关闭都会触发此回调。

    ErrorCallback: 用于处理一般错误事件。
    ▮▮▮▮⚝ onError(AsyncSocket* socket, const exception& ex) noexcept: 当 AsyncSocket 发生一般性错误时调用,例如 Socket 创建失败、设置 Socket 选项失败等。

    回调函数的执行环境
    所有的回调函数(ConnectCallbackReadCallbackWriteCallbackCloseCallbackErrorCallback)都是在 EventBase 线程 中被调用的。这意味着在回调函数中可以直接安全地访问和操作与该 EventBase 关联的对象,例如 AsyncSocket 对象本身。但是,不应该在回调函数中执行耗时的阻塞操作,因为这会阻塞 EventBase 线程,影响事件循环的效率,降低程序的并发性能。如果需要在回调函数中执行耗时操作,应该将任务提交到线程池或者其他异步执行机制中处理。

    回调函数的生命周期管理
    通常情况下,回调对象(例如 MyReadCallbackMyWriteCallback 等)的生命周期需要由应用程序手动管理。在很多示例中,可以看到在回调函数(例如 onReadErr()readEOF())中使用 delete this; 来释放回调对象自身。这是因为 AsyncSocket 不会自动管理回调对象的生命周期,需要应用程序根据实际情况进行管理,以避免内存泄漏。一种常见的做法是在回调对象不再需要时,或者在连接关闭时,手动删除回调对象。

    事件处理流程示例 (Read Event)
    假设一个 AsyncSocket 已经设置了 ReadCallback。当远端发送数据到达时,Socket 变为可读,EventBase 会检测到这个读事件,并执行以下步骤:
    1. EventBase 调度 AsyncSocket 注册的 ReadCallbackonDataAvailable() 方法。
    2. 在 onDataAvailable() 方法中,应用程序调用 socket->recv() 从 Socket 接收缓冲区读取数据,并将数据处理后返回。
    3. onDataAvailable() 方法返回后,EventBase 继续事件循环,监听新的事件。

    如果 socket->recv() 成功读取到数据,onDataAvailable() 方法可能会被多次调用,直到 Socket 接收缓冲区中的数据被读取完。如果 socket->recv() 返回 nullptr,表示当前没有更多数据可读,onDataAvailable() 方法应该结束数据读取操作。

    理解 AsyncSocket 的事件回调机制,并正确实现和管理回调函数,是使用 Folly/IO 进行高效异步网络编程的关键。通过合理地利用回调函数,可以构建出响应迅速、性能优异的网络应用程序。

    3.6 实战代码:构建高性能 TCP 客户端与服务端 (Practical Code: Building High-Performance TCP Client and Server)

    本节将通过完整的实战代码示例,演示如何使用 Folly/IO 的 AsyncSocketAsyncServerSocket 构建高性能的 TCP 客户端和服务器。我们将构建一个简单的 Echo 服务 (Echo Service),客户端发送消息到服务端,服务端将消息原样返回给客户端。

    服务端代码 (Echo Server)

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/io/async/AsyncServerSocket.h>
    2 #include <folly/io/async/EventBase.h>
    3 #include <folly/io/async/AsyncSocket.h>
    4 #include <folly/io/IOBuf.h>
    5 #include <folly/SocketAddress.h>
    6 #include <iostream>
    7
    8 using namespace folly;
    9 using namespace std;
    10
    11 class EchoServerReadCallback : public AsyncSocket::ReadCallback {
    12 public:
    13 EchoServerReadCallback(AsyncSocket* sock) : socket_(sock) {}
    14 void onDataAvailable(AsyncSocket* socket) noexcept override {
    15 unique_ptr<IOBuf> buf;
    16 while ((buf = socket->recv()) != nullptr) {
    17 cout << "Server received: " << string((const char*)buf->data(), buf->length()) << endl;
    18 // Echo back to client
    19 socket->write(nullptr, std::move(buf));
    20 }
    21 }
    22 void readEOF(AsyncSocket* socket) noexcept override {
    23 cout << "Client closed connection." << endl;
    24 delete this;
    25 socket->close();
    26 }
    27 void onReadErr(AsyncSocket* socket, const exception& ex) noexcept override {
    28 cerr << "Read error: " << ex.what() << endl;
    29 delete this;
    30 }
    31 private:
    32 AsyncSocket* socket_;
    33 };
    34
    35 class EchoServerAcceptCallback : public AsyncServerSocket::AcceptCallback {
    36 public:
    37 void connectionAccepted(
    38 std::unique_ptr<AsyncSocket> sock,
    39 const folly::SocketAddress& peerAddress) noexcept override {
    40 cout << "Accepted connection from " << peerAddress.getAddressStr() << ":" << peerAddress.getPort() << endl;
    41 sock->setReadCB(new EchoServerReadCallback(sock.get()));
    42 sockets_.push_back(std::move(sock));
    43 }
    44 private:
    45 vector<unique_ptr<AsyncSocket>> sockets_;
    46 };
    47
    48
    49 int main() {
    50 EventBase evb;
    51 AsyncServerSocket serverSocket(&evb);
    52 EchoServerAcceptCallback* acceptCallback = new EchoServerAcceptCallback();
    53 serverSocket.setConnectionAcceptCallback(acceptCallback);
    54
    55 serverSocket.bind(SocketAddress::fromHostPort("0.0.0.0", 8080));
    56 serverSocket.listen();
    57
    58 cout << "Echo server listening on port 8080..." << endl;
    59 evb.loopForever();
    60 return 0;
    61 }

    客户端代码 (Echo Client)

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/io/async/AsyncSocket.h>
    2 #include <folly/io/async/EventBase.h>
    3 #include <folly/io/IOBuf.h>
    4 #include <folly/SocketAddress.h>
    5 #include <iostream>
    6 #include <string>
    7
    8 using namespace folly;
    9 using namespace std;
    10
    11 class EchoClientReadCallback : public AsyncSocket::ReadCallback {
    12 public:
    13 void onDataAvailable(AsyncSocket* socket) noexcept override {
    14 unique_ptr<IOBuf> buf;
    15 while ((buf = socket->recv()) != nullptr) {
    16 cout << "Client received echo: " << string((const char*)buf->data(), buf->length()) << endl;
    17 }
    18 }
    19 void readEOF(AsyncSocket* socket) noexcept override {
    20 cout << "Server closed connection." << endl;
    21 delete this;
    22 socket->close();
    23 }
    24 void onReadErr(AsyncSocket* socket, const exception& ex) noexcept override {
    25 cerr << "Read error: " << ex.what() << endl;
    26 delete this;
    27 }
    28 };
    29
    30 class EchoClientConnectCallback : public AsyncSocket::ConnectCallback {
    31 public:
    32 EchoClientConnectCallback(string message) : message_(message) {}
    33 void connectSuccess(AsyncSocket* socket) noexcept override {
    34 cout << "Connected to server!" << endl;
    35 socket->setReadCB(new EchoClientReadCallback());
    36 // Send message after connection success
    37 sendMessage(socket);
    38 }
    39 void connectErr(AsyncSocket* socket, const exception& ex) noexcept override {
    40 cerr << "Connection error: " << ex.what() << endl;
    41 delete socket;
    42 }
    43
    44 private:
    45 void sendMessage(AsyncSocket* socket) {
    46 cout << "Client sending: " << message_ << endl;
    47 socket->write(nullptr, IOBuf::copyBuffer(message_));
    48 }
    49 string message_;
    50 };
    51
    52
    53 int main(int argc, char** argv) {
    54 if (argc != 2) {
    55 cerr << "Usage: echo_client <message>" << endl;
    56 return 1;
    57 }
    58 string message = argv[1];
    59
    60 EventBase evb;
    61 AsyncSocket* sock = new AsyncSocket(&evb);
    62 EchoClientConnectCallback* connectCallback = new EchoClientConnectCallback(message);
    63 sock->connect(unique_ptr<EchoClientConnectCallback>(connectCallback), SocketAddress::fromHostPort("127.0.0.1", 8080));
    64
    65 evb.loopForever();
    66 return 0;
    67 }

    编译和运行

    1. 编译服务端代码
    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 g++ -o echo_server echo_server.cpp -lfolly -lglog -pthread
    1. 编译客户端代码
    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 g++ -o echo_client echo_client.cpp -lfolly -lglog -pthread
    1. 运行服务端
    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 ./echo_server
    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 服务端会输出 "Echo server listening on port 8080...",并等待客户端连接。
    1. 运行客户端
    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 ./echo_client "Hello Echo Server!"
    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 客户端会连接到服务端,发送消息 "Hello Echo Server!",并接收服务端返回的 Echo 消息。服务端和客户端都会在控制台输出相应的日志信息。

    代码解释

    服务端 (Echo Server)
    ▮▮▮▮⚝ EchoServerReadCallback 类处理客户端发送的数据,并在 onDataAvailable() 中将接收到的数据回显给客户端。
    ▮▮▮▮⚝ EchoServerAcceptCallback 类在 connectionAccepted() 中为新的连接设置 EchoServerReadCallback
    ▮▮▮▮⚝ main() 函数创建 AsyncServerSocket,设置回调,绑定端口 8080 并开始监听。

    客户端 (Echo Client)
    ▮▮▮▮⚝ EchoClientReadCallback 类处理服务端返回的 Echo 消息,并在 onDataAvailable() 中打印接收到的消息。
    ▮▮▮▮⚝ EchoClientConnectCallback 类在 connectSuccess() 中设置 EchoClientReadCallback,并在连接成功后发送命令行参数指定的消息给服务端。
    ▮▮▮▮⚝ main() 函数创建 AsyncSocket,设置连接回调,连接到 127.0.0.1:8080,并进入事件循环。

    这个 Echo 服务示例展示了如何使用 AsyncSocketAsyncServerSocket 构建一个简单的、基于回调的异步 TCP 应用。通过这个示例,可以更好地理解 Folly/IO 异步 Socket 编程的基本流程和核心概念。在实际应用中,可以基于此示例进行扩展,构建更复杂、更强大的网络应用程序。

    END_OF_CHAPTER

    4. chapter 4: AsyncTransport:传输层抽象 (AsyncTransport: Transport Layer Abstraction)

    4.1 AsyncTransport 的设计理念与优势 (Design Philosophy and Advantages of AsyncTransport)

    在深入探讨 AsyncTransport 之前,我们首先需要理解其诞生的背景和设计理念。在网络编程中,我们经常需要处理各种不同的传输协议,例如 TCP、UDP、TLS/SSL 等。如果直接在应用层代码中与底层的 Socket API 打交道,会导致代码的耦合性(Coupling)过高,可维护性(Maintainability)和可测试性(Testability)下降。AsyncTransport 的出现正是为了解决这些问题,它提供了一个抽象层(Abstraction Layer),将底层的传输细节与上层应用逻辑解耦,从而使得网络编程更加灵活、高效和易于维护。

    AsyncTransport 的核心设计理念可以归纳为以下几点:

    抽象化传输层AsyncTransport 定义了一组抽象接口(Abstract Interface),代表了传输层的通用能力,例如数据的发送和接收、连接的建立和断开等。这些接口与具体的底层传输协议(如 TCP 或 UDP)无关,使得上层应用可以基于这些抽象接口进行编程,而无需关心底层的传输细节。

    异步非阻塞AsyncTransport 完全基于 异步非阻塞(Asynchronous Non-blocking)的 IO 模型构建,与 AsyncSocket 一脉相承。这意味着所有的传输操作都不会阻塞线程,而是通过回调函数(Callback Function)或 Future/Promise 等机制来异步地通知结果。这使得基于 AsyncTransport 构建的网络应用能够充分利用系统资源,实现高并发和低延迟。

    灵活性与可扩展性AsyncTransport 的抽象设计使得它可以很容易地支持各种不同的传输协议。除了基于 AsyncSocketAsyncTransportWrapper,用户还可以根据自己的需求实现自定义的 AsyncTransport,例如基于 共享内存(Shared Memory)、进程间通信(Inter-Process Communication, IPC)或其他自定义协议的传输层。这种灵活性和可扩展性使得 AsyncTransport 能够适应各种复杂的网络应用场景。

    易于测试:由于 AsyncTransport 将传输层抽象出来,我们可以很容易地对其进行单元测试(Unit Test)和集成测试(Integration Test)。例如,我们可以实现一个内存中的(In-memory)AsyncTransport,用于在测试环境中模拟网络传输,而无需实际的网络连接。这大大提高了网络应用的可测试性,降低了开发和维护成本。

    AsyncTransport 的主要优势可以总结如下:

    解耦与抽象:将应用层与传输层解耦,提高代码的可维护性和可读性。
    高性能:基于异步非阻塞 IO 模型,充分利用系统资源,实现高吞吐量和低延迟。
    灵活性与可扩展性:支持各种传输协议,易于扩展和定制。
    易于测试:方便进行单元测试和集成测试,提高代码质量。
    统一接口:为不同的传输协议提供统一的编程接口,降低学习成本。

    总而言之,AsyncTransport 是 Folly/IO 库中一个非常重要的组件,它通过提供传输层抽象,极大地简化了网络编程的复杂性,提高了网络应用的性能、灵活性和可维护性。在接下来的章节中,我们将深入探讨 AsyncTransport 的具体使用方法和高级应用场景。

    4.2 AsyncTransportWrapper:基于 AsyncSocket 的传输层封装 (AsyncTransportWrapper: Transport Layer Encapsulation Based on AsyncSocket)

    AsyncTransportWrapperAsyncTransport 接口的一个具体实现(Concrete Implementation),也是 Folly/IO 库中最常用的一种 AsyncTransport。顾名思义,AsyncTransportWrapper 是对 AsyncSocket 的一层封装,它利用 AsyncSocket 提供的异步 Socket API,实现了 AsyncTransport 接口定义的传输层抽象。

    AsyncTransportWrapper 的主要作用是将底层的 AsyncSocket 操作,例如 read(读取)、write(写入)、connect(连接)、close(关闭)等,转换为 AsyncTransport 接口定义的抽象操作,例如 send(发送)、receive(接收)、connect(连接)、close(关闭)等。这样做的好处是,上层应用可以使用统一的 AsyncTransport 接口来操作不同的传输层实现,而无需关心底层是 AsyncSocket 还是其他的传输机制。

    AsyncTransportWrapper类继承关系(Class Inheritance)大致如下:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 AsyncTransport
    2 └── AsyncTransportWrapper

    AsyncTransportWrapper 继承自 AsyncTransport 抽象类,并实现了其定义的纯虚函数,从而成为一个具体的传输层实现。在 AsyncTransportWrapper 的内部,它持有一个 AsyncSocket 对象,所有的传输操作实际上都是通过调用底层的 AsyncSocket 对象来完成的。

    创建 AsyncTransportWrapper

    创建 AsyncTransportWrapper 通常需要一个已经建立连接的 AsyncSocket 对象。例如,在客户端,我们可以先创建一个 AsyncSocket 并连接到服务端,然后基于这个 AsyncSocket 创建 AsyncTransportWrapper

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/io/async/AsyncSocket.h>
    2 #include <folly/io/async/AsyncTransportWrapper.h>
    3 #include <folly/io/async/EventBase.h>
    4 #include <folly/io/IOBuf.h>
    5
    6 #include <iostream>
    7
    8 using namespace folly;
    9 using namespace folly::io;
    10 using namespace std;
    11
    12 int main() {
    13 EventBase evb;
    14 AsyncSocket::UniquePtr socket(AsyncSocket::newSocket(&evb));
    15
    16 socket->connect(nullptr, /* address */
    17 SocketAddress::fromHostPort("127.0.0.1", 8080), /* addr */
    18 0 /* timeout */);
    19
    20 // 创建 AsyncTransportWrapper,传入已连接的 AsyncSocket
    21 AsyncTransportWrapper::UniquePtr transport =
    22 AsyncTransportWrapper::UniquePtr(new AsyncTransportWrapper(std::move(socket)));
    23
    24 // 现在可以使用 transport 进行数据发送和接收了
    25 // ...
    26
    27 evb.loopForever();
    28 return 0;
    29 }

    在服务端,当 AsyncServerSocket 接受一个新的连接时,会创建一个新的 AsyncSocket 对象,我们可以基于这个新的 AsyncSocket 对象创建 AsyncTransportWrapper

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/io/async/AsyncServerSocket.h>
    2 #include <folly/io/async/AsyncTransportWrapper.h>
    3 #include <folly/io/async/EventBase.h>
    4 #include <folly/io/IOBuf.h>
    5
    6 #include <iostream>
    7
    8 using namespace folly;
    9 using namespace folly::io;
    10 using namespace std;
    11
    12 class MyConnection : public AsyncTransportWrapper::TransportCallback {
    13 public:
    14 void getReadBuffer(void** bufReturn, size_t* lenReturn) noexcept override {
    15 // ...
    16 }
    17 void readDataAvailable(size_t len) noexcept override {
    18 // ...
    19 }
    20 void readEOF() noexcept override {
    21 // ...
    22 }
    23 void readError(const AsyncSocketException& ex) noexcept override {
    24 // ...
    25 }
    26 void writeSuccess() noexcept override {
    27 // ...
    28 }
    29 void writeError(size_t bytesWritten, const AsyncSocketException& ex) noexcept override {
    30 // ...
    31 }
    32 void connectSuccess(std::unique_ptr<AsyncTransport> transport) noexcept override {
    33 cout << "Connection established!" << endl;
    34 // 基于 transport 创建 AsyncTransportWrapper
    35 AsyncTransportWrapper::UniquePtr wrapper =
    36 AsyncTransportWrapper::UniquePtr(static_cast<AsyncTransportWrapper*>(transport.release()));
    37 // 现在可以使用 wrapper 进行数据发送和接收了
    38 // ...
    39 }
    40 void connectError(const AsyncSocketException& ex) noexcept override {
    41 cerr << "Connection error: " << ex.what() << endl;
    42 }
    43 void detachTransport(AsyncTransport* transport) noexcept override {
    44 // ...
    45 }
    46 EventBase* getEventBase() noexcept override {
    47 // ...
    48 return nullptr; // Replace with actual EventBase if needed
    49 }
    50 };
    51
    52
    53 int main() {
    54 EventBase evb;
    55 AsyncServerSocket::UniquePtr serverSocket(AsyncServerSocket::newSocket(&evb));
    56
    57 SocketAddress address;
    58 address.setFromLocalPort(8080);
    59 serverSocket->bind(address);
    60 serverSocket->listen(10);
    61 serverSocket->setAcceptCallback([](std::unique_ptr<AsyncSocket> sock) {
    62 std::unique_ptr<MyConnection> connection(new MyConnection());
    63 sock->setTransportCallbacks(connection.get());
    64 sock->setReadCB(connection.get());
    65 sock->connect(std::move(sock)); // Initiate connection to self for callback
    66 });
    67
    68
    69 cout << "Server listening on port 8080" << endl;
    70 evb.loopForever();
    71 return 0;
    72 }

    使用 AsyncTransportWrapper

    创建 AsyncTransportWrapper 之后,我们就可以使用它进行数据的发送和接收了。AsyncTransportWrapper 提供了 sendreceive 等方法,用于异步地发送和接收数据。这些方法接受 IOBuf 作为数据载体,充分利用了 IOBuf 的零拷贝特性,提高了数据处理的效率。

    例如,使用 AsyncTransportWrapper 发送数据的代码如下:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/io/async/AsyncSocket.h>
    2 #include <folly/io/async/AsyncTransportWrapper.h>
    3 #include <folly/io/async/EventBase.h>
    4 #include <folly/io/IOBuf.h>
    5
    6 #include <iostream>
    7
    8 using namespace folly;
    9 using namespace folly::io;
    10 using namespace std;
    11
    12 class MyTransportCallback : public AsyncTransportWrapper::TransportCallback {
    13 public:
    14 explicit MyTransportCallback(AsyncTransportWrapper* transport) : transport_(transport) {}
    15
    16 void getReadBuffer(void** bufReturn, size_t* lenReturn) noexcept override {
    17 // ...
    18 }
    19 void readDataAvailable(size_t len) noexcept override {
    20 // ...
    21 }
    22 void readEOF() noexcept override {
    23 // ...
    24 }
    25 void readError(const AsyncSocketException& ex) noexcept override {
    26 // ...
    27 }
    28 void writeSuccess() noexcept override {
    29 cout << "Write success!" << endl;
    30 }
    31 void writeError(size_t bytesWritten, const AsyncSocketException& ex) noexcept override {
    32 cerr << "Write error: " << ex.what() << endl;
    33 }
    34 void connectSuccess(std::unique_ptr<AsyncTransport> transport) noexcept override {
    35 // ...
    36 }
    37 void connectError(const AsyncSocketException& ex) noexcept override {
    38 // ...
    39 }
    40 void detachTransport(AsyncTransport* transport) noexcept override {
    41 // ...
    42 }
    43 EventBase* getEventBase() noexcept override {
    44 // ...
    45 return nullptr; // Replace with actual EventBase if needed
    46 }
    47
    48 void sendData(std::string data) {
    49 auto buf = IOBuf::copyBuffer(data);
    50 transport_->send(std::move(buf));
    51 }
    52
    53 private:
    54 AsyncTransportWrapper* transport_;
    55 };
    56
    57
    58 int main() {
    59 EventBase evb;
    60 AsyncSocket::UniquePtr socket(AsyncSocket::newSocket(&evb));
    61
    62 socket->connect(nullptr, /* address */
    63 SocketAddress::fromHostPort("127.0.0.1", 8080), /* addr */
    64 0 /* timeout */);
    65
    66 AsyncTransportWrapper::UniquePtr transport =
    67 AsyncTransportWrapper::UniquePtr(new AsyncTransportWrapper(std::move(socket)));
    68
    69 MyTransportCallback transportCallback(transport.get());
    70 transport->setTransportCallbacks(&transportCallback);
    71
    72 transportCallback.sendData("Hello, AsyncTransportWrapper!");
    73
    74 evb.loopForever();
    75 return 0;
    76 }

    在这个例子中,我们首先创建了一个 AsyncTransportWrapper 对象,然后通过 sendData 方法发送了一个字符串 "Hello, AsyncTransportWrapper!"。sendData 方法将字符串转换为 IOBuf,然后调用 transport_->send() 方法进行异步发送。当数据发送成功或失败时,会通过 TransportCallback 接口通知上层应用。

    AsyncTransportWrapper 作为 AsyncTransport 的一个重要实现,为我们提供了一种方便、高效的方式来使用异步 Socket 进行网络编程。它隐藏了底层的 Socket 操作细节,使得我们可以更加专注于上层应用逻辑的开发。

    4.3 自定义 AsyncTransport 的实现 (Implementation of Custom AsyncTransport)

    虽然 AsyncTransportWrapper 已经能够满足大部分网络编程的需求,但在某些特殊场景下,我们可能需要实现自定义的 AsyncTransport。例如,我们可能需要基于 共享内存(Shared Memory)、进程间通信(IPC)或其他自定义协议来实现传输层。AsyncTransport 接口的抽象设计为我们提供了这种灵活性。

    要实现自定义的 AsyncTransport,我们需要创建一个新的类,继承自 AsyncTransport 抽象类,并实现其定义的纯虚函数(Pure Virtual Function)。AsyncTransport 抽象类定义了以下关键的纯虚函数:

    send(std::unique_ptr<IOBuf> buf): 异步发送数据。
    receive(size_t wanted, ReceiveCallback* callback): 异步接收指定长度的数据。
    close(): 关闭传输连接。
    connect(ConnectCallback* callback): 建立连接(仅客户端 AsyncTransport 需要实现)。
    disconnect(): 断开连接。
    setTransportCallbacks(TransportCallback* callback): 设置传输层回调。
    getTransportCallbacks() const: 获取传输层回调。
    isReadable() const: 判断是否可读。
    isWritable() const: 判断是否可写。
    good() const: 判断传输是否处于良好状态。
    error() const: 获取错误信息。
    getEventBase() const: 获取关联的 EventBase
    getLocalAddress() const: 获取本地地址。
    getPeerAddress() const: 获取对端地址。

    在实现自定义 AsyncTransport 时,我们需要根据具体的传输机制来实现这些纯虚函数。例如,如果我们想要实现一个基于共享内存的 AsyncTransport,我们需要:

    定义共享内存区域:在发送端和接收端之间建立共享内存区域,用于数据传输。

    实现 send 函数:将要发送的数据写入共享内存区域,并通知接收端数据已准备好。

    实现 receive 函数:从共享内存区域读取数据,并通过回调函数通知上层应用。

    实现连接和关闭函数:根据共享内存的特性,实现连接建立和关闭的逻辑。

    实现其他接口函数:根据共享内存的特性,实现其他接口函数,例如 isReadableisWritable 等。

    下面是一个简化的基于内存的 AsyncTransport 的示例代码,用于演示自定义 AsyncTransport 的实现思路:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/io/async/AsyncTransport.h>
    2 #include <folly/io/async/EventBase.h>
    3 #include <folly/io/IOBuf.h>
    4 #include <folly/io/Cursor.h>
    5
    6 #include <memory>
    7 #include <queue>
    8 #include <mutex>
    9 #include <condition_variable>
    10
    11 using namespace folly;
    12 using namespace folly::io;
    13
    14 class InMemoryTransport : public AsyncTransport {
    15 public:
    16 InMemoryTransport(EventBase* evb, InMemoryTransport* peer = nullptr)
    17 : eventBase_(evb), peer_(peer) {
    18 if (peer_) {
    19 peer_->peer_ = this; // 双向连接
    20 }
    21 }
    22 ~InMemoryTransport() override = default;
    23
    24 void send(std::unique_ptr<IOBuf> buf) override {
    25 if (peer_) {
    26 peer_->receiveData(std::move(buf));
    27 } else {
    28 // 错误处理:没有对端
    29 if (auto callbacks = getTransportCallbacks()) {
    30 callbacks->writeError(0, AsyncSocketException(AsyncSocketException::NOT_CONNECTED, "No peer"));
    31 }
    32 }
    33 }
    34
    35 void receive(size_t wanted, ReceiveCallback* callback) override {
    36 receiveCallback_ = callback;
    37 processReceiveQueue();
    38 }
    39
    40 void close() override {
    41 closed_ = true;
    42 if (receiveCallback_) {
    43 receiveCallback_->readEOF();
    44 receiveCallback_ = nullptr;
    45 }
    46 if (auto callbacks = getTransportCallbacks()) {
    47 callbacks->detachTransport(this);
    48 }
    49 }
    50
    51 void disconnect() override {
    52 close();
    53 }
    54
    55 void setTransportCallbacks(TransportCallback* callback) override {
    56 transportCallbacks_ = callback;
    57 }
    58
    59 TransportCallback* getTransportCallbacks() const override {
    60 return transportCallbacks_;
    61 }
    62
    63 bool isReadable() const override {
    64 return !closed_;
    65 }
    66
    67 bool isWritable() const override {
    68 return !closed_ && peer_ != nullptr && !peer_->closed_;
    69 }
    70
    71 bool good() const override {
    72 return !closed_ && peer_ != nullptr && !peer_->closed_;
    73 }
    74
    75 AsyncSocketException error() const override {
    76 return lastError_;
    77 }
    78
    79 EventBase* getEventBase() const override {
    80 return eventBase_;
    81 }
    82
    83 SocketAddress getLocalAddress() const override {
    84 return SocketAddress(); // 内存传输没有实际地址
    85 }
    86
    87 SocketAddress getPeerAddress() const override {
    88 return SocketAddress(); // 内存传输没有实际地址
    89 }
    90
    91 // 内部方法,用于接收数据
    92 private:
    93 void receiveData(std::unique_ptr<IOBuf> buf) {
    94 {
    95 std::lock_guard<std::mutex> lock(receiveQueueMutex_);
    96 receiveQueue_.push(std::move(buf));
    97 }
    98 processReceiveQueue();
    99 }
    100
    101 void processReceiveQueue() {
    102 if (!receiveCallback_) {
    103 return;
    104 }
    105
    106 while (!receiveQueue_.empty()) {
    107 std::unique_ptr<IOBuf> buf;
    108 {
    109 std::lock_guard<std::mutex> lock(receiveQueueMutex_);
    110 buf = std::move(receiveQueue_.front());
    111 receiveQueue_.pop();
    112 }
    113
    114 size_t receivedLen = buf->computeChainDataLength();
    115 receiveCallback_->readDataAvailable(receivedLen);
    116
    117 // 模拟 getReadBuffer 和 commitReadData 过程
    118 void* readBuf;
    119 size_t readLen;
    120 receiveCallback_->getReadBuffer(&readBuf, &readLen);
    121 if (readLen >= receivedLen) {
    122 Cursor cursor(buf.get());
    123 cursor.copyTo((char*)readBuf, receivedLen);
    124 receiveCallback_->commitReadData();
    125 } else {
    126 // 错误处理:缓冲区不足
    127 lastError_ = AsyncSocketException(AsyncSocketException::INTERNAL_ERROR, "Read buffer too small");
    128 receiveCallback_->readError(lastError_);
    129 receiveCallback_ = nullptr;
    130 return;
    131 }
    132 }
    133 }
    134
    135
    136 private:
    137 EventBase* eventBase_;
    138 InMemoryTransport* peer_;
    139 ReceiveCallback* receiveCallback_ = nullptr;
    140 TransportCallback* transportCallbacks_ = nullptr;
    141 std::queue<std::unique_ptr<IOBuf>> receiveQueue_;
    142 std::mutex receiveQueueMutex_;
    143 AsyncSocketException lastError_;
    144 bool closed_ = false;
    145 };

    这个示例代码实现了一个简单的 InMemoryTransport,它使用内存队列来模拟数据传输。send 方法将数据放入对端的接收队列,receive 方法从接收队列中取出数据并通知上层应用。这个例子虽然简化,但展示了实现自定义 AsyncTransport 的基本思路。

    在实际应用中,自定义 AsyncTransport 的实现会更加复杂,需要根据具体的传输机制进行详细设计和实现。但是,AsyncTransport 接口的抽象性为我们提供了很大的灵活性,使得我们可以根据不同的需求定制传输层,从而构建更加高效和灵活的网络应用。

    4.4 AsyncTransport 的高级应用场景 (Advanced Application Scenarios of AsyncTransport)

    AsyncTransport 的抽象设计不仅简化了基本的网络编程,还在许多高级应用场景中发挥着重要作用。以下列举一些 AsyncTransport 的高级应用场景:

    协议复用(Protocol Multiplexing):在同一个物理连接上复用多个逻辑连接,可以有效地提高网络资源的利用率,并减少连接建立和维护的开销。AsyncTransport 可以作为协议复用的基础,通过在 AsyncTransport 之上构建多路复用协议(Multiplexing Protocol),例如 SPDY 或 HTTP/2,来实现协议复用。每个逻辑连接可以被抽象为一个独立的 AsyncTransport 对象,共享底层的物理连接。

    自定义帧协议(Custom Framing Protocol):在某些场景下,标准的网络协议可能无法满足需求,我们需要自定义网络协议。AsyncTransport 可以作为自定义协议的基础,通过在 AsyncTransport 之上构建帧协议(Framing Protocol),来定义数据的封装和解析规则。例如,我们可以自定义一种变长帧协议(Variable-Length Frame Protocol),在每个数据包前添加长度字段,用于标识数据包的长度。AsyncTransport 负责底层的传输,帧协议负责数据的封装和解析。

    集成其他 Folly 库:Folly 库提供了许多强大的工具和组件,例如 Futures/PromisesConcurrentHashMapDynamic 等。AsyncTransport 可以与其他 Folly 库进行集成,构建更加复杂和高效的网络应用。例如,我们可以使用 Futures/Promises 来管理异步操作的流程,使用 ConcurrentHashMap 来存储和管理连接状态,使用 Dynamic 来处理动态配置信息。AsyncTransport 作为网络通信的基础,可以与这些 Folly 组件协同工作,发挥更大的作用。

    构建测试框架AsyncTransport 的抽象性使得我们可以很容易地构建网络测试框架(Network Testing Framework)。例如,我们可以实现一个模拟网络环境(Simulated Network Environment),用于在测试环境中模拟各种网络条件,例如延迟、丢包、乱序等。我们可以使用自定义的 AsyncTransport 来模拟不同的网络传输行为,从而对网络应用进行全面的测试。例如,前面提到的 InMemoryTransport 就可以用于单元测试,模拟内存中的数据传输。

    跨进程通信(Cross-Process Communication):AsyncTransport 不仅可以用于网络通信,还可以用于跨进程通信。例如,我们可以基于 Unix Domain Socket共享内存 实现 AsyncTransport,用于在同一台机器上的不同进程之间进行高效的数据传输。这在构建微服务架构(Microservices Architecture)或 分布式系统(Distributed System)时非常有用。

    总而言之,AsyncTransport 的抽象设计为我们提供了很大的灵活性和可扩展性,使得它可以在各种高级应用场景中发挥重要作用。通过深入理解 AsyncTransport 的设计理念和使用方法,我们可以构建更加高效、灵活和可靠的网络应用。

    END_OF_CHAPTER

    5. chapter 5: Request/Response 抽象:构建网络服务 (Request/Response Abstraction: Building Network Services)

    5.1 Request 和 Response 的抽象模型 (Abstract Model of Request and Response)

    在构建网络服务时,请求/响应模式 (Request/Response Pattern) 是一种 фундаментальная (fundamental) 且广泛应用的设计模式。它定义了客户端与服务端之间交互的基本流程:客户端发起一个请求 (Request),服务端接收并处理该请求后,返回一个响应 (Response)。这种模式简洁明了,易于理解和实现,是构建各种网络应用,从简单的客户端-服务端应用到复杂的分布式系统的基石。

    抽象模型 (Abstract Model) 的核心价值在于它将复杂的网络交互过程简化为两个明确定义的实体:请求和响应。这种抽象屏蔽了底层网络通信的细节,例如 Socket 连接管理、数据传输格式等,使开发者能够专注于业务逻辑的实现,从而提高开发效率和代码的可维护性。

    请求 (Request) 通常包含以下关键组成部分:

    请求方法 (Method): 指示客户端希望服务端执行的操作类型。常见的请求方法包括 GET(获取资源)、POST(创建资源)、PUT(更新资源)、DELETE(删除资源)等。不同的协议会定义不同的请求方法。
    请求 URI/路径 (URI/Path): 标识客户端请求的目标资源。URI (Uniform Resource Identifier)URL (Uniform Resource Locator) 用于定位服务端上的特定资源。路径则是在 URI 中指定资源位置的部分。
    请求头部 (Headers): 包含关于请求的附加信息,以键值对的形式存在。例如,Content-Type 头部指示请求体的 медиатип (media type),User-Agent 头部标识客户端的类型。
    请求体 (Body): 可选部分,包含要发送给服务端的实际数据。例如,POST 请求通常会在请求体中携带要创建或更新的数据。对于 GET 请求,请求体通常为空。

    响应 (Response) 则是服务端对请求处理结果的反馈,通常包含以下关键组成部分:

    状态码 (Status Code): 一个数字代码,指示请求的处理结果。例如,200 OK 表示请求成功,404 Not Found 表示请求的资源不存在,500 Internal Server Error 表示服务端发生内部错误。
    响应头部 (Headers): 包含关于响应的附加信息,同样以键值对的形式存在。例如,Content-Type 头部指示响应体的 медиатип (media type),Content-Length 头部指示响应体的长度。
    响应体 (Body): 可选部分,包含服务端返回的实际数据。例如,对于 GET 请求,响应体可能包含请求的资源数据;对于 POST 请求,响应体可能包含新创建资源的信息。

    抽象模型的优势 (Advantages of Abstract Model)

    解耦 (Decoupling): 请求和响应的抽象将客户端和服务端逻辑解耦。客户端只需要关注如何构造请求并发送,服务端只需要关注如何接收请求、处理并生成响应。双方无需关心彼此的内部实现细节。
    模块化 (Modularity): 基于请求/响应模式,可以将复杂的网络服务分解为更小的、可管理的模块。每个模块可以专注于处理特定类型的请求,提高系统的模块化程度和可维护性。
    可重用性 (Reusability): 请求和响应的抽象模型具有很高的可重用性。相同的请求/响应模式可以应用于不同的网络协议和应用场景。例如,HTTP、gRPC 等协议都基于请求/响应模式。
    简化开发 (Simplified Development): 抽象模型降低了网络编程的复杂性。开发者无需深入了解底层网络细节,可以更专注于业务逻辑的实现,加速开发过程。

    总而言之,Request/Response 抽象模型是构建网络服务的核心概念。理解其基本原理和组成部分,对于深入学习和应用 Folly/IO 以及其他网络编程框架至关重要。它为我们提供了一种结构化的方式来思考和设计网络交互,使得构建高效、可维护的网络服务成为可能。

    5.2 基于 Request/Response 构建简单的网络协议 (Building Simple Network Protocols Based on Request/Response)

    Request/Response 抽象模型不仅适用于像 HTTP 这样复杂的协议,也同样可以用于构建各种自定义的简单网络协议 (Custom Simple Network Protocols)。通过定义明确的请求和响应格式,我们可以快速构建满足特定应用场景的网络通信方案。

    构建简单协议的步骤 (Steps to Build a Simple Protocol)

    1. 定义请求类型 (Define Request Types): 首先需要确定协议需要支持哪些操作。每种操作对应一种请求类型。例如,对于一个简单的键值存储服务,可能需要 GET(获取值)、SET(设置值)、DELETE(删除值)这三种请求类型。

    2. 定义请求格式 (Define Request Format): 确定每种请求类型的具体格式。这包括请求方法、URI/路径(如果适用)、头部和请求体的格式。对于简单的文本协议,可以使用文本命令和分隔符来表示请求。对于更复杂的需求,可以使用二进制格式,例如 Protocol Buffers 或 Thrift。

    3. 定义响应格式 (Define Response Format): 类似于请求格式,需要定义响应的格式,包括状态码、头部和响应体的格式。状态码用于指示请求的处理结果,响应体则包含实际的响应数据或错误信息。

    4. 序列化与反序列化 (Serialization and Deserialization): 为了在网络上传输请求和响应,需要将它们序列化成字节流。在接收端,需要将字节流反序列化成请求和响应对象。选择合适的序列化方法对于协议的性能和效率至关重要。

    示例:简单的键值存储协议 (Example: Simple Key-Value Store Protocol)

    假设我们要构建一个简单的键值存储服务,我们可以定义如下基于文本的 Request/Response 协议:

    请求格式 (Request Format)

    命令 (Command): 使用文本命令表示请求类型,例如 GET, SET, DELETE
    键 (Key): 要操作的键,字符串类型。
    值 (Value): 对于 SET 命令,需要指定要设置的值,字符串类型。对于 GETDELETE 命令,值为空。
    分隔符 (Delimiter): 使用空格分隔命令、键和值,使用换行符 \n 结束请求。

    响应格式 (Response Format)

    状态 (Status): 使用 OKERROR 文本表示请求是否成功。
    消息 (Message): 对于 OK 状态,可能包含请求的值(对于 GET 命令);对于 ERROR 状态,包含错误信息。
    分隔符 (Delimiter): 使用空格分隔状态和消息,使用换行符 \n 结束响应。

    请求示例 (Request Examples)

    ⚝ 获取键为 mykey 的值: GET mykey\n
    ⚝ 设置键 mykey 的值为 myvalueSET mykey myvalue\n
    ⚝ 删除键 mykeyDELETE mykey\n

    响应示例 (Response Examples)

    ⚝ 成功获取键 mykey 的值 myvalueOK myvalue\n
    ⚝ 键 mykey 不存在: ERROR Key not found\n
    ⚝ 设置键值成功: OK\n
    ⚝ 删除键成功: OK\n

    协议设计考虑 (Protocol Design Considerations)

    效率 (Efficiency): 协议的设计需要考虑传输效率和解析效率。文本协议易于调试和理解,但通常效率较低。二进制协议效率更高,但可读性较差。
    可扩展性 (Extensibility): 协议应该易于扩展,以便在未来添加新的功能或请求类型。使用版本号或预留字段可以提高协议的可扩展性。
    错误处理 (Error Handling): 协议需要定义明确的错误处理机制,以便客户端能够正确处理服务端返回的错误。状态码和错误消息是重要的错误处理手段。
    安全性 (Security): 对于需要安全通信的场景,协议需要考虑安全性。可以使用 SSL/TLS 加密传输数据,或者在协议层面实现认证和授权机制。

    通过以上步骤和考虑,我们可以基于 Request/Response 抽象模型构建各种简单的网络协议,满足不同的应用需求。Folly/IO 提供了丰富的工具和组件,可以帮助我们更方便地实现这些协议,例如 IOBuf 用于高效地处理数据,AsyncSocket 用于异步网络通信。在后续章节中,我们将结合 Folly/IO 探讨如何实现这些协议。

    5.3 HTTP 请求与响应的抽象与应用 (Abstraction and Application of HTTP Request and Response)

    HTTP (Hypertext Transfer Protocol) 是互联网上应用最广泛的应用层协议 (Application Layer Protocol),它完美地体现了 Request/Response 抽象模型。理解 HTTP 的请求和响应结构,对于理解网络服务构建至关重要。

    HTTP 请求 (HTTP Request) 的结构:

    一个 HTTP 请求由以下几个部分组成:

    1. 请求行 (Request Line): 包含请求方法、请求 URI 和 HTTP 协议版本。例如: GET /index.html HTTP/1.1
      ▮▮▮▮⚝ 请求方法 (Method): 例如 GET, POST, PUT, DELETE, HEAD, OPTIONS 等,定义了客户端希望执行的操作。
      ▮▮▮▮⚝ 请求 URI (URI): 标识请求的目标资源,例如 /index.html, /api/users
      ▮▮▮▮⚝ HTTP 协议版本 (HTTP Version): 例如 HTTP/1.1, HTTP/2

    2. 请求头部 (Request Headers): 以键值对的形式提供关于请求的附加信息。例如:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 Host: www.example.com
    2 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...
    3 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    4 Content-Type: application/json
    5 Content-Length: 128

    ▮▮▮▮⚝ 常用请求头部 (Common Request Headers)
    ▮▮▮▮▮▮▮▮⚝ Host: 指定请求的主机名和端口号。
    ▮▮▮▮▮▮▮▮⚝ User-Agent: 标识客户端的类型和版本。
    ▮▮▮▮▮▮▮▮⚝ Accept: 客户端能够接收的 медиа类型 (media type)。
    ▮▮▮▮▮▮▮▮⚝ Content-Type: 请求体的 медиа类型 (media type),例如 application/json, text/plain, multipart/form-data
    ▮▮▮▮▮▮▮▮⚝ Content-Length: 请求体的长度。
    ▮▮▮▮▮▮▮▮⚝ Connection: 控制连接选项,例如 keep-aliveclose
    ▮▮▮▮▮▮▮▮⚝ Cookie: 客户端发送给服务端的 Cookie 信息。

    1. 请求体 (Request Body): 可选部分,包含要发送给服务端的数据。例如,POSTPUT 请求通常会包含请求体。请求体的格式由 Content-Type 头部指定。

    HTTP 响应 (HTTP Response) 的结构:

    一个 HTTP 响应由以下几个部分组成:

    1. 状态行 (Status Line): 包含 HTTP 协议版本、状态码和状态消息。例如: HTTP/1.1 200 OK
      ▮▮▮▮⚝ HTTP 协议版本 (HTTP Version): 例如 HTTP/1.1, HTTP/2
      ▮▮▮▮⚝ 状态码 (Status Code): 三位数字代码,指示请求的处理结果。例如 200, 404, 500
      ▮▮▮▮⚝ 状态消息 (Status Message): 对状态码的简要描述,例如 OK, Not Found, Internal Server Error

    2. 响应头部 (Response Headers): 以键值对的形式提供关于响应的附加信息。例如:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 Content-Type: text/html; charset=UTF-8
    2 Content-Length: 1024
    3 Date: Tue, 23 May 2023 10:00:00 GMT
    4 Server: Apache/2.4.41 (Ubuntu)
    5 Set-Cookie: sessionId=abcdefg; Path=/; HttpOnly

    ▮▮▮▮⚝ 常用响应头部 (Common Response Headers)
    ▮▮▮▮▮▮▮▮⚝ Content-Type: 响应体的 медиа类型 (media type),例如 text/html, application/json
    ▮▮▮▮▮▮▮▮⚝ Content-Length: 响应体的长度。
    ▮▮▮▮▮▮▮▮⚝ Date: 响应的发送时间。
    ▮▮▮▮▮▮▮▮⚝ Server: 服务端软件的信息。
    ▮▮▮▮▮▮▮▮⚝ Set-Cookie: 服务端设置 Cookie 的指令。
    ▮▮▮▮▮▮▮▮⚝ Cache-Control: 控制客户端缓存行为。

    1. 响应体 (Response Body): 可选部分,包含服务端返回的实际数据,例如 HTML 文档、JSON 数据、图片等。响应体的格式由 Content-Type 头部指定。

    HTTP 的应用场景 (Application Scenarios of HTTP)

    HTTP 作为应用层协议,被广泛应用于各种网络场景:

    Web 浏览 (Web Browsing): 最常见的应用场景,浏览器通过 HTTP 协议与 Web 服务器交互,获取网页内容。
    Web API (Web API): 构建 RESTful API 或其他类型的 Web API,用于不同系统之间的数据交换和功能调用。
    移动应用后端 (Mobile Application Backend): 移动应用通常使用 HTTP 协议与后端服务器通信,获取数据和执行操作。
    微服务架构 (Microservices Architecture): 微服务之间可以使用 HTTP 协议进行通信,构建分布式系统。
    物联网 (Internet of Things, IoT): 许多 IoT 设备也使用 HTTP 协议与云平台或控制中心通信。

    Folly/IO 与 HTTP (Folly/IO and HTTP)

    虽然 Folly/IO 本身没有直接提供完整的 HTTP 协议实现,但它提供了构建高性能网络应用的基础组件,例如 AsyncSocket, IOBuf, Cursor 等。我们可以利用这些组件,结合 Folly 的其他库(例如 folly/Uri.h 用于 URI 解析,folly/String.h 用于字符串处理),构建高效的 HTTP 客户端和服务端。在后续的实战代码部分,我们将演示如何使用 Folly/IO 构建一个简单的 HTTP 服务端。

    理解 HTTP 的 Request/Response 结构及其应用场景,是网络编程的基础。Folly/IO 提供的工具可以帮助我们更高效地处理 HTTP 协议,构建高性能的 Web 服务和应用。

    5.4 实战代码:构建简单的 HTTP 服务端 (Practical Code: Building a Simple HTTP Server)

    本节将通过一个简化的代码示例,演示如何使用 Folly/IO 构建一个简单的 HTTP 服务端 (Simple HTTP Server)。这个服务端只处理 GET 请求,并返回一个简单的 "Hello, World!" 页面。这个示例旨在展示 Request/Response 抽象模型在实际代码中的应用,以及 Folly/IO 相关组件的基本用法。

    代码框架 (Code Framework)

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/io/async/AsyncServerSocket.h>
    2 #include <folly/io/async/AsyncSocket.h>
    3 #include <folly/io/IOBuf.h>
    4 #include <folly/io/Cursor.h>
    5 #include <folly/io/IOBufQueue.h>
    6 #include <folly/portability/Sockets.h>
    7 #include <folly/String.h>
    8 #include <folly/EventBase.h>
    9 #include <iostream>
    10
    11 using namespace folly;
    12 using namespace folly::io;
    13 using namespace std;
    14
    15 class SimpleHttpServer : public AsyncServerSocket::AcceptCallback, public AsyncSocket::ReadCallback {
    16 public:
    17 explicit SimpleHttpServer(EventBase* evb) : eventBase_(evb) {}
    18
    19 void start(int port) {
    20 serverSocket_ = AsyncServerSocket::newSocket(eventBase_);
    21 serverSocket_->setAcceptCallback(this);
    22 serverSocket_->bind(SocketAddress("0.0.0.0", port));
    23 serverSocket_->listen(10);
    24 serverSocket_->startAccepting();
    25 cout << "Simple HTTP Server started on port " << port << endl;
    26 }
    27
    28 // AsyncServerSocket::AcceptCallback methods
    29 void connectionAccepted(std::unique_ptr<AsyncSocket> sock) noexcept override {
    30 cout << "Connection accepted from " << sock->getPeerAddress().describe() << endl;
    31 sock->setReadCallback(this);
    32 sock->read(); // 开始读取数据
    33 }
    34
    35 void acceptError(exception_wrapper ew) noexcept override {
    36 cerr << "Accept error: " << ew.what() << endl;
    37 }
    38
    39 // AsyncSocket::ReadCallback methods
    40 void dataAvailable(AsyncSocket* sock) noexcept override {
    41 IOBufQueue queue;
    42 sock->recv(&queue);
    43 processRequest(sock, queue);
    44 sock->read(); // 继续读取数据
    45 }
    46
    47 void readEOF(AsyncSocket* sock) noexcept override {
    48 cout << "Client disconnected: " << sock->getPeerAddress().describe() << endl;
    49 sock->close();
    50 }
    51
    52 void readError(AsyncSocket* sock, exception_wrapper ew) noexcept override {
    53 cerr << "Read error from " << sock->getPeerAddress().describe() << ": " << ew.what() << endl;
    54 sock->close();
    55 }
    56
    57 private:
    58 void processRequest(AsyncSocket* sock, IOBufQueue& requestQueue) {
    59 // 1. 解析 HTTP 请求行 (Parse HTTP Request Line)
    60 string requestLine;
    61 string body;
    62 {
    63 Cursor cursor(requestQueue.front());
    64 string line;
    65 if (cursor.readLine(line)) {
    66 requestLine = line;
    67 // 简化处理,假设请求行之后没有其他头部,直接读取 body (Simplified processing, assuming no headers after request line)
    68 requestQueue.trimStart(requestQueue.front()->computeChainDataLength()); // 移除已处理的请求行 (Remove processed request line)
    69 if (!requestQueue.empty()) {
    70 body = string((char*)requestQueue.front()->data(), requestQueue.front()->length());
    71 requestQueue.trimStart(requestQueue.front()->computeChainDataLength()); // 移除 body (Remove body)
    72 }
    73 } else {
    74 cerr << "Failed to read request line" << endl;
    75 return;
    76 }
    77 }
    78
    79
    80 cout << "Request Line: " << requestLine << endl;
    81 cout << "Body: " << body << endl;
    82
    83
    84 // 2. 构造 HTTP 响应 (Construct HTTP Response)
    85 string responseBody = "<html><body><h1>Hello, World!</h1></body></html>";
    86 string response = "HTTP/1.1 200 OK\r\n";
    87 response += "Content-Type: text/html\r\n";
    88 response += "Content-Length: " + to_string(responseBody.length()) + "\r\n";
    89 response += "Connection: close\r\n"; // 简化处理,每次请求后关闭连接 (Simplified processing, close connection after each request)
    90 response += "\r\n";
    91 response += responseBody;
    92
    93 // 3. 发送 HTTP 响应 (Send HTTP Response)
    94 auto iobuf = IOBuf::copyBuffer(response);
    95 sock->write(move(iobuf));
    96 sock->close(); // 关闭连接 (Close connection)
    97 }
    98
    99 EventBase* eventBase_;
    100 unique_ptr<AsyncServerSocket> serverSocket_;
    101 };
    102
    103 int main() {
    104 EventBase eventBase;
    105 SimpleHttpServer server(&eventBase);
    106 server.start(8080);
    107 eventBase.loopForever();
    108 return 0;
    109 }

    代码解释 (Code Explanation)

    1. 头文件包含 (Header Includes): 包含了 Folly/IO 相关的头文件,例如 AsyncServerSocket.h, AsyncSocket.h, IOBuf.h, Cursor.h, IOBufQueue.h,以及其他必要的头文件。
    2. SimpleHttpServer 类 (SimpleHttpServer Class)
      ▮▮▮▮⚝ 继承自 AsyncServerSocket::AcceptCallbackAsyncSocket::ReadCallback,用于处理连接接受事件和数据读取事件。
      ▮▮▮▮⚝ start(int port) 方法: 启动 HTTP 服务端,绑定端口并开始监听连接。
      ▮▮▮▮⚝ connectionAccepted(std::unique_ptr<AsyncSocket> sock) 方法: 当有新的连接被接受时调用,设置 ReadCallback 并开始读取数据。
      ▮▮▮▮⚝ dataAvailable(AsyncSocket* sock) 方法: 当有数据可读时调用,从 AsyncSocket 读取数据到 IOBufQueue,并调用 processRequest 处理请求。
      ▮▮▮▮⚝ processRequest(AsyncSocket* sock, IOBufQueue& requestQueue) 方法: 核心请求处理逻辑。
      ▮▮▮▮▮▮▮▮⚝ 解析请求行 (Parse Request Line): 使用 CursorIOBufQueue 中读取请求行。这里为了简化,只读取请求行,忽略请求头部和请求体(对于简单的 GET 请求足够)。
      ▮▮▮▮▮▮▮▮⚝ 构造响应 (Construct Response): 构建 HTTP 响应字符串,包括状态行、头部(Content-Type, Content-Length, Connection)和响应体("Hello, World!" HTML)。
      ▮▮▮▮▮▮▮▮⚝ 发送响应 (Send Response): 使用 IOBuf::copyBuffer 将响应字符串转换为 IOBuf,并通过 AsyncSocket::write 发送响应。
      ▮▮▮▮▮▮▮▮⚝ 关闭连接 (Close Connection): 发送响应后,关闭连接。
    3. main 函数 (main Function)
      ▮▮▮▮⚝ 创建 EventBase 对象,Folly/IO 的异步事件循环核心。
      ▮▮▮▮⚝ 创建 SimpleHttpServer 对象,并启动服务。
      ▮▮▮▮⚝ 调用 eventBase.loopForever() 启动事件循环,使服务端持续运行。

    编译和运行 (Compilation and Execution)

    1. 编译 (Compilation): 使用支持 C++17 和 Folly 的编译器编译代码。例如,使用 g++:
    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 g++ -std=c++17 -o simple_http_server simple_http_server.cpp -lfolly -lglog -lzstd -llz4 -lsnappy

    ▮▮▮▮⚝ 注意: 编译命令可能需要根据你的 Folly 安装环境进行调整,例如添加 -I 指定头文件路径,-L 指定库文件路径,以及链接其他 Folly 依赖的库。

    1. 运行 (Execution): 运行编译生成的可执行文件 simple_http_server
    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 ./simple_http_server
    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 服务端将会在 8080 端口启动。
    1. 测试 (Testing): 使用浏览器或 curl 等工具访问 http://localhost:8080,应该能够看到 "Hello, World!" 页面。
    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 curl http://localhost:8080

    总结 (Summary)

    这个简单的 HTTP 服务端示例展示了如何使用 Folly/IO 的 AsyncServerSocketAsyncSocket 组件构建异步网络服务,以及如何使用 IOBufCursor 处理网络数据。虽然这个示例非常简化,但它涵盖了构建 Request/Response 服务的基本流程:接受连接、读取请求、处理请求、构造响应、发送响应和关闭连接。在实际应用中,我们需要处理更复杂的 HTTP 请求解析、路由、头部处理、错误处理等,但这个示例为我们理解 Folly/IO 在构建网络服务中的作用奠定了基础。

    END_OF_CHAPTER

    6. chapter 6: SSL/TLS 安全通信 (SSL/TLS Secure Communication)

    6.1 SSLContext 与 SSLOptions:SSL/TLS 配置 (SSLContext and SSLOptions: SSL/TLS Configuration)

    在现代网络通信中,安全性是至关重要的考量因素。为了保护数据在传输过程中的机密性和完整性,安全套接层协议(Secure Sockets Layer, SSL)和传输层安全协议(Transport Layer Security, TLS)成为了事实上的标准。Folly/IO 提供了强大的 SSL/TLS 支持,允许开发者轻松地为网络应用添加安全通信能力。在 Folly/IO 中,SSLContextSSLOptions 是配置 SSL/TLS 的核心组件。

    SSLContext 类主要负责管理全局的 SSL/TLS 上下文信息,例如证书(Certificates)、私钥(Private Keys)以及 SSL/TLS 协议版本等。SSLOptions 类则允许针对每个连接或会话配置特定的 SSL/TLS 选项,例如启用的密码套件(Cipher Suites)、是否需要客户端证书验证等。

    6.1.1 SSLContext:全局 SSL/TLS 上下文

    SSLContext 类是 SSL/TLS 配置的入口点,它封装了 OpenSSL 或 BoringSSL 库提供的底层 SSL 上下文。通过 SSLContext,我们可以加载服务器或客户端的证书和私钥,设置支持的 SSL/TLS 协议版本,以及配置其他全局性的 SSL/TLS 参数。

    创建 SSLContext 对象

    创建 SSLContext 对象通常是 SSL/TLS 配置的第一步。在 Folly/IO 中,可以通过 SSLContextConfig 类来配置 SSLContext 的创建过程。例如,可以使用默认配置创建一个 SSLContext 对象:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/io/async/SSLContext.h>
    2
    3 auto sslContext = std::make_shared<folly::SSLContext>();

    加载证书和私钥

    对于服务器端,需要加载服务器证书和私钥,以便客户端可以验证服务器的身份并建立加密连接。对于客户端,虽然通常不需要加载客户端证书,但在某些需要客户端证书认证的场景下,也需要加载客户端证书和私钥。

    可以使用 SSLContext 提供的 API 来加载证书和私钥。常用的 API 包括:

    loadCertFromFile(const std::string& certFile): 从文件中加载 PEM 格式的证书。
    loadKeyFromFile(const std::string& keyFile): 从文件中加载 PEM 格式的私钥。
    loadTrustedCertificates(const std::string& certFile): 加载信任的 CA 证书,用于验证客户端或服务器证书的有效性。

    例如,加载服务器证书和私钥的代码如下:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/io/async/SSLContext.h>
    2
    3 auto sslContext = std::make_shared<folly::SSLContext>();
    4 try {
    5 sslContext->loadCertFromFile("server.crt");
    6 sslContext->loadKeyFromFile("server.key");
    7 } catch (const std::exception& e) {
    8 // 异常处理
    9 std::cerr << "Failed to load certificate or key: " << e.what() << std::endl;
    10 return 1;
    11 }

    设置 SSL/TLS 协议版本

    SSLContext 允许配置支持的 SSL/TLS 协议版本。默认情况下,Folly/IO 会使用一个安全的协议版本范围。但用户也可以根据需要显式地设置支持的协议版本,例如只允许 TLS 1.2 和 TLS 1.3:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/io/async/SSLContext.h>
    2 #include <folly/io/async/SSLOptions.h>
    3
    4 auto sslContext = std::make_shared<folly::SSLContext>();
    5 folly::SSLOptions options;
    6 options.minVersion = folly::SSLVersion::TLSv1_2;
    7 options.maxVersion = folly::SSLVersion::TLSv1_3;
    8 sslContext->setOptions(options);

    folly::SSLVersion 枚举定义了支持的 SSL/TLS 协议版本,包括 TLSv1_0, TLSv1_1, TLSv1_2, TLSv1_3 等。

    6.1.2 SSLOptions:连接级别的 SSL/TLS 选项

    SSLOptions 类允许为每个 SSL/TLS 连接或会话配置特定的选项。SSLOptions 对象可以传递给 AsyncSocketsslHandshake 方法,以定制该连接的 SSL/TLS 行为。

    配置密码套件 (Cipher Suites)

    密码套件定义了 SSL/TLS 连接中使用的加密算法,包括密钥交换算法、对称加密算法和哈希算法。选择合适的密码套件对于安全性和性能都至关重要。SSLOptions 允许用户指定允许使用的密码套件列表。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/io/async/SSLOptions.h>
    2
    3 folly::SSLOptions options;
    4 options.cipherSuites = "ECDHE-RSA-AES128-GCM-SHA256:AES128-GCM-SHA256";

    上述代码示例设置了允许使用的密码套件为 "ECDHE-RSA-AES128-GCM-SHA256" 和 "AES128-GCM-SHA256"。密码套件字符串的格式遵循 OpenSSL 的规范。

    客户端证书验证

    对于服务器端,可以配置是否需要客户端提供证书进行身份验证。如果需要客户端证书验证,可以通过 SSLOptions 设置验证模式,并加载信任的 CA 证书。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/io/async/SSLOptions.h>
    2 #include <folly/io/async/SSLContext.h>
    3
    4 folly::SSLOptions options;
    5 options.verifyPeerCertificate = true; // 启用客户端证书验证
    6 options.setVerificationOption(folly::SSLVerifyPeerEnum::VERIFY_PEER); // 设置验证模式为需要验证客户端证书
    7 // 需要在 SSLContext 中加载信任的 CA 证书
    8 auto sslContext = std::make_shared<folly::SSLContext>();
    9 sslContext->loadTrustedCertificates("ca.crt");

    folly::SSLVerifyPeerEnum 枚举定义了不同的客户端证书验证模式,例如 VERIFY_NONE (不验证客户端证书), VERIFY_PEER (需要验证客户端证书,但验证失败不中断连接), VERIFY_PEER_STRICT (需要验证客户端证书,验证失败中断连接) 等。

    其他 SSL/TLS 选项

    SSLOptions 还提供了许多其他选项,用于更细粒度地控制 SSL/TLS 连接的行为,例如:

    allowRenegotiation: 是否允许 SSL/TLS 协商重连。
    sessionTickets: 是否启用会话票据(Session Tickets)以加速会话恢复。
    alpnProtocols: 应用层协议协商(Application-Layer Protocol Negotiation, ALPN)协议列表。

    开发者可以根据具体的安全需求和性能考量,灵活配置 SSLOptions 中的各项参数。

    通过 SSLContextSSLOptions 的协同工作,Folly/IO 提供了强大而灵活的 SSL/TLS 配置能力,满足各种复杂的安全通信需求。

    6.2 在 AsyncSocket 中启用 SSL/TLS (Enabling SSL/TLS in AsyncSocket)

    AsyncSocket 是 Folly/IO 中用于异步网络编程的核心组件。为了在 AsyncSocket 中启用 SSL/TLS 安全通信,需要将配置好的 SSLContextSSLOptions 传递给 AsyncSocket,并在建立连接后进行 SSL/TLS 握手(Handshake)。

    6.2.1 客户端 SSL/TLS 连接

    对于客户端 AsyncSocket,在建立 TCP 连接之后,需要显式地调用 sslHandshake 方法来启动 SSL/TLS 握手过程。sslHandshake 方法接受 SSLContextSSLOptions 作为参数。

    以下代码示例展示了如何在客户端 AsyncSocket 中启用 SSL/TLS:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/io/async/AsyncSocket.h>
    2 #include <folly/io/async/EventBase.h>
    3 #include <folly/io/async/SSLContext.h>
    4 #include <folly/io/async/SSLOptions.h>
    5 #include <folly/SocketAddress.h>
    6
    7 #include <iostream>
    8
    9 int main() {
    10 folly::EventBase evb;
    11 auto socket = folly::AsyncSocket::newSocket(&evb);
    12 auto sslContext = std::make_shared<folly::SSLContext>();
    13 folly::SSLOptions sslOptions;
    14
    15 // ... 配置 sslContext 和 sslOptions (例如加载 CA 证书) ...
    16 try {
    17 sslContext->loadTrustedCertificates("ca.crt");
    18 } catch (const std::exception& e) {
    19 std::cerr << "Failed to load trusted certificates: " << e.what() << std::endl;
    20 return 1;
    21 }
    22
    23
    24 folly::SocketAddress address("www.example.com", 443); // HTTPS 默认端口 443
    25
    26 socket->connect(address).then([socket, sslContext, sslOptions]() {
    27 std::cout << "TCP connected, starting SSL handshake..." << std::endl;
    28 return socket->sslHandshake(sslContext, sslOptions);
    29 }).then([]() {
    30 std::cout << "SSL handshake successful!" << std::endl;
    31 // ... 安全通信 ...
    32 }).onError([&](const folly::exception_wrapper& e) {
    33 std::cerr << "Connection or SSL handshake error: " << e.what() << std::endl;
    34 });
    35
    36 evb.loopForever();
    37 return 0;
    38 }

    在这个例子中,客户端首先使用 connect 方法建立 TCP 连接。连接建立成功后,在 then 回调中调用 sslHandshake 方法,传入之前配置好的 sslContextsslOptionssslHandshake 方法返回一个 folly::Future<void>,表示 SSL/TLS 握手操作的异步结果。握手成功后,后续的 then 回调会被执行,此时 AsyncSocket 就处于安全通信状态。如果握手失败,onError 回调会被调用,处理错误情况。

    6.2.2 服务端 SSL/TLS 连接

    对于服务端 AsyncServerSocket,需要在接受连接后,对新创建的 AsyncSocket 调用 sslHandshake 方法。通常在 ConnectionAcceptedCallback 回调函数中进行 SSL/TLS 握手。

    以下代码示例展示了如何在服务端 AsyncServerSocket 中启用 SSL/TLS:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/io/async/AsyncServerSocket.h>
    2 #include <folly/io/async/EventBase.h>
    3 #include <folly/io/async/SSLContext.h>
    4 #include <folly/io/async/SSLOptions.h>
    5 #include <folly/SocketAddress.h>
    6
    7 #include <iostream>
    8
    9 int main() {
    10 folly::EventBase evb;
    11 auto serverSocket = folly::AsyncServerSocket::newSocket(&evb);
    12 auto sslContext = std::make_shared<folly::SSLContext>();
    13 folly::SSLOptions sslOptions;
    14
    15 // ... 配置 sslContext 和 sslOptions (例如加载服务器证书和私钥) ...
    16 try {
    17 sslContext->loadCertFromFile("server.crt");
    18 sslContext->loadKeyFromFile("server.key");
    19 } catch (const std::exception& e) {
    20 std::cerr << "Failed to load certificate or key: " << e.what() << std::endl;
    21 return 1;
    22 }
    23
    24
    25 serverSocket->setConnectionAcceptedCallback([sslContext, sslOptions](
    26 std::unique_ptr<folly::AsyncSocket> socket,
    27 const folly::SocketAddress& /*peerAddress*/) {
    28 std::cout << "Accepted connection, starting SSL handshake..." << std::endl;
    29 socket->sslHandshake(sslContext, sslOptions).then([]() {
    30 std::cout << "SSL handshake successful on server side!" << std::endl;
    31 // ... 安全通信 ...
    32 }).onError([&](const folly::exception_wrapper& e) {
    33 std::cerr << "SSL handshake error on server side: " << e.what() << std::endl;
    34 socket->close(); // 关闭连接
    35 });
    36 return socket.release(); // 返回裸指针,AsyncServerSocket 管理 socket 生命周期
    37 });
    38
    39 folly::SocketAddress address("0.0.0.0", 8443); // HTTPS 服务端口 8443
    40 serverSocket->bind(address);
    41 serverSocket->listen(1024);
    42 serverSocket->startAccepting();
    43
    44 evb.loopForever();
    45 return 0;
    46 }

    服务端代码中,在 ConnectionAcceptedCallback 回调函数中,对新接受的 AsyncSocket 调用 sslHandshake 方法,同样传入 sslContextsslOptions。握手成功或失败的处理方式与客户端类似。

    6.2.3 SSL/TLS 事件处理

    在 SSL/TLS 握手过程中,以及在安全通信过程中,可能会发生一些事件,例如握手完成、连接关闭、证书验证失败等。AsyncSocket 提供了相应的回调函数来处理这些事件。

    sslHandshakeSuccess(): SSL/TLS 握手成功回调。
    sslHandshakeError(const folly::exception_wrapper& ex): SSL/TLS 握手失败回调。
    sslSessionClosed(): SSL/TLS 会话关闭回调。
    sslPeerCertificateVerified(): 客户端证书验证成功回调(仅服务端)。
    sslPeerCertificateVerificationError(const folly::exception_wrapper& ex): 客户端证书验证失败回调(仅服务端)。

    开发者可以根据需要,在 AsyncSocket 的子类中重写这些回调函数,以实现自定义的 SSL/TLS 事件处理逻辑。

    通过以上步骤,就可以在 AsyncSocket 中成功启用 SSL/TLS 安全通信,为网络应用提供可靠的数据加密和身份验证机制。

    6.3 基于 OpenSSL 或 BoringSSL 的实现细节 (Implementation Details Based on OpenSSL or BoringSSL)

    Folly/IO 的 SSL/TLS 实现底层依赖于成熟的密码学库,目前主要支持 OpenSSL 和 BoringSSL。OpenSSL 是一个广泛使用的开源 SSL/TLS 工具包,而 BoringSSL 是 Google 基于 OpenSSL 分支维护的,主要用于 Chromium 和 Android 项目。

    6.3.1 OpenSSL 与 BoringSSL 的选择

    Folly/IO 在编译时可以选择链接 OpenSSL 或 BoringSSL。通常情况下,Folly/IO 会优先选择 BoringSSL,因为它在性能和安全性方面都有一定的优势,并且是 Facebook 内部使用的 SSL/TLS 库。如果系统中没有 BoringSSL,则会退而使用 OpenSSL。

    开发者在构建 Folly/IO 项目时,可以通过 CMake 配置来显式指定使用的 SSL/TLS 库。例如,可以使用 -DFOLLY_USE_BORINGSSL=OFF 强制使用 OpenSSL。

    6.3.2 Folly/IO 对底层库的抽象

    Folly/IO 的 SSLContextSSLOptions 类对 OpenSSL 和 BoringSSL 的底层 API 进行了抽象封装,为开发者提供了一套统一的、易于使用的接口。这意味着,开发者在使用 Folly/IO 的 SSL/TLS 功能时,无需直接与 OpenSSL 或 BoringSSL 的复杂 API 打交道,降低了开发难度,并提高了代码的可移植性。

    例如,无论是使用 OpenSSL 还是 BoringSSL,加载证书和私钥、设置密码套件、启用客户端证书验证等操作,在 Folly/IO 中都使用相同的 API 和流程。Folly/IO 内部会根据编译时选择的底层库,自动调用相应的 OpenSSL 或 BoringSSL 函数。

    6.3.3 底层库的特性与差异

    虽然 Folly/IO 尽量屏蔽了底层库的差异,但 OpenSSL 和 BoringSSL 在某些特性和行为上仍然存在一些区别。例如:

    协议支持: BoringSSL 通常会更快地支持最新的 TLS 协议和扩展,例如 TLS 1.3、ALPN 等。
    性能: 在某些场景下,BoringSSL 可能比 OpenSSL 具有更好的性能,尤其是在现代硬件平台上。
    API 细节: 尽管 Folly/IO 进行了抽象,但在一些高级配置或定制化场景下,可能仍然需要了解底层库的 API 细节。例如,在配置自定义的证书验证逻辑时,可能需要使用 OpenSSL 或 BoringSSL 提供的回调函数。
    许可证: OpenSSL 使用 OpenSSL License 和 SSLeay License 双重许可,而 BoringSSL 使用 BSD 许可证。许可证的选择可能会影响项目的使用和分发。

    开发者在选择和使用 Folly/IO 的 SSL/TLS 功能时,可以根据项目的具体需求和环境,考虑底层库的选择和特性。如果对性能和最新的协议支持有较高要求,BoringSSL 通常是更好的选择。如果需要更广泛的平台兼容性或需要使用 OpenSSL 特有的功能,则可以选择 OpenSSL。

    总而言之,Folly/IO 通过抽象 OpenSSL 和 BoringSSL 的底层实现,为开发者提供了便捷且强大的 SSL/TLS 功能。了解底层库的实现细节,有助于更深入地理解 Folly/IO 的 SSL/TLS 工作原理,并在必要时进行更高级的定制和优化。

    6.4 SSL/TLS 的性能考量与优化 (Performance Considerations and Optimization of SSL/TLS)

    SSL/TLS 提供了安全通信的保障,但同时也引入了性能开销。加密和解密操作、握手过程中的密钥交换、证书验证等都会消耗 CPU 资源和网络带宽。在构建高性能网络应用时,需要充分考虑 SSL/TLS 的性能影响,并采取相应的优化措施。

    6.4.1 SSL/TLS 性能开销分析

    SSL/TLS 的性能开销主要来自以下几个方面:

    CPU 密集型操作:

    加密和解密: 对传输的数据进行加密和解密需要消耗大量的 CPU 资源,尤其是在使用计算密集型的加密算法时。
    握手过程: SSL/TLS 握手过程包括密钥交换、证书验证、会话协商等步骤,这些步骤都需要进行复杂的密码学运算,消耗 CPU 资源。

    网络延迟:

    握手延迟: SSL/TLS 握手通常需要多次网络往返(Round-Trip Time, RTT),增加了连接建立的延迟。
    数据传输开销: 加密后的数据包通常会比未加密的数据包略大,增加了网络传输的开销。

    内存开销:

    SSL/TLS 上下文: SSLContextSSLOptions 对象会占用一定的内存空间。
    会话缓存: 为了加速会话恢复,SSL/TLS 库通常会维护会话缓存,也会占用一定的内存。

    6.4.2 SSL/TLS 性能优化策略

    为了降低 SSL/TLS 的性能开销,可以从以下几个方面进行优化:

    会话复用 (Session Resumption)

    SSL/TLS 会话复用允许客户端和服务器在后续连接中重用之前协商好的会话密钥,从而避免完整的握手过程,显著降低握手延迟和 CPU 开销。Folly/IO 默认启用会话复用,可以通过 SSLOptions 中的 sessionTickets 选项来控制是否启用会话票据(Session Tickets),会话票据是一种常用的会话复用机制。

    密码套件选择 (Cipher Suite Selection)

    选择合适的密码套件对于性能至关重要。一般来说,应优先选择使用硬件加速的密码算法,例如 AES-GCM。同时,应避免使用过时的或安全性较弱的密码套件,例如 RC4、DES 等。在 SSLOptions 中,可以通过 cipherSuites 选项来配置允许使用的密码套件列表。

    协议版本选择

    TLS 1.3 相比之前的 TLS 版本,在握手过程和加密算法上都进行了优化,具有更好的性能和安全性。如果条件允许,应优先使用 TLS 1.3 协议。在 SSLOptions 中,可以通过 minVersionmaxVersion 选项来设置允许使用的 TLS 协议版本范围。

    硬件加速 (Hardware Acceleration)

    现代 CPU 通常都提供了硬件加速指令集(例如 AES-NI、AVX-512)来加速密码学运算。确保 OpenSSL 或 BoringSSL 编译时启用了硬件加速支持,可以显著提升 SSL/TLS 的性能。

    异步操作和非阻塞 IO

    Folly/IO 本身就是基于异步和非阻塞 IO 的网络库。利用 AsyncSocket 的异步特性,可以将 SSL/TLS 握手和数据传输操作放在后台线程中执行,避免阻塞主线程,提高应用的并发处理能力。

    连接池和会话缓存

    对于需要频繁建立 SSL/TLS 连接的应用,可以使用连接池来复用已建立的连接,减少连接建立的开销。同时,合理配置 SSL/TLS 会话缓存的大小和过期时间,可以提高会话复用的效率。

    证书链优化

    在服务器端,应尽量减小证书链的长度,避免传输过大的证书链,降低握手延迟。同时,可以使用 OCSP Stapling 或 CRL Distribution Points 等技术来优化证书状态验证过程。

    应用层协议优化

    在应用层协议设计时,可以考虑减少 SSL/TLS 握手的频率。例如,对于长连接应用,可以在连接建立初期进行一次 SSL/TLS 握手,然后在整个连接生命周期内复用该连接。

    通过综合运用以上优化策略,可以在保证安全性的前提下,最大限度地降低 SSL/TLS 的性能开销,构建高性能的安全网络应用。在实际应用中,需要根据具体的场景和需求,权衡安全性和性能,选择合适的 SSL/TLS 配置和优化方案。

    END_OF_CHAPTER

    7. chapter 7: Cursor 与 IOBufQueue:高效数据处理工具 (Cursor and IOBufQueue: Efficient Data Processing Tools)

    7.1 Cursor:IOBuf 中的高效数据读写 (Cursor: Efficient Data Read and Write in IOBuf)

    Cursor 类是 Folly/IO 库中用于在 IOBuf 链中进行高效数据读取和写入的关键工具。它提供了一种轻量级、零拷贝的方式来访问和操作 IOBuf 中的数据,避免了传统的数据复制开销,尤其在处理大型网络数据包时,能够显著提升性能。

    7.1.1 Cursor 的基本概念与优势 (Basic Concepts and Advantages of Cursor)

    Cursor 本质上是一个指向 IOBuf 链中特定位置的指针,它记录了当前在 IOBuf 链中的 IOBuf 节点以及在该节点内的偏移量。与直接操作 IOBuf 的指针相比,Cursor 具有以下优势:

    链式遍历的便捷性Cursor 能够自动处理 IOBuf 链的跳转,当当前 IOBuf 节点的数据读取完毕后,Cursor 会自动移动到下一个 IOBuf 节点,无需手动处理链表的迭代,简化了链式 IOBuf 的遍历操作。
    边界检查与安全性Cursor 提供了边界检查机制,可以防止越界访问 IOBuf 的内存,提高了数据访问的安全性。
    零拷贝读取Cursor 的读取操作通常是零拷贝的,它直接返回指向 IOBuf 内部数据的指针或引用,避免了数据复制,提升了读取效率。
    类型安全的读取接口Cursor 提供了多种类型安全的读取接口,例如 read<T>(),可以方便地读取指定类型的数据,并进行类型转换和字节序处理。

    7.1.2 Cursor 的常用 API 详解 (Detailed Explanation of Common Cursor APIs)

    Cursor 类提供了丰富的 API,用于在 IOBuf 中进行各种数据读取和操作。以下是一些常用的 API 及其详细解释:

    构造函数 (Constructors)
    Cursor 提供了多种构造函数,用于从不同的来源创建 Cursor 对象:
    ▮▮▮▮⚝ Cursor(const IOBuf* iobuf): 从一个 IOBuf 对象创建 Cursor,初始位置指向 IOBuf 的起始位置。
    ▮▮▮▮⚝ Cursor(const IOBuf* iobuf, size_t offset): 从一个 IOBuf 对象和指定的偏移量创建 Cursor,初始位置指向 IOBuf 的指定偏移量。
    ▮▮▮▮⚝ Cursor(const IOBufQueue& queue): 从一个 IOBufQueue 对象创建 Cursor,初始位置指向 IOBufQueue 的头部 IOBuf 的起始位置。

    读取 API (Read APIs)
    Cursor 提供了多种 read 系列的 API,用于从 IOBuf 中读取数据:
    ▮▮▮▮⚝ template <typename T> T readBE(): 从当前位置读取类型 T 的数据,并按照大端字节序 (Big-Endian) 解释。
    ▮▮▮▮⚝ template <typename T> T readLE(): 从当前位置读取类型 T 的数据,并按照小端字节序 (Little-Endian) 解释。
    ▮▮▮▮⚝ template <typename T> T readNative(): 从当前位置读取类型 T 的数据,并按照本地字节序解释。
    ▮▮▮▮⚝ template <typename T> bool tryReadBE(T& val): 尝试从当前位置读取类型 T 的数据(大端字节序),如果读取成功,将数据写入 val 并返回 true,否则返回 false
    ▮▮▮▮⚝ template <typename T> bool tryReadLE(T& val): 尝试从当前位置读取类型 T 的数据(小端字节序),如果读取成功,将数据写入 val 并返回 true,否则返回 false
    ▮▮▮▮⚝ template <typename T> bool tryReadNative(T& val): 尝试从当前位置读取类型 T 的数据(本地字节序),如果读取成功,将数据写入 val 并返回 true,否则返回 false
    ▮▮▮▮⚝ folly::StringPiece readFixedString(size_t len): 从当前位置读取固定长度 len 的字符串,返回 folly::StringPiece 对象,零拷贝。
    ▮▮▮▮⚝ folly::StringPiece tryReadFixedString(size_t len): 尝试从当前位置读取固定长度 len 的字符串,如果读取成功,返回 folly::StringPiece 对象,否则返回空 StringPiece
    ▮▮▮▮⚝ void copyTo(unsigned char* buf, size_t len): 从当前位置复制 len 字节的数据到缓冲区 buf
    ▮▮▮▮⚝ bool tryCopyTo(unsigned char* buf, size_t len): 尝试从当前位置复制 len 字节的数据到缓冲区 buf,如果复制成功,返回 true,否则返回 false

    移动 API (Move APIs)
    Cursor 提供了移动当前位置的 API:
    ▮▮▮▮⚝ void skip(size_t len): 向前跳过 len 字节。
    ▮▮▮▮⚝ bool trySkip(size_t len): 尝试向前跳过 len 字节,如果跳过成功,返回 true,否则返回 false(例如,如果剩余数据不足 len 字节)。
    ▮▮▮▮⚝ void rewind(size_t len): 向后回退 len 字节。
    ▮▮▮▮⚝ bool tryRewind(size_t len): 尝试向后回退 len 字节,如果回退成功,返回 true,否则返回 false(例如,如果已经回退到起始位置之前)。

    其他 API (Other APIs)
    ▮▮▮▮⚝ size_t totalLength(): 返回从当前位置到 IOBuf 链末尾的剩余总长度。
    ▮▮▮▮⚝ size_t currentBufferLength(): 返回当前 IOBuf 节点从当前位置到末尾的剩余长度。
    ▮▮▮▮⚝ bool isAtEnd(): 判断是否已经到达 IOBuf 链的末尾。
    ▮▮▮▮⚝ void resetToStart(): 将 Cursor 重置到 IOBuf 链的起始位置。

    7.1.3 实战代码:使用 Cursor 读取网络数据包头部 (Practical Code: Reading Network Packet Header using Cursor)

    假设我们接收到一个网络数据包,其头部结构如下:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 struct PacketHeader {
    2 uint16_t magicNumber; // 魔数,2 字节,大端字节序
    3 uint8_t version; // 版本号,1 字节
    4 uint32_t payloadLength; // 负载长度,4 字节,大端字节序
    5 };

    我们可以使用 Cursor 来高效地解析这个头部:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/io/IOBuf.h>
    2 #include <folly/io/Cursor.h>
    3 #include <iostream>
    4
    5 using namespace folly;
    6
    7 int main() {
    8 // 模拟接收到的网络数据包,存储在 IOBuf 中
    9 auto iobuf = IOBuf::create(1024);
    10 iobuf->append(7); // 假设头部长度为 7 字节
    11 unsigned char* data = iobuf->writableData();
    12 // 填充头部数据 (示例数据)
    13 data[0] = 0x12; // magicNumber 高字节
    14 data[1] = 0x34; // magicNumber 低字节
    15 data[2] = 0x01; // version
    16 data[3] = 0x00; // payloadLength 高字节
    17 data[4] = 0x00;
    18 data[5] = 0x01;
    19 data[6] = 0x00; // payloadLength 低字节
    20
    21
    22 Cursor cursor(iobuf.get());
    23
    24 PacketHeader header;
    25 header.magicNumber = cursor.readBE<uint16_t>();
    26 header.version = cursor.read<uint8_t>(); // 默认本地字节序,单字节无所谓
    27 header.payloadLength = cursor.readBE<uint32_t>();
    28
    29 std::cout << "Magic Number: 0x" << std::hex << header.magicNumber << std::endl;
    30 std::cout << "Version: " << std::dec << static_cast<int>(header.version) << std::endl;
    31 std::cout << "Payload Length: " << header.payloadLength << std::endl;
    32
    33 return 0;
    34 }

    代码解释:

    1. 我们首先创建了一个 IOBuf 对象 iobuf,并模拟填充了网络数据包的头部数据。
    2. 然后,我们使用 iobuf 创建了一个 Cursor 对象 cursor
    3. 接着,我们使用 cursor.readBE<uint16_t>() 读取 2 字节的魔数,并指定为大端字节序。
    4. 使用 cursor.read<uint8_t>() 读取 1 字节的版本号。
    5. 使用 cursor.readBE<uint32_t>() 读取 4 字节的负载长度,并指定为大端字节序。
    6. 最后,我们打印解析出的头部信息。

    这个例子展示了如何使用 Cursor 简洁高效地从 IOBuf 中读取各种类型的数据,并处理字节序问题。Cursor 的零拷贝特性保证了读取操作的高性能,尤其在处理大型网络数据包时优势更加明显。

    7.2 IOBufQueue:网络数据缓存与分片处理 (IOBufQueue: Network Data Buffering and Fragmentation Handling)

    IOBufQueue 是 Folly/IO 库中用于高效缓存和管理网络接收数据的队列。它基于 IOBuf 链表实现,专门设计用于处理网络数据流,尤其擅长处理 TCP 粘包和分片问题,以及在异步网络编程中缓存接收到的数据。

    7.2.1 IOBufQueue 的基本概念与优势 (Basic Concepts and Advantages of IOBufQueue)

    IOBufQueue 本质上是一个 IOBuf 链表的队列,它提供了线程安全的操作接口,用于在生产者-消费者模式中缓存数据。在网络编程中,IOBufQueue 通常作为网络数据接收缓冲区,其主要优势包括:

    高效的数据缓存IOBufQueue 使用 IOBuf 链表存储数据,可以高效地追加和移除数据,避免了内存复制和重新分配的开销。
    零拷贝操作IOBufQueue 的数据追加和移除操作通常是零拷贝的,它直接操作 IOBuf 链表的指针,减少了数据拷贝,提升了性能。
    分片数据处理:网络数据包可能被分片传输,IOBufQueue 可以缓存接收到的分片数据,直到接收到完整的数据包,方便上层应用进行完整的数据处理。
    线程安全IOBufQueue 提供了线程安全的 API,可以在多线程环境中安全地使用,例如,在网络 IO 线程接收数据,在工作线程处理数据。
    方便的数据访问IOBufQueue 提供了 Cursor 接口,可以方便地使用 Cursor 在队列中读取数据,进行协议解析等操作。

    7.2.2 IOBufQueue 的常用 API 详解 (Detailed Explanation of Common IOBufQueue APIs)

    IOBufQueue 类提供了丰富的 API,用于管理和操作网络数据队列。以下是一些常用的 API 及其详细解释:

    构造函数 (Constructors)
    ▮▮▮▮⚝ IOBufQueue(): 创建一个空的 IOBufQueue 对象。
    ▮▮▮▮⚝ IOBufQueue(Options options): 创建一个 IOBufQueue 对象,并指定选项,例如最大队列长度限制等。

    数据追加 API (Append APIs)
    ▮▮▮▮⚝ void append(std::unique_ptr<IOBuf> buf): 将一个 IOBuf 对象追加到队列的末尾,IOBufQueue 会接管 IOBuf 的所有权。
    ▮▮▮▮⚝ void append(IOBufQueue& other): 将另一个 IOBufQueue 对象 other 中的所有数据移动到当前队列的末尾,other 队列会变为空。
    ▮▮▮▮⚝ void append(const void* data, size_t len): 将一段内存数据 data (长度为 len) 复制到新的 IOBuf 中,并追加到队列的末尾。

    数据移除 API (Consume APIs)
    ▮▮▮▮⚝ std::unique_ptr<IOBuf> front(): 返回队列头部的 IOBuf 对象,但不移除,如果队列为空,返回 nullptr
    ▮▮▮▮⚝ std::unique_ptr<IOBuf> pop_front(): 移除并返回队列头部的 IOBuf 对象,如果队列为空,返回 nullptr
    ▮▮▮▮⚝ std::unique_ptr<IOBuf> split(size_t len): 从队列头部开始,分割出长度为 len 的数据,并返回一个新的 IOBuf 对象,原队列头部数据会被移除,如果队列中数据不足 len,则返回队列中所有数据。
    ▮▮▮▮⚝ void trimStart(size_t len): 从队列头部移除 len 字节的数据,如果队列中数据不足 len,则清空队列。
    ▮▮▮▮⚝ void clear(): 清空队列中的所有数据。

    数据访问 API (Access APIs)
    ▮▮▮▮⚝ size_t frontChainLength(): 返回队列头部 IOBuf 链的总长度。
    ▮▮▮▮⚝ size_t computeChainDataLength(): 返回队列中所有 IOBuf 链的总长度,即队列中缓存的总数据量。
    ▮▮▮▮⚝ bool empty(): 判断队列是否为空。
    ▮▮▮▮⚝ Cursor frontCursor(): 创建一个指向队列头部数据的 Cursor 对象,用于读取队列中的数据。

    其他 API (Other APIs)
    ▮▮▮▮⚝ void shrinkToFit(): 尝试释放队列占用的多余内存。
    ▮▮▮▮⚝ void setLimits(Options options): 设置队列的限制选项,例如最大队列长度等。

    7.2.3 实战代码:使用 IOBufQueue 缓存和处理 TCP 粘包 (Practical Code: Buffering and Handling TCP Packet Aggregation using IOBufQueue)

    TCP 是面向字节流的协议,可能会出现粘包问题,即多个应用层数据包被 TCP 合并成一个 TCP 段发送。IOBufQueue 可以用来缓存接收到的 TCP 数据,并根据应用层协议进行分包处理。

    假设我们定义了一个简单的基于长度的应用层协议,每个数据包的格式为:[数据包长度 (4 字节,大端字节序)] [数据包数据]

    以下代码演示了如何使用 IOBufQueue 接收 TCP 数据,并解析出完整的数据包:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/io/IOBufQueue.h>
    2 #include <folly/io/Cursor.h>
    3 #include <iostream>
    4
    5 using namespace folly;
    6
    7 void processPacket(IOBuf& packet) {
    8 // 处理完整的数据包
    9 std::cout << "处理数据包,长度: " << packet.computeChainDataLength() << std::endl;
    10 // ... 实际的数据包处理逻辑 ...
    11 }
    12
    13 int main() {
    14 IOBufQueue queue;
    15
    16 // 模拟接收到的 TCP 数据 (可能包含粘包)
    17 auto recvBuf1 = IOBuf::create(1024);
    18 recvBuf1->append(8); // 假设第一个 TCP 段包含两个数据包的部分数据
    19 unsigned char* data1 = recvBuf1->writableData();
    20 // 数据包 1 长度 (4 字节)
    21 data1[0] = 0x00; data1[1] = 0x00; data1[2] = 0x00; data1[3] = 0x0A; // 长度 10
    22 // 数据包 1 数据 (部分)
    23 data1[4] = 0x01; data1[5] = 0x02; data1[6] = 0x03; data1[7] = 0x04;
    24
    25 auto recvBuf2 = IOBuf::create(1024);
    26 recvBuf2->append(10); // 假设第二个 TCP 段包含数据包 1 的剩余部分和数据包 2 的部分数据
    27 unsigned char* data2 = recvBuf2->writableData();
    28 // 数据包 1 数据 (剩余部分)
    29 data2[0] = 0x05; data2[1] = 0x06; data2[2] = 0x07; data2[3] = 0x08; data2[4] = 0x09; data2[5] = 0x0A;
    30 // 数据包 2 长度 (4 字节)
    31 data2[6] = 0x00; data2[7] = 0x00; data2[8] = 0x00; data2[9] = 0x05; // 长度 5
    32
    33 auto recvBuf3 = IOBuf::create(1024);
    34 recvBuf3->append(5); // 假设第三个 TCP 段包含数据包 2 的剩余部分
    35 unsigned char* data3 = recvBuf3->writableData();
    36 // 数据包 2 数据 (剩余部分)
    37 data3[0] = 0x0B; data3[1] = 0x0C; data3[2] = 0x0D; data3[3] = 0x0E; data3[4] = 0x0F;
    38
    39
    40 // 将接收到的 TCP 数据追加到 IOBufQueue
    41 queue.append(std::move(recvBuf1));
    42 queue.append(std::move(recvBuf2));
    43 queue.append(std::move(recvBuf3));
    44
    45 while (true) {
    46 Cursor cursor = queue.frontCursor();
    47 if (cursor.totalLength() < sizeof(uint32_t)) {
    48 // 数据不足以解析数据包长度,等待更多数据
    49 break;
    50 }
    51
    52 uint32_t packetLength = cursor.readBE<uint32_t>();
    53 size_t totalPacketLength = sizeof(uint32_t) + packetLength;
    54
    55 if (queue.computeChainDataLength() < totalPacketLength) {
    56 // 完整数据包未到达,等待更多数据
    57 break;
    58 }
    59
    60 // 完整数据包已到达,分割出来并处理
    61 std::unique_ptr<IOBuf> packetBuf = queue.split(totalPacketLength);
    62 processPacket(*packetBuf);
    63 }
    64
    65 return 0;
    66 }

    代码解释:

    1. 我们创建了一个 IOBufQueue 对象 queue 用于缓存接收到的 TCP 数据。
    2. 我们模拟接收了三个 TCP 段 recvBuf1, recvBuf2, recvBuf3,并将它们追加到 queue 中。
    3. 在一个循环中,我们不断尝试从 queue 中解析完整的数据包。
    4. 首先,我们创建一个 Cursor 对象 cursor 指向队列头部数据。
    5. 我们检查队列中是否有足够的数据来读取数据包长度 (4 字节)。
    6. 如果足够,我们读取数据包长度 packetLength
    7. 然后,我们检查队列中是否有足够的数据来构成一个完整的数据包 (长度字段 + 数据字段)。
    8. 如果完整数据包已到达,我们使用 queue.split(totalPacketLength) 从队列中分割出完整的数据包,并调用 processPacket() 函数进行处理。
    9. 如果数据不足以解析数据包长度或完整数据包未到达,则跳出循环,等待接收更多数据。

    这个例子展示了如何使用 IOBufQueue 缓存 TCP 数据,并根据基于长度的应用层协议,处理 TCP 粘包问题,解析出完整的数据包。IOBufQueue 的高效缓存和分片处理能力,使得网络编程更加方便和高效。

    7.3 Cursor 与 IOBufQueue 的组合应用 (Combined Application of Cursor and IOBufQueue)

    CursorIOBufQueue 通常在 Folly/IO 中组合使用,以实现高效的网络数据接收、缓存和解析。IOBufQueue 负责缓存接收到的网络数据,处理分片和粘包问题,而 Cursor 则负责在 IOBufQueue 缓存的数据中进行高效的数据读取和协议解析。

    组合应用的典型场景:

    网络数据接收与协议解析:在网络服务器或客户端程序中,IOBufQueue 作为接收缓冲区,接收来自 Socket 的数据。然后,使用 Cursor 遍历 IOBufQueue 中的数据,解析应用层协议,提取数据包,进行业务逻辑处理。
    数据流处理管道:在数据流处理管道中,IOBufQueue 可以作为数据缓冲队列,连接不同的处理阶段。例如,网络接收阶段将数据放入 IOBufQueue,协议解析阶段从 IOBufQueue 中读取数据,数据处理阶段再从 IOBufQueue 中读取数据。Cursor 则在每个处理阶段用于高效地访问和操作 IOBufQueue 中的数据。
    零拷贝数据处理IOBufQueueCursor 的组合使用,可以实现端到端的零拷贝数据处理。数据从网络接口接收到 IOBufQueue 中,然后使用 Cursor 直接在 IOBuf 内部访问和操作数据,避免了数据在内存中的多次复制,提升了整体性能。

    组合应用的优势:

    高性能:零拷贝的数据处理方式,减少了内存复制开销,提升了网络 IO 和数据处理的性能。
    高效率Cursor 提供了便捷的 API,简化了 IOBuf 链的遍历和数据读取操作,提高了开发效率。
    灵活性IOBufQueue 提供了灵活的数据缓存和管理机制,可以适应各种网络应用场景。
    可靠性IOBufQueue 的线程安全性和 Cursor 的边界检查机制,提高了程序的可靠性和安全性。

    实战案例:构建高性能 HTTP 服务器

    在构建高性能 HTTP 服务器时,IOBufQueue 可以用于缓存接收到的 HTTP 请求数据,Cursor 可以用于解析 HTTP 请求头和请求体。

    接收数据:使用 AsyncSocket 接收 HTTP 请求数据,并将接收到的数据追加到 IOBufQueue 中。
    请求解析:创建一个 Cursor 对象指向 IOBufQueue 的头部数据,使用 Cursor 的 API 解析 HTTP 请求行、请求头字段等。
    请求体处理:如果 HTTP 请求包含请求体,可以使用 Cursor 继续读取请求体数据,或者将请求体数据从 IOBufQueue 中分割出来,进行进一步处理。
    响应发送:构建 HTTP 响应数据,将其写入 IOBuf 中,然后使用 AsyncSocket 发送响应数据。

    通过 IOBufQueueCursor 的组合应用,可以构建高性能、高效率的 HTTP 服务器,充分利用 Folly/IO 的零拷贝和异步特性。

    7.4 实战代码:使用 Cursor 和 IOBufQueue 解析网络协议 (Practical Code: Parsing Network Protocols Using Cursor and IOBufQueue)

    本节将通过一个更完整的实战代码示例,演示如何使用 CursorIOBufQueue 解析一个自定义的网络协议。

    自定义协议定义:

    我们定义一个简单的基于长度和类型的网络协议,数据包格式如下:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 [魔数 (2 字节,固定值:0xABCD)] [版本号 (1 字节)] [消息类型 (1 字节)] [数据包长度 (4 字节,大端字节序,包含头部和数据)] [数据]

    魔数 (Magic Number): 固定值 0xABCD,用于数据包的起始标识。
    版本号 (Version): 协议版本号。
    消息类型 (MessageType): 消息类型,例如:0x01 - 请求消息,0x02 - 响应消息,0x03 - 通知消息。
    数据包长度 (Packet Length): 整个数据包的长度,包括头部和数据部分。
    数据 (Data): 实际的消息数据。

    代码示例:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/io/IOBufQueue.h>
    2 #include <folly/io/Cursor.h>
    3 #include <iostream>
    4
    5 using namespace folly;
    6
    7 // 协议常量定义
    8 const uint16_t MAGIC_NUMBER = 0xABCD;
    9 const size_t HEADER_SIZE = 2 + 1 + 1 + 4; // 魔数 + 版本号 + 消息类型 + 数据包长度
    10
    11 // 消息类型枚举
    12 enum class MessageType : uint8_t {
    13 REQUEST = 0x01,
    14 RESPONSE = 0x02,
    15 NOTIFICATION = 0x03
    16 };
    17
    18 // 数据包结构体
    19 struct Packet {
    20 uint16_t magicNumber;
    21 uint8_t version;
    22 MessageType messageType;
    23 uint32_t packetLength;
    24 IOBuf payload; // 使用 IOBuf 存储 payload,方便零拷贝处理
    25 };
    26
    27 // 解析数据包函数
    28 std::optional<Packet> parsePacket(IOBufQueue& queue) {
    29 Cursor cursor = queue.frontCursor();
    30
    31 // 检查数据是否足够解析头部
    32 if (cursor.totalLength() < HEADER_SIZE) {
    33 return std::nullopt; // 数据不足,返回空 Optional
    34 }
    35
    36 Packet packet;
    37 packet.magicNumber = cursor.readBE<uint16_t>();
    38
    39 // 检查魔数
    40 if (packet.magicNumber != MAGIC_NUMBER) {
    41 std::cerr << "Error: Invalid magic number: 0x" << std::hex << packet.magicNumber << std::endl;
    42 queue.trimStart(1); // 移除 1 字节,尝试下一个字节作为包起始
    43 return std::nullopt;
    44 }
    45
    46 packet.version = cursor.read<uint8_t>();
    47 packet.messageType = static_cast<MessageType>(cursor.read<uint8_t>());
    48 packet.packetLength = cursor.readBE<uint32_t>();
    49
    50 // 检查数据包长度是否合法 (至少包含头部长度)
    51 if (packet.packetLength < HEADER_SIZE) {
    52 std::cerr << "Error: Invalid packet length: " << packet.packetLength << std::endl;
    53 queue.trimStart(HEADER_SIZE); // 移除头部长度的数据,丢弃错误包
    54 return std::nullopt;
    55 }
    56
    57 // 检查是否接收到完整的数据包
    58 if (queue.computeChainDataLength() < packet.packetLength) {
    59 return std::nullopt; // 完整数据包未到达,返回空 Optional
    60 }
    61
    62 // 分割出完整的数据包 (包括头部)
    63 std::unique_ptr<IOBuf> fullPacketBuf = queue.split(packet.packetLength);
    64 // 创建 Cursor 用于访问完整数据包
    65 Cursor packetCursor(fullPacketBuf.get());
    66 packetCursor.skip(HEADER_SIZE); // 跳过头部,指向 payload 起始位置
    67 packet.payload = packetCursor.clone(); // 使用 clone 创建 payload 的 IOBuf,零拷贝
    68
    69 return packet; // 返回解析成功的数据包
    70 }
    71
    72 void processPacket(const Packet& packet) {
    73 std::cout << "收到数据包:" << std::endl;
    74 std::cout << " 魔数: 0x" << std::hex << packet.magicNumber << std::endl;
    75 std::cout << " 版本号: " << std::dec << static_cast<int>(packet.version) << std::endl;
    76 std::cout << " 消息类型: " << static_cast<int>(packet.messageType) << std::endl;
    77 std::cout << " 数据包长度: " << packet.packetLength << std::endl;
    78 std::cout << " Payload 长度: " << packet.payload.computeChainDataLength() << std::endl;
    79 // ... 实际的数据包处理逻辑 ...
    80 }
    81
    82
    83 int main() {
    84 IOBufQueue queue;
    85
    86 // 模拟接收到的 TCP 数据 (包含多个数据包和部分数据)
    87 auto recvBuf1 = IOBuf::create(1024);
    88 recvBuf1->append(10);
    89 unsigned char* data1 = recvBuf1->writableData();
    90 // 数据包 1 头部 (部分)
    91 data1[0] = 0xAB; data1[1] = 0xCD; // 魔数
    92 data1[2] = 0x01; // 版本号
    93 data1[3] = 0x01; // 消息类型
    94 data1[4] = 0x00; data1[5] = 0x00; data1[6] = 0x00; data1[7] = 0x10; // 数据包长度 16
    95 data1[8] = 0x01; data1[9] = 0x02; // 数据包 1 数据 (部分)
    96
    97 auto recvBuf2 = IOBuf::create(1024);
    98 recvBuf2->append(20);
    99 unsigned char* data2 = recvBuf2->writableData();
    100 // 数据包 1 数据 (剩余部分)
    101 data2[0] = 0x03; data2[1] = 0x04; data2[2] = 0x05; data2[3] = 0x06; data2[4] = 0x07; data2[5] = 0x08; data2[6] = 0x09; data2[7] = 0x0A;
    102 // 数据包 2 头部
    103 data2[8] = 0xAB; data2[9] = 0xCD; // 魔数
    104 data2[10] = 0x02; // 版本号
    105 data2[11] = 0x02; // 消息类型
    106 data2[12] = 0x00; data2[13] = 0x00; data2[14] = 0x00; data2[15] = 0x0F; // 数据包长度 15
    107 // 数据包 2 数据
    108 data2[16] = 0x0B; data2[17] = 0x0C; data2[18] = 0x0D; data2[19] = 0x0E;
    109
    110 // 将接收到的 TCP 数据追加到 IOBufQueue
    111 queue.append(std::move(recvBuf1));
    112 queue.append(std::move(recvBuf2));
    113
    114
    115 while (true) {
    116 std::optional<Packet> packetOpt = parsePacket(queue);
    117 if (packetOpt.has_value()) {
    118 processPacket(packetOpt.value());
    119 } else {
    120 // 没有解析到完整的数据包,等待更多数据
    121 break;
    122 }
    123 }
    124
    125 return 0;
    126 }

    代码解释:

    1. 协议定义:定义了协议常量 MAGIC_NUMBERHEADER_SIZE,消息类型枚举 MessageType,以及数据包结构体 Packet
    2. parsePacket() 函数
      ▮▮▮▮⚝ 从 IOBufQueue 的头部创建一个 Cursor 对象。
      ▮▮▮▮⚝ 检查队列中是否有足够的数据解析头部。
      ▮▮▮▮⚝ 读取魔数,并验证魔数是否正确。如果魔数错误,则丢弃 1 字节,尝试重新解析。
      ▮▮▮▮⚝ 读取版本号、消息类型和数据包长度。
      ▮▮▮▮⚝ 验证数据包长度是否合法。
      ▮▮▮▮⚝ 检查是否接收到完整的数据包。
      ▮▮▮▮⚝ 如果接收到完整的数据包,使用 queue.split() 分割出完整的数据包。
      ▮▮▮▮⚝ 创建 Cursor 指向完整数据包的 payload 部分,并使用 clone() 创建 payload 的 IOBuf (零拷贝)。
      ▮▮▮▮⚝ 返回解析成功的 Packet 对象。如果解析失败或数据不足,返回 std::nullopt
    3. processPacket() 函数
      ▮▮▮▮⚝ 接收解析出的 Packet 对象,打印数据包信息,并进行实际的数据包处理逻辑。
    4. main() 函数
      ▮▮▮▮⚝ 创建 IOBufQueue 对象。
      ▮▮▮▮⚝ 模拟接收 TCP 数据,包含两个完整的数据包和一个部分数据包。
      ▮▮▮▮⚝ 在一个循环中,不断调用 parsePacket() 函数尝试解析数据包。
      ▮▮▮▮⚝ 如果解析成功,调用 processPacket() 函数处理数据包。
      ▮▮▮▮⚝ 如果解析失败 (返回 std::nullopt),则跳出循环,等待接收更多数据。

    这个实战代码示例展示了如何使用 CursorIOBufQueue 组合解析一个自定义的网络协议,包括数据接收、缓存、协议解析、错误处理和数据包分发等关键步骤。通过这个示例,读者可以更深入地理解 CursorIOBufQueue 在网络编程中的应用,并掌握使用它们构建高效网络应用的技巧。

    END_OF_CHAPTER

    8. chapter 8: 高级主题与实战案例 (Advanced Topics and Practical Case Studies)

    8.1 Folly/IO 的性能调优与最佳实践 (Performance Tuning and Best Practices of Folly/IO)

    Folly/IO 以其高性能和异步特性而闻名,但在实际应用中,为了充分发挥其潜力,并构建真正高效的网络应用,性能调优和遵循最佳实践至关重要。本节将深入探讨 Folly/IO 的性能调优技巧,并总结一些最佳实践,帮助读者构建更快速、更稳定的网络服务。

    8.1.1 性能分析与瓶颈识别 (Performance Analysis and Bottleneck Identification)

    性能调优的第一步是准确地识别性能瓶颈。在 Folly/IO 应用中,常见的性能瓶颈可能出现在以下几个方面:

    CPU 密集型操作:例如,复杂的协议解析、数据压缩/解压缩、加密/解密等计算密集型任务会消耗大量的 CPU 资源,影响整体性能。
    内存分配与拷贝:频繁的内存分配和数据拷贝操作,尤其是在处理大量网络数据时,会成为性能瓶颈。Folly/IO 的 IOBuf 旨在减少数据拷贝,但仍需注意避免不必要的拷贝。
    IO 阻塞:尽管 Folly/IO 提供了异步 IO,但如果使用不当,例如在回调函数中执行阻塞操作,仍然会导致性能下降。
    锁竞争:在高并发场景下,不合理的锁使用会导致线程竞争,降低并发性能。
    网络延迟:网络延迟是网络应用性能的重要影响因素,需要通过合理的协议设计和网络优化来降低延迟。

    为了识别性能瓶颈,可以使用以下工具和方法:

    性能剖析工具 (Profiling Tools):使用 perf (Linux)、Instruments (macOS) 或 VTune Amplifier 等性能剖析工具,可以分析 CPU 使用率、内存分配、函数调用关系等,找出性能瓶颈所在。
    火焰图 (Flame Graph):火焰图是一种直观的可视化性能剖析结果的工具,可以清晰地展示 CPU 的时间都消耗在哪些函数上。
    指标监控 (Metrics Monitoring):监控关键性能指标,例如吞吐量(Throughput)、延迟(Latency)、CPU 使用率、内存使用率、错误率等,可以帮助实时了解系统性能状况,及时发现问题。可以使用 fizz-metrics 或 Prometheus 等监控系统。
    压力测试 (Stress Testing):通过模拟高负载场景,例如使用 wrkbombardier 或自定义的压力测试工具,可以测试系统的性能极限,找出在高负载下的瓶颈。

    8.1.2 IOBuf 的性能优化 (Performance Optimization of IOBuf)

    IOBuf 是 Folly/IO 的核心组件,其性能直接影响到整个 Folly/IO 应用的性能。以下是一些 IOBuf 的性能优化技巧:

    避免不必要的拷贝IOBuf 的零拷贝特性旨在减少数据拷贝,但在使用时仍需注意避免不必要的拷贝操作。例如,尽量使用 IOBuf::clone()IOBuf::share() 来共享数据,而不是 IOBuf::copy() 来复制数据。
    预分配内存:在创建 IOBuf 时,如果预先知道数据大小,可以使用 IOBuf::create(size)IOBuf::preallocate(size) 来预分配内存,避免后续的内存 re-allocation。
    使用链式 IOBuf:对于需要拼接的数据,可以使用链式 IOBuf,通过 IOBuf::prependChain()IOBuf::appendChain() 将多个 IOBuf 链接起来,避免数据拷贝。
    减少内存碎片:频繁地创建和销毁小块 IOBuf 可能会导致内存碎片,影响性能。可以通过对象池 (Object Pool) 或内存池 (Memory Pool) 等技术来管理 IOBuf 的内存,减少内存碎片。
    合理使用 CursorCursor 提供了高效的 IOBuf 数据访问方式,可以避免不必要的边界检查和数据拷贝。在需要频繁读取或写入 IOBuf 数据时,应优先使用 Cursor

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 // 示例:使用 IOBuf 构建零拷贝数据处理管道
    2 #include <folly/io/IOBuf.h>
    3 #include <folly/String.h>
    4 #include <iostream>
    5
    6 using namespace folly;
    7
    8 int main() {
    9 // 创建 IOBuf
    10 auto iobuf = IOBuf::copyBuffer("Hello, Folly/IO!");
    11
    12 // 创建 Cursor 用于读取 IOBuf 数据
    13 Cursor cursor(iobuf.get());
    14
    15 // 读取数据,零拷贝
    16 StringPiece data;
    17 cursor.readFixed(data, iobuf->length());
    18
    19 std::cout << "Data: " << data.toString() << std::endl;
    20
    21 // 修改 IOBuf 数据,零拷贝
    22 MutableCursor mutableCursor(iobuf.get());
    23 mutableCursor.skip(7); // 跳过 "Hello, "
    24 mutableCursor.writeFixed("World"); // 修改为 "World"
    25
    26 Cursor cursor2(iobuf.get());
    27 StringPiece data2;
    28 cursor2.readFixed(data2, iobuf->length());
    29 std::cout << "Modified Data: " << data2.toString() << std::endl;
    30
    31 return 0;
    32 }

    8.1.3 异步 Socket 的性能优化 (Performance Optimization of Asynchronous Socket)

    异步 Socket 是 Folly/IO 高性能网络编程的基础。以下是一些异步 Socket 的性能优化技巧:

    合理设置 Socket 参数:根据应用场景,合理设置 Socket 的 TCP 参数,例如 TCP_NODELAYSO_REUSEADDRSO_KEEPALIVESO_SNDBUFSO_RCVBUF 等。例如,对于低延迟应用,可以启用 TCP_NODELAY;对于高并发连接,可以启用 SO_REUSEADDR
    优化 EventBase 线程模型EventBase 是 Folly/IO 异步 IO 的核心,其线程模型直接影响到异步 Socket 的性能。可以根据 CPU 核心数和应用负载,调整 EventBase 的线程数。通常情况下,EventBase 线程数设置为 CPU 核心数或 CPU 核心数的两倍即可。
    避免在 IO 线程中执行阻塞操作:异步 IO 的核心思想是将 IO 操作放在后台线程执行,避免阻塞主线程。在 Folly/IO 中,IO 回调函数通常在 EventBase 线程中执行,因此在回调函数中应避免执行阻塞操作,例如文件 IO、数据库查询等。如果必须执行阻塞操作,应将其提交到独立的线程池中执行。
    使用 AsyncSocket::setReadBufferSettings() 优化读取性能AsyncSocket::setReadBufferSettings() 可以配置读取缓冲区的大小和水位线,合理设置这些参数可以提高读取性能,减少系统调用次数。
    连接池 (Connection Pool):对于需要频繁建立和断开连接的场景,例如 HTTP 客户端,可以使用连接池来复用连接,减少连接建立和断开的开销。可以使用 folly::io::async::Requestfolly::io::async::Response 结合连接池来构建高效的 HTTP 客户端。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 // 示例:设置 AsyncSocket TCP_NODELAY
    2 #include <folly/io/async/AsyncSocket.h>
    3 #include <folly/io/async/EventBase.h>
    4 #include <folly/SocketAddress.h>
    5 #include <iostream>
    6
    7 using namespace folly;
    8 using namespace folly::io::async;
    9
    10 int main() {
    11 EventBase evb;
    12 AsyncSocket socket(&evb);
    13
    14 // 设置 TCP_NODELAY
    15 socket.setTcpNoDelay(true);
    16
    17 // 连接到服务器
    18 SocketAddress addr("127.0.0.1", 8080);
    19 socket.connect(nullptr, addr, 0);
    20
    21 // ... 其他操作 ...
    22
    23 evb.loopForever();
    24 return 0;
    25 }

    8.1.4 最佳实践总结 (Summary of Best Practices)

    尽早进行性能测试:在开发初期就应该进行性能测试,及早发现性能问题,避免在后期重构代码。
    持续监控性能:在生产环境中持续监控性能指标,及时发现和解决性能问题。
    代码审查与优化:定期进行代码审查,检查代码中是否存在潜在的性能问题,并进行优化。
    了解 Folly/IO 的内部机制:深入了解 IOBufAsyncSocketEventBase 等 Folly/IO 组件的内部机制,可以更好地进行性能调优。
    参考 Folly/IO 的示例代码和文档:Folly/IO 提供了丰富的示例代码和详细的文档,可以参考这些资源来学习和使用 Folly/IO,避免常见的错误和陷阱。
    社区交流与学习:积极参与 Folly/IO 社区的交流,与其他开发者分享经验,学习最佳实践。

    8.2 Folly/IO 与其他 Folly 库的集成 (Integration of Folly/IO with Other Folly Libraries)

    Folly 是一个庞大而强大的 C++ 库集合,Folly/IO 作为其中的重要组成部分,可以与其他 Folly 库无缝集成,共同构建更复杂、更高效的应用。本节将介绍 Folly/IO 与其他常用 Folly 库的集成应用。

    8.2.1 与 Futures/Promises 的集成 (Integration with Futures/Promises)

    FuturesPromises 是 Folly 提供的异步编程利器,可以简化异步代码的编写和管理。Folly/IO 的异步操作,例如 AsyncSocket::connect()AsyncSocket::read()AsyncSocket::write() 等,可以很容易地与 Futures/Promises 集成。

    通过 Promise 可以将异步操作的结果传递给 Future,从而实现异步操作的链式调用和错误处理。例如,可以使用 PromiseAsyncSocket 的连接成功回调函数中设置 Future 的值,然后在 Future 上注册后续的操作。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 // 示例:使用 Futures/Promises 处理 AsyncSocket 连接
    2 #include <folly/io/async/AsyncSocket.h>
    3 #include <folly/io/async/EventBase.h>
    4 #include <folly/SocketAddress.h>
    5 #include <folly/futures/Future.h>
    6 #include <folly/futures/Promise.h>
    7 #include <iostream>
    8
    9 using namespace folly;
    10 using namespace folly::io::async;
    11
    12 Future<AsyncSocket*> connectAsync(EventBase& evb, const SocketAddress& addr) {
    13 Promise<AsyncSocket*> promise;
    14 AsyncSocket* socket = new AsyncSocket(&evb);
    15
    16 socket->connect(nullptr, addr, 0, [&, socket](AsyncSocket* sock, int err) {
    17 if (err == 0) {
    18 promise.setValue(socket); // 连接成功,设置 Promise 的值
    19 } else {
    20 promise.setException(std::runtime_error(folly::stringPrintf("Connect failed: %s", strerror(err)))); // 连接失败,设置 Promise 的异常
    21 delete socket; // 释放资源
    22 }
    23 });
    24 return promise.getFuture();
    25 }
    26
    27 int main() {
    28 EventBase evb;
    29 SocketAddress addr("127.0.0.1", 8080);
    30
    31 connectAsync(evb, addr)
    32 .then([](AsyncSocket* socket) {
    33 std::cout << "Connected successfully!" << std::endl;
    34 // ... 后续操作 ...
    35 socket->close();
    36 delete socket;
    37 })
    38 .onError([](const std::exception& ex) {
    39 std::cerr << "Connection error: " << ex.what() << std::endl;
    40 });
    41
    42 evb.loopForever();
    43 return 0;
    44 }

    8.2.2 与 Executor 的集成 (Integration with Executor)

    Executor 是 Folly 提供的线程池抽象,用于管理和调度任务的执行。可以将 Folly/IO 的回调函数提交到 Executor 中执行,从而将 IO 线程与业务逻辑线程分离,提高系统的并发性和响应速度。

    例如,可以将 AsyncSocket 的读取回调函数提交到 Executor 中执行,在独立的线程池中处理读取到的数据,避免阻塞 IO 线程。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 // 示例:使用 Executor 处理 AsyncSocket 读取回调
    2 #include <folly/io/async/AsyncSocket.h>
    3 #include <folly/io/async/EventBase.h>
    4 #include <folly/SocketAddress.h>
    5 #include <folly/executors/ThreadPoolExecutor.h>
    6 #include <iostream>
    7
    8 using namespace folly;
    9 using namespace folly::io::async;
    10 using namespace folly::executors;
    11
    12 class MyConnection : public AsyncSocket::ReadCallback {
    13 public:
    14 MyConnection(EventBase& evb, ThreadPoolExecutor& executor) : socket_(&evb), executor_(executor) {
    15 socket_.setReadCB(this);
    16 }
    17
    18 void connect(const SocketAddress& addr) {
    19 socket_.connect(nullptr, addr, 0);
    20 }
    21
    22 void getReadBuffer(void** bufReturn, size_t* sizeReturn) override {
    23 *bufReturn = readBuffer_;
    24 *sizeReturn = sizeof(readBuffer_);
    25 }
    26
    27 void readDataAvailable(size_t len) noexcept override {
    28 // 将数据处理任务提交到 Executor 线程池
    29 executor_.add([this, len] {
    30 processData(readBuffer_, len);
    31 });
    32 socket_.notifyReadReady(); // 继续读取数据
    33 }
    34
    35 void readEOF() noexcept override {
    36 std::cout << "Connection EOF" << std::endl;
    37 delete this; // 连接结束,释放资源
    38 }
    39
    40 void readErr(const AsyncSocketException& ex) noexcept override {
    41 std::cerr << "Read error: " << ex.what() << std::endl;
    42 delete this; // 读取错误,释放资源
    43 }
    44
    45 private:
    46 void processData(void* data, size_t len) {
    47 // 在独立的线程池中处理数据
    48 std::cout << "Processing data in thread: " << std::this_thread::get_id() << std::endl;
    49 // ... 数据处理逻辑 ...
    50 }
    51
    52 private:
    53 AsyncSocket socket_;
    54 ThreadPoolExecutor& executor_;
    55 char readBuffer_[1024];
    56 };
    57
    58 int main() {
    59 EventBase evb;
    60 ThreadPoolExecutor executor(4); // 创建一个包含 4 个线程的线程池
    61 SocketAddress addr("127.0.0.1", 8080);
    62
    63 MyConnection* connection = new MyConnection(evb, executor);
    64 connection->connect(addr);
    65
    66 evb.loopForever();
    67 return 0;
    68 }

    8.2.3 与 ConcurrentSkipListSet 等并发数据结构的集成 (Integration with ConcurrentSkipListSet and other Concurrent Data Structures)

    Folly 提供了丰富的并发数据结构,例如 ConcurrentSkipListSetConcurrentHashMap 等,可以用于构建高并发的网络应用。Folly/IO 可以与这些并发数据结构集成,实现高效的数据共享和访问。

    例如,可以使用 ConcurrentSkipListSet 维护在线用户列表,在 AsyncSocket 的连接建立和断开回调函数中更新列表,实现高并发的在线用户管理。

    8.2.4 与其他 Folly 库的集成应用场景 (Integration Application Scenarios with Other Folly Libraries)

    使用 FBStringStringPiece 处理字符串:Folly 提供的 FBStringStringPiece 提供了高效的字符串操作,可以用于处理网络协议中的字符串数据,例如 HTTP 头部、URL 等。
    使用 json 库解析和生成 JSON 数据:Folly 的 json 库提供了快速的 JSON 解析和生成功能,可以用于处理基于 JSON 的网络协议。
    使用 Uri 库解析 URL:Folly 的 Uri 库提供了强大的 URL 解析功能,可以用于处理 HTTP 请求中的 URL。
    使用 wangle 构建高性能网络服务框架wangle 是基于 Folly 构建的网络服务框架,提供了更高级的网络编程抽象,例如 Pipeline、Filter 等,可以简化高性能网络服务的开发。

    8.3 基于 Folly/IO 构建复杂网络应用案例 (Case Studies of Building Complex Network Applications Based on Folly/IO)

    Folly/IO 凭借其高性能和异步特性,非常适合构建各种复杂的网络应用。本节将通过两个案例,介绍如何使用 Folly/IO 构建高性能代理服务器和分布式消息队列系统。

    8.3.1 构建高性能代理服务器 (Building a High-Performance Proxy Server)

    代理服务器是位于客户端和服务器之间的中间服务器,可以转发客户端的请求到服务器,并将服务器的响应返回给客户端。高性能代理服务器需要具备以下特点:

    高并发连接处理能力:能够同时处理大量的客户端连接。
    低延迟请求转发:尽可能快地将客户端请求转发到服务器,并将服务器响应返回给客户端。
    高吞吐量数据传输:能够高效地传输大量的数据。
    支持多种协议:例如 HTTP、HTTPS、SOCKS 等。
    可扩展性:易于扩展,以应对不断增长的负载。

    使用 Folly/IO 构建高性能代理服务器的关键技术包括:

    异步 Socket:使用 AsyncSocket 处理客户端和服务器的连接,实现非阻塞 IO。
    IOBuf:使用 IOBuf 高效地管理和传输网络数据,减少数据拷贝。
    EventBase:使用 EventBase 管理异步事件,提高并发处理能力。
    连接池:使用连接池复用与服务器的连接,减少连接建立和断开的开销。
    Pipeline:可以使用 wangle 框架的 Pipeline 机制,构建灵活的请求处理流程,例如请求解析、转发、响应处理等。

    代理服务器架构示例:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 +-------------------+ +-------------------+ +-------------------+
    2 | Client (AsyncSocket) | --> | Proxy Server (AsyncServerSocket, AsyncSocket, EventBase, IOBuf, Connection Pool) | --> | Server (External) |
    3 +-------------------+ +-------------------+ +-------------------+

    核心组件:

    AsyncServerSocket: 监听客户端连接。
    AsyncSocket: 处理客户端和服务器的连接。
    EventBase: 事件循环,管理异步事件。
    IOBuf: 数据缓冲区,高效数据传输。
    连接池 (Connection Pool): 复用与服务器的连接。
    请求解析与转发模块: 解析客户端请求,选择目标服务器,转发请求。
    响应处理模块: 处理服务器响应,返回给客户端。

    关键步骤:

    1. 监听客户端连接: 使用 AsyncServerSocket 监听客户端连接请求。
    2. 建立客户端连接: 当有客户端连接请求到达时,创建 AsyncSocket 处理客户端连接。
    3. 解析客户端请求: 从客户端 AsyncSocket 读取数据,使用 Cursor 解析客户端请求,例如 HTTP 请求头。
    4. 选择目标服务器: 根据请求内容,选择目标服务器地址。
    5. 建立服务器连接 (连接池): 从连接池中获取与目标服务器的连接,如果连接池中没有可用连接,则建立新的 AsyncSocket 连接到目标服务器。
    6. 转发请求: 将客户端请求数据通过服务器 AsyncSocket 转发到目标服务器。
    7. 接收服务器响应: 从服务器 AsyncSocket 读取服务器响应数据。
    8. 转发响应: 将服务器响应数据通过客户端 AsyncSocket 转发给客户端。
    9. 连接管理: 管理客户端和服务器连接的生命周期,处理连接断开和错误。
    10. 连接池管理: 定期检查和清理连接池中的连接,保持连接池的健康状态。

    8.3.2 构建分布式消息队列系统 (Building a Distributed Message Queue System)

    分布式消息队列系统 (Distributed Message Queue System) 是一种用于在分布式系统中传递消息的中间件,具有解耦、异步、削峰等优点。高性能分布式消息队列系统需要具备以下特点:

    高吞吐量消息收发:能够快速接收和发送大量的消息。
    低延迟消息传递:尽可能快地将消息从生产者传递到消费者。
    消息持久化:保证消息的可靠性,即使系统发生故障,消息也不会丢失。
    消息顺序性:保证消息的顺序性,按照生产者发送的顺序传递给消费者。
    可扩展性:易于扩展,以应对不断增长的消息量和消费者数量。

    使用 Folly/IO 构建分布式消息队列系统的关键技术包括:

    异步 Socket:使用 AsyncSocket 实现生产者、Broker 和消费者之间的网络通信,实现非阻塞 IO。
    IOBufQueue:使用 IOBufQueue 缓存接收到的网络数据,处理分片数据。
    EventBase:使用 EventBase 管理异步事件,提高并发处理能力。
    持久化存储:使用 RocksDB 或其他高性能 Key-Value 存储系统持久化消息。
    分布式协调:使用 ZooKeeper 或 etcd 等分布式协调服务管理 Broker 节点和消费者节点。
    消息路由与分发:实现消息路由和分发策略,将消息路由到正确的消费者。

    消息队列系统架构示例:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 +-------------------+ +-------------------+ +-------------------+ +-------------------+
    2 | Producer (AsyncSocket) | --> | Broker (AsyncServerSocket, AsyncSocket, EventBase, IOBufQueue, RocksDB, ZooKeeper) | --> | Broker (AsyncServerSocket, AsyncSocket, EventBase, IOBufQueue, RocksDB, ZooKeeper) | --> | Consumer (AsyncSocket) |
    3 +-------------------+ +-------------------+ +-------------------+ +-------------------+

    核心组件:

    Producer (生产者): 负责生产消息,通过 AsyncSocket 发送消息到 Broker。
    Broker (消息 Broker): 消息队列的核心组件,负责接收、存储、路由和分发消息。
    ▮▮▮▮⚝ AsyncServerSocket: 监听生产者和消费者的连接。
    ▮▮▮▮⚝ AsyncSocket: 处理生产者和消费者的连接。
    ▮▮▮▮⚝ EventBase: 事件循环,管理异步事件。
    ▮▮▮▮⚝ IOBufQueue: 缓存接收到的消息数据。
    ▮▮▮▮⚝ RocksDB (或其他持久化存储): 持久化存储消息。
    ▮▮▮▮⚝ ZooKeeper (或其他分布式协调服务): 管理 Broker 节点和消费者节点,实现分布式协调。
    ▮▮▮▮⚝ 消息队列 (Message Queue): 存储消息的队列数据结构,例如使用 std::deque 或更高效的并发队列。
    ▮▮▮▮⚝ 消息路由与分发模块: 根据消息的 Topic 或其他属性,将消息路由到正确的消费者。
    Consumer (消费者): 负责从 Broker 订阅消息,通过 AsyncSocket 接收消息并进行处理。

    关键步骤:

    1. 生产者发送消息: 生产者通过 AsyncSocket 连接到 Broker,将消息封装成 IOBuf 发送给 Broker。
    2. Broker 接收消息: Broker 使用 AsyncServerSocket 监听生产者连接,使用 AsyncSocket 接收生产者发送的消息,并将消息存储到 IOBufQueue 中。
    3. 消息持久化: Broker 将 IOBufQueue 中的消息持久化到 RocksDB 或其他持久化存储系统中,保证消息的可靠性。
    4. 消息路由与分发: Broker 根据消息的 Topic 或其他属性,将消息路由到订阅该 Topic 的消费者队列。
    5. 消费者订阅消息: 消费者通过 AsyncSocket 连接到 Broker,订阅感兴趣的 Topic。
    6. Broker 推送消息: Broker 从消费者队列中取出消息,通过 AsyncSocket 推送给消费者。
    7. 消费者接收消息: 消费者通过 AsyncSocket 接收 Broker 推送的消息,并进行处理。
    8. 消息确认 (ACK): 消费者处理完消息后,向 Broker 发送消息确认 (ACK),Broker 收到 ACK 后,可以删除已持久化的消息。
    9. 分布式协调: 使用 ZooKeeper 或 etcd 等分布式协调服务管理 Broker 节点和消费者节点,实现 Broker 节点的选举、故障转移、消费者节点的注册和发现等功能。

    8.4 Folly/IO 的未来发展趋势展望 (Future Development Trends of Folly/IO)

    Folly/IO 作为 Facebook 开源的高性能 IO 库,一直在不断发展和演进。展望未来,Folly/IO 的发展趋势可能包括以下几个方面:

    QUIC 协议支持:QUIC (Quick UDP Internet Connections) 是 Google 开发的一种基于 UDP 的新型传输协议,旨在提供更低的延迟和更高的可靠性。随着 QUIC 协议的普及,Folly/IO 可能会增加对 QUIC 协议的支持,提供更高效的网络传输能力。
    IO_URING 集成:IO_URING 是 Linux 内核提供的一种新的异步 IO 接口,可以进一步提高异步 IO 的性能。Folly/IO 可能会考虑集成 IO_URING,利用 IO_URING 的优势,提升性能。
    Rust 语言互操作性:Rust 语言以其高性能和安全性而受到越来越多的关注。Folly/IO 可能会加强与 Rust 语言的互操作性,例如提供 Rust 语言的绑定,方便 Rust 开发者使用 Folly/IO。
    更高级的网络协议支持:Folly/IO 可能会增加对更多网络协议的支持,例如 gRPC、WebSocket 等,提供更丰富的网络编程功能。
    性能持续优化:Folly/IO 团队会持续进行性能优化,例如改进 IOBuf 的内存管理、优化异步 Socket 的实现、提升 EventBase 的调度效率等,不断提升 Folly/IO 的性能。
    易用性提升:Folly/IO 可能会在易用性方面进行改进,例如提供更简洁的 API、更完善的文档、更丰富的示例代码,降低 Folly/IO 的学习和使用门槛。
    云原生 (Cloud Native) 支持:随着云原生技术的兴起,Folly/IO 可能会加强对云原生环境的支持,例如与 Kubernetes 等云平台更好地集成,提供更适合云原生应用的特性。

    总而言之,Folly/IO 的未来发展将继续围绕高性能、异步、易用性等核心目标,不断吸收新的技术和理念,为开发者提供更强大、更便捷的网络编程工具,助力构建下一代高性能网络应用。

    END_OF_CHAPTER

    9. chapter 9: 总结与展望 (Summary and Outlook)

    9.1 Folly/IO 的核心价值回顾 (Review of the Core Value of Folly/IO)

    在本书的尾声,我们再次回顾 Folly/IO 这一强大的网络编程库,并总结其为现代网络应用开发带来的核心价值。Folly/IO 不仅仅是一组工具的集合,更是 Facebook 多年高性能网络基础设施建设经验的结晶,它体现了对效率、性能和灵活性的极致追求。

    高性能 (High Performance)Folly/IO 的设计核心之一就是高性能。
    ▮▮▮▮ⓑ 零拷贝 (Zero-Copy):通过 IOBuf 数据结构,Folly/IO 实现了真正的零拷贝,大幅减少了数据在内核态和用户态之间的复制,降低了 CPU 开销,提升了数据处理速度。这对于处理高吞吐量、低延迟的网络应用至关重要。
    ▮▮▮▮ⓒ 异步非阻塞 (Asynchronous Non-blocking)AsyncSocketAsyncServerSocket 等组件提供了完全异步非阻塞的 IO 操作,结合 EventBase 事件循环,使得程序能够高效地处理并发连接,充分利用系统资源,避免了传统阻塞 IO 模型的性能瓶颈。
    ▮▮▮▮ⓓ 高效的数据结构 (Efficient Data Structures)IOBufIOBufQueue 等数据结构的设计,充分考虑了网络数据的特点,例如分片、拼接等,提供了高效的数据管理和操作能力,为构建高性能网络应用奠定了基础。

    异步编程模型 (Asynchronous Programming Model)Folly/IO 提供了完善的异步编程模型,使得开发者能够以更简洁、更高效的方式编写网络应用。
    ▮▮▮▮ⓑ EventBase 事件驱动 (Event-Driven with EventBase)EventBaseFolly/IO 异步编程的核心,它负责事件的调度和分发,使得开发者可以将精力集中在业务逻辑上,而无需过多关注底层 IO 事件的处理。
    ▮▮▮▮ⓒ 回调机制 (Callback Mechanism)AsyncSocket 等组件通过回调函数来处理异步事件,例如连接建立、数据接收、数据发送等,这种基于回调的异步编程模型清晰易懂,易于维护。
    ▮▮▮▮ⓓ PromiseFuture 集成 (Integration with Promise and Future):虽然本书没有深入探讨,但 Folly/IOFolly 库中的 PromiseFuture 组件可以很好地集成,进一步提升异步编程的表达能力和灵活性,使得复杂的异步流程管理更加便捷。

    强大的抽象能力 (Powerful Abstraction Capabilities)Folly/IO 提供了多层次的抽象,从底层的 IOBuf 到高层的 Request/Response 抽象,覆盖了网络编程的各个层面,使得开发者可以根据实际需求选择合适的抽象层次,提高开发效率和代码可维护性。
    ▮▮▮▮ⓑ 传输层抽象 (AsyncTransport)AsyncTransport 提供了传输层的抽象,使得开发者可以方便地构建各种基于不同传输协议的网络应用,例如 TCPUDPUnix Domain Socket 等,同时也方便了自定义传输协议的扩展。
    ▮▮▮▮ⓒ 请求/响应抽象 (Request/Response)RequestResponse 抽象简化了网络服务的构建,尤其是在构建 HTTP 等请求响应式协议的应用时,能够大大提高开发效率,降低复杂性。
    ▮▮▮▮ⓓ 安全通信支持 (SSL/TLS)Folly/IO 提供了完善的 SSL/TLS 支持,使得开发者可以轻松地为网络应用添加安全通信能力,保障数据传输的安全性。

    灵活性与可扩展性 (Flexibility and Scalability)Folly/IO 设计上注重灵活性和可扩展性,可以很好地适应各种不同的网络应用场景。
    ▮▮▮▮ⓑ 模块化设计 (Modular Design)Folly/IO 的各个组件都是模块化的,开发者可以根据需要选择性地使用,也可以方便地扩展和定制。
    ▮▮▮▮ⓒ 跨平台支持 (Cross-Platform Support)Folly/IO 具有良好的跨平台性,可以在 LinuxmacOS 等多种操作系统上运行,降低了跨平台开发的难度。
    ▮▮▮▮ⓓ Folly 库的良好集成 (Good Integration with Folly Library)Folly/IOFolly 库的一部分,可以与其他 Folly 组件(例如 Futures, Promises, ConcurrentSkipListMap 等)无缝集成,共同构建更强大的应用。

    总而言之,Folly/IO 的核心价值在于其 高性能异步编程模型强大的抽象能力 以及 灵活性与可扩展性。它为开发者提供了一套强大而全面的工具,可以用于构建各种高性能、高可靠性的网络应用,尤其是在需要处理大规模并发连接和海量数据传输的场景下,Folly/IO 的优势更加明显。

    9.2 Folly/IO 在未来网络编程中的应用前景 (Application Prospects of Folly/IO in Future Network Programming)

    展望未来,网络编程领域将持续演进,面临新的挑战和机遇。Folly/IO 作为一款成熟且高性能的网络库,在未来的网络编程中依然具有广阔的应用前景。

    云计算与微服务 (Cloud Computing and Microservices):云计算和微服务架构已经成为现代应用开发的主流趋势。
    ▮▮▮▮ⓑ 高性能网络通信基石 (Foundation for High-Performance Network Communication):在云计算和微服务环境中,服务之间的通信至关重要。Folly/IO 的高性能和异步特性使其成为构建高性能微服务通信框架的理想选择,可以支撑起大规模微服务集群的高效运转。
    ▮▮▮▮ⓒ 服务网格 (Service Mesh) 基础设施 (Infrastructure for Service Mesh):服务网格作为微服务架构的关键组件,负责服务间的流量管理、安全性和可观测性。Folly/IO 可以作为构建服务网格控制平面和数据平面的基础库,提供高性能的网络通信能力。

    边缘计算 (Edge Computing):边缘计算将计算和数据存储推向网络边缘,以降低延迟、提高效率。
    ▮▮▮▮ⓑ 资源受限环境下的高性能 (High Performance in Resource-Constrained Environments):边缘计算环境通常资源受限,例如 CPU、内存等。Folly/IO 的零拷贝和异步特性可以最大限度地减少资源消耗,提高边缘设备的资源利用率,使其能够在有限的资源下提供高性能的网络服务。
    ▮▮▮▮ⓒ 实时数据处理 (Real-time Data Processing):边缘计算场景通常需要实时处理来自传感器、设备等的数据。Folly/IO 的低延迟特性使其非常适合构建实时数据处理管道,例如实时监控、智能交通等应用。

    实时通信 (Real-time Communication):实时通信应用,例如在线游戏、视频会议、实时协作等,对网络延迟和吞吐量有着极高的要求。
    ▮▮▮▮ⓑ 低延迟网络基础设施 (Low-Latency Network Infrastructure)Folly/IO 的异步非阻塞和零拷贝特性可以显著降低网络延迟,为实时通信应用提供高性能的网络基础设施。
    ▮▮▮▮ⓒ 高并发连接处理 (High-Concurrency Connection Handling):实时通信应用通常需要处理大量的并发连接。Folly/IOEventBase 和异步 Socket API 可以高效地处理高并发连接,保证实时通信的流畅性和稳定性。

    大数据处理 (Big Data Processing):大数据处理领域需要高效的数据传输和处理能力。
    ▮▮▮▮ⓑ 高效数据传输管道 (Efficient Data Transmission Pipelines)Folly/IOIOBufIOBufQueue 可以构建高效的数据传输管道,用于大数据在不同节点之间的快速传输,例如在分布式计算框架中。
    ▮▮▮▮ⓒ 网络协议解析与处理 (Network Protocol Parsing and Processing):在大数据处理中,常常需要处理各种网络协议的数据。Folly/IO 提供的 Cursor 等工具可以方便地解析和处理网络协议数据,提高数据处理效率。

    物联网 (Internet of Things, IoT):物联网设备数量庞大,网络连接复杂多样。
    ▮▮▮▮ⓑ 轻量级网络协议栈 (Lightweight Network Protocol Stack)Folly/IO 可以用于构建轻量级的网络协议栈,适用于资源受限的物联网设备,实现高效的网络通信。
    ▮▮▮▮ⓒ 统一的异步 IO 接口 (Unified Asynchronous IO Interface)Folly/IO 提供的统一异步 IO 接口可以简化物联网设备的网络编程,提高开发效率,降低维护成本。

    总而言之,随着网络技术的不断发展,对高性能、低延迟、高并发网络应用的需求将持续增长。Folly/IO 凭借其卓越的性能、成熟的设计和丰富的功能,将在云计算、微服务、边缘计算、实时通信、大数据处理、物联网等领域发挥越来越重要的作用,成为未来网络编程领域不可或缺的关键技术之一。开发者掌握 Folly/IO,将能够更好地应对未来网络编程的挑战,构建更强大、更高效的网络应用。

    9.3 持续学习 Folly/IO 的资源与建议 (Resources and Suggestions for Continuous Learning of Folly/IO)

    学习 Folly/IO 并将其应用于实际项目是一个持续深入的过程。为了帮助读者更好地掌握 Folly/IO,本节提供一些学习资源和建议。

    官方资源 (Official Resources)
    Folly GitHub 仓库 🔗https://github.com/facebook/folly:这是学习 Folly/IO 最权威的资源。
    ▮▮▮▮⚝ folly/io 目录:重点关注 folly/io 目录下的源代码,包括头文件 (.h) 和实现文件 (.cpp)。通过阅读源代码,可以深入理解 Folly/IO 的设计原理和实现细节。
    ▮▮▮▮⚝ README.md 和文档Folly 仓库中通常包含 README.md 文件,以及一些文档,可以帮助你快速了解 FollyFolly/IO 的基本概念和使用方法。虽然官方文档可能相对简略,但仍然是重要的参考资料。
    Facebook Engineering 博客 🔗https://engineering.fb.com/Facebook Engineering 博客经常发布关于 FollyFolly/IO 的技术文章,分享其设计理念、应用场景和最佳实践。通过阅读这些博客文章,可以了解 Folly/IOFacebook 内部的应用案例,以及一些高级用法和技巧。

    社区资源 (Community Resources)
    Stack Overflow 🔗https://stackoverflow.com/:在 Stack Overflow 上搜索 follyfolly-io 相关的标签,可以找到很多关于 Folly/IO 的问题和解答。你可以在这里提问遇到的问题,或者帮助解答其他开发者的问题,参与社区交流。
    GitHub Issues 和 DiscussionsFolly GitHub 仓库的 IssuesDiscussions 页面也是重要的社区交流平台。你可以在这里查看 Bug 报告、功能请求、以及与其他开发者和维护者的讨论。
    C++ 开源社区:关注 C++ 开源社区,例如 Redditr/cpp 版块,以及一些 C++ 技术论坛,有时也会有关于 FollyFolly/IO 的讨论。

    学习建议 (Learning Suggestions)
    从基础开始 (Start with the Basics):首先,从 IOBufAsyncSocket 等核心组件入手,理解它们的基本概念、API 和使用方法。可以参考本书前面的章节,以及 Folly/IO 的头文件注释和示例代码。
    阅读源代码 (Read the Source Code):深入理解 Folly/IO 的最佳方式是阅读源代码。从 IOBuf.hAsyncSocket.h 等关键头文件开始,逐步阅读相关实现代码,理解其内部工作原理。
    实践出真知 (Practice Makes Perfect):通过编写实际的代码来练习使用 Folly/IO。可以从简单的示例程序开始,例如构建一个简单的 TCP 客户端/服务端,或者实现一个简单的网络协议解析器。逐步尝试更复杂的应用场景,例如构建一个简单的 HTTP 服务器,或者一个高性能代理服务器。
    参与社区 (Engage with the Community):积极参与 Folly/IO 社区,例如在 Stack Overflow 上提问和回答问题,参与 GitHub IssuesDiscussions,与其他开发者交流学习经验,共同进步。
    持续学习和探索 (Continuous Learning and Exploration)Folly/IO 是一个不断发展和完善的库。保持对 Folly 仓库的关注,关注最新的 Commit 和版本发布,学习新的特性和功能,不断提升自己的 Folly/IO 技能。同时,也要关注网络编程领域的新技术和发展趋势,将 Folly/IO 与其他技术结合应用,发挥其更大的价值。

    希望本章的总结与展望,以及提供的学习资源和建议,能够帮助读者更好地理解 Folly/IO 的核心价值,掌握其使用方法,并在未来的网络编程实践中充分利用 Folly/IO 的强大功能,构建更高效、更可靠的网络应用。网络编程之路漫漫,愿你不断精进,勇攀高峰! 🚀

    END_OF_CHAPTER