mirror of
https://github.com/langhuihui/monibuca.git
synced 2025-09-27 01:15:52 +08:00
doc: add reader
This commit is contained in:
144
doc/arch/reader_design_philosophy.md
Normal file
144
doc/arch/reader_design_philosophy.md
Normal file
@@ -0,0 +1,144 @@
|
||||
# Implementing Go's Reader Interface Design Philosophy: A Case Study with Monibuca Streaming Media Processing
|
||||
|
||||
## Introduction
|
||||
|
||||
Go is renowned for its philosophy of simplicity, efficiency, and concurrency safety, with the io.Reader interface being a prime example of this philosophy. In practical business development, correctly applying the design concepts of the io.Reader interface is crucial for building high-quality, maintainable systems. This article will explore how to implement Go's Reader interface design philosophy in real-world business scenarios using RTP data processing in the Monibuca streaming media server as an example, covering core concepts such as synchronous programming patterns, single responsibility principle, separation of concerns, and composition reuse.
|
||||
|
||||
## What is Go's Reader Interface Design Philosophy?
|
||||
|
||||
Go's io.Reader interface design philosophy is primarily reflected in the following aspects:
|
||||
|
||||
1. **Simplicity**: The io.Reader interface defines only one method `Read(p []byte) (n int, err error)`. This minimalist design means any type that implements this method can be considered a Reader.
|
||||
|
||||
2. **Composability**: By combining different Readers, powerful data processing pipelines can be built.
|
||||
|
||||
3. **Single Responsibility**: Each Reader is responsible for only one specific task, adhering to the single responsibility principle.
|
||||
|
||||
4. **Separation of Concerns**: Different Readers handle different data formats or protocols, achieving separation of concerns.
|
||||
|
||||
## Reader Design Practice in Monibuca
|
||||
|
||||
In the Monibuca streaming media server, we've designed a series of Readers to handle data at different layers:
|
||||
|
||||
1. **SinglePortReader**: Handles single-port multiplexed data streams
|
||||
2. **RTPTCPReader** and **RTPUDPReader**: Handle RTP packets over TCP and UDP protocols respectively
|
||||
3. **RTPPayloadReader**: Extracts payload from RTP packets
|
||||
4. **AnnexBReader**: Processes H.264/H.265 Annex B format data
|
||||
|
||||
### Synchronous Programming Pattern
|
||||
|
||||
Go's io.Reader interface naturally supports synchronous programming patterns. In Monibuca, we process data layer by layer synchronously:
|
||||
|
||||
```go
|
||||
// Reading data from RTP packets
|
||||
func (r *RTPPayloadReader) Read(buf []byte) (n int, err error) {
|
||||
// If there's data in the buffer, read it first
|
||||
if r.buffer.Length > 0 {
|
||||
n, _ = r.buffer.Read(buf)
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Read a new RTP packet
|
||||
err = r.IRTPReader.Read(&r.Packet)
|
||||
// ... process data
|
||||
}
|
||||
```
|
||||
|
||||
This synchronous pattern makes the code logic clear, easy to understand, and debug.
|
||||
|
||||
### Single Responsibility Principle
|
||||
|
||||
Each Reader has a clear responsibility:
|
||||
|
||||
- **RTPTCPReader**: Only responsible for parsing RTP packets from TCP streams
|
||||
- **RTPUDPReader**: Only responsible for parsing RTP packets from UDP packets
|
||||
- **RTPPayloadReader**: Only responsible for extracting payload from RTP packets
|
||||
- **AnnexBReader**: Only responsible for parsing Annex B format data
|
||||
|
||||
This design makes each component very focused, making them easy to test and maintain.
|
||||
|
||||
### Separation of Concerns
|
||||
|
||||
By separating processing logic at different layers into different Readers, we achieve separation of concerns:
|
||||
|
||||
```go
|
||||
// Example of creating an RTP reader
|
||||
switch mode {
|
||||
case StreamModeUDP:
|
||||
rtpReader = NewRTPPayloadReader(NewRTPUDPReader(conn))
|
||||
case StreamModeTCPActive, StreamModeTCPPassive:
|
||||
rtpReader = NewRTPPayloadReader(NewRTPTCPReader(conn))
|
||||
}
|
||||
```
|
||||
|
||||
This separation allows us to modify and optimize the processing logic at each layer independently without affecting other layers.
|
||||
|
||||
### Composition Reuse
|
||||
|
||||
Go's Reader design philosophy encourages code reuse through composition. In Monibuca, we build complete data processing pipelines by combining different Readers:
|
||||
|
||||
```go
|
||||
// RTPPayloadReader composes IRTPReader
|
||||
type RTPPayloadReader struct {
|
||||
IRTPReader // Composed interface
|
||||
// ... other fields
|
||||
}
|
||||
|
||||
// AnnexBReader can be used in combination with RTPPayloadReader
|
||||
annexBReader := &AnnexBReader{}
|
||||
rtpReader := NewRTPPayloadReader(NewRTPUDPReader(conn))
|
||||
```
|
||||
|
||||
## Data Processing Flow Sequence Diagram
|
||||
|
||||
To better understand how these Readers work together, let's look at a sequence diagram:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant C as Client
|
||||
participant S as Server
|
||||
participant SPR as SinglePortReader
|
||||
participant RTCP as RTPTCPReader
|
||||
participant RTPU as RTPUDPReader
|
||||
participant RTPP as RTPPayloadReader
|
||||
participant AR as AnnexBReader
|
||||
|
||||
C->>S: Send RTP packets
|
||||
S->>SPR: Receive data
|
||||
SPR->>RTCP: Parse TCP mode data
|
||||
SPR->>RTPU: Parse UDP mode data
|
||||
RTCP->>RTPP: Extract RTP packet payload
|
||||
RTPU->>RTPP: Extract RTP packet payload
|
||||
RTPP->>AR: Parse Annex B format data
|
||||
AR-->>S: Return parsed NALU data
|
||||
```
|
||||
|
||||
## Design Patterns in Practical Applications
|
||||
|
||||
In Monibuca, we've adopted several design patterns to better implement the Reader interface design philosophy:
|
||||
|
||||
### 1. Decorator Pattern
|
||||
|
||||
RTPPayloadReader decorates IRTPReader, adding payload extraction functionality on top of reading RTP packets.
|
||||
|
||||
### 2. Adapter Pattern
|
||||
|
||||
SinglePortReader adapts multiplexed data streams, converting them into the standard io.Reader interface.
|
||||
|
||||
### 3. Factory Pattern
|
||||
|
||||
Factory functions like `NewRTPTCPReader`, `NewRTPUDPReader`, etc., are used to create different types of Readers.
|
||||
|
||||
## Performance Optimization and Best Practices
|
||||
|
||||
In practical applications, we also need to consider performance optimization:
|
||||
|
||||
1. **Memory Reuse**: Using `util.Buffer` and `util.Memory` to reduce memory allocation
|
||||
2. **Buffering Mechanism**: Using buffers in RTPPayloadReader to handle incomplete packets
|
||||
3. **Error Handling**: Using `errors.Join` to combine multiple error messages
|
||||
|
||||
## Conclusion
|
||||
|
||||
Through our practice in the Monibuca streaming media server, we can see the powerful impact of Go's Reader interface design philosophy in real-world business scenarios. By following design concepts such as synchronous programming patterns, single responsibility principle, separation of concerns, and composition reuse, we can build highly cohesive, loosely coupled, maintainable, and extensible systems.
|
||||
|
||||
This design philosophy is not only applicable to streaming media processing but also to any scenario that requires data stream processing. Mastering and correctly applying these design principles will help us write more elegant and efficient Go code.
|
146
doc_CN/arch/reader_design_philosophy.md
Normal file
146
doc_CN/arch/reader_design_philosophy.md
Normal file
@@ -0,0 +1,146 @@
|
||||
# 贯彻 Go 语言 Reader 接口设计哲学:以 Monibuca 中的流媒体处理为例
|
||||
|
||||
## 引言
|
||||
|
||||
Go 语言以其简洁、高效和并发安全的设计哲学而闻名,其中 io.Reader 接口是这一哲学的典型体现。在实际业务开发中,如何正确运用 io.Reader 接口的设计思想,对于构建高质量、可维护的系统至关重要。本文将以 Monibuca 流媒体服务器中的 RTP 数据处理为例,深入探讨如何在实际业务中贯彻 Go 语言的 Reader 接口设计哲学,包括同步编程模式、单一职责原则、关注点分离以及组合复用等核心概念。
|
||||
|
||||
## 什么是 Go 语言的 Reader 接口设计哲学?
|
||||
|
||||
Go 语言的 io.Reader 接口设计哲学主要体现在以下几个方面:
|
||||
|
||||
1. **简单性**:io.Reader 接口只定义了一个方法 `Read(p []byte) (n int, err error)`,这种极简设计使得任何实现了该方法的类型都可以被视为一个 Reader。
|
||||
|
||||
2. **组合性**:通过组合不同的 Reader,可以构建出功能强大的数据处理管道。
|
||||
|
||||
3. **单一职责**:每个 Reader 只负责一个特定的任务,符合单一职责原则。
|
||||
|
||||
4. **关注点分离**:不同的 Reader 负责处理不同的数据格式或协议,实现了关注点的分离。
|
||||
|
||||
## Monibuca 中的 Reader 设计实践
|
||||
|
||||
在 Monibuca 流媒体服务器中,我们设计了一系列的 Reader 来处理不同层次的数据:
|
||||
|
||||
1. **SinglePortReader**:处理单端口多路复用的数据流
|
||||
2. **RTPTCPReader** 和 **RTPUDPReader**:分别处理 TCP 和 UDP 协议的 RTP 数据包
|
||||
3. **RTPPayloadReader**:从 RTP 包中提取有效载荷
|
||||
4. **AnnexBReader**:处理 H.264/H.265 的 Annex B 格式数据
|
||||
|
||||
> 备注:在处理 PS流时从RTPPayloadReader还要经过 PS包解析、PES包解析才进入 AnnexBReader
|
||||
|
||||
### 同步编程模式
|
||||
|
||||
Go 的 io.Reader 接口天然支持同步编程模式。在 Monibuca 中,我们通过同步方式逐层处理数据:
|
||||
|
||||
```go
|
||||
// 从 RTP 包中读取数据
|
||||
func (r *RTPPayloadReader) Read(buf []byte) (n int, err error) {
|
||||
// 如果缓冲区中有数据,先读取缓冲区中的数据
|
||||
if r.buffer.Length > 0 {
|
||||
n, _ = r.buffer.Read(buf)
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// 读取新的 RTP 包
|
||||
err = r.IRTPReader.Read(&r.Packet)
|
||||
// ... 处理数据
|
||||
}
|
||||
```
|
||||
|
||||
这种同步模式使得代码逻辑清晰,易于理解和调试。
|
||||
|
||||
### 单一职责原则
|
||||
|
||||
每个 Reader 都有明确的职责:
|
||||
|
||||
- **RTPTCPReader**:只负责从 TCP 流中解析 RTP 包
|
||||
- **RTPUDPReader**:只负责从 UDP 数据包中解析 RTP 包
|
||||
- **RTPPayloadReader**:只负责从 RTP 包中提取有效载荷
|
||||
- **AnnexBReader**:只负责解析 Annex B 格式的数据
|
||||
|
||||
这种设计使得每个组件都非常专注,易于测试和维护。
|
||||
|
||||
### 关注点分离
|
||||
|
||||
通过将不同层次的处理逻辑分离到不同的 Reader 中,我们实现了关注点的分离:
|
||||
|
||||
```go
|
||||
// 创建 RTP 读取器的示例
|
||||
switch mode {
|
||||
case StreamModeUDP:
|
||||
rtpReader = NewRTPPayloadReader(NewRTPUDPReader(conn))
|
||||
case StreamModeTCPActive, StreamModeTCPPassive:
|
||||
rtpReader = NewRTPPayloadReader(NewRTPTCPReader(conn))
|
||||
}
|
||||
```
|
||||
|
||||
这种分离使得我们可以独立地修改和优化每一层的处理逻辑,而不会影响其他层。
|
||||
|
||||
### 组合复用
|
||||
|
||||
Go 语言的 Reader 设计哲学鼓励通过组合来复用代码。在 Monibuca 中,我们通过组合不同的 Reader 来构建完整的数据处理管道:
|
||||
|
||||
```go
|
||||
// RTPPayloadReader 组合了 IRTPReader
|
||||
type RTPPayloadReader struct {
|
||||
IRTPReader // 组合接口
|
||||
// ... 其他字段
|
||||
}
|
||||
|
||||
// AnnexBReader 可以与 RTPPayloadReader 组合使用
|
||||
annexBReader := &AnnexBReader{}
|
||||
rtpReader := NewRTPPayloadReader(NewRTPUDPReader(conn))
|
||||
```
|
||||
|
||||
## 数据处理流程时序图
|
||||
|
||||
为了更直观地理解这些 Reader 是如何协同工作的,我们来看一个时序图:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant C as 客户端
|
||||
participant S as 服务器
|
||||
participant SPR as SinglePortReader
|
||||
participant RTCP as RTPTCPReader
|
||||
participant RTPU as RTPUDPReader
|
||||
participant RTPP as RTPPayloadReader
|
||||
participant AR as AnnexBReader
|
||||
|
||||
C->>S: 发送 RTP 数据包
|
||||
S->>SPR: 接收数据
|
||||
SPR->>RTCP: TCP 模式数据解析
|
||||
SPR->>RTPU: UDP 模式数据解析
|
||||
RTCP->>RTPP: 提取 RTP 包有效载荷
|
||||
RTPU->>RTPP: 提取 RTP 包有效载荷
|
||||
RTPP->>AR: 解析 Annex B 格式数据
|
||||
AR-->>S: 返回解析后的 NALU 数据
|
||||
```
|
||||
|
||||
## 实际应用中的设计模式
|
||||
|
||||
在 Monibuca 中,我们采用了多种设计模式来更好地贯彻 Reader 接口的设计哲学:
|
||||
|
||||
### 1. 装饰器模式
|
||||
|
||||
RTPPayloadReader 装饰了 IRTPReader,在读取 RTP 包的基础上增加了有效载荷提取功能。
|
||||
|
||||
### 2. 适配器模式
|
||||
|
||||
SinglePortReader 适配了多路复用的数据流,将其转换为标准的 io.Reader 接口。
|
||||
|
||||
### 3. 工厂模式
|
||||
|
||||
通过 `NewRTPTCPReader`、`NewRTPUDPReader` 等工厂函数来创建不同类型的 Reader。
|
||||
|
||||
## 性能优化与最佳实践
|
||||
|
||||
在实际应用中,我们还需要考虑性能优化:
|
||||
|
||||
1. **内存复用**:通过 `util.Buffer` 和 `util.Memory` 来减少内存分配
|
||||
2. **缓冲机制**:在 RTPPayloadReader 中使用缓冲区来处理不完整的数据包
|
||||
3. **错误处理**:通过 `errors.Join` 来合并多个错误信息
|
||||
|
||||
## 结论
|
||||
|
||||
通过在 Monibuca 流媒体服务器中的实践,我们可以看到 Go 语言的 Reader 接口设计哲学在实际业务中的强大威力。通过遵循同步编程模式、单一职责原则、关注点分离和组合复用等设计理念,我们能够构建出高内聚、低耦合、易于维护和扩展的系统。
|
||||
|
||||
这种设计哲学不仅适用于流媒体处理,也适用于任何需要处理数据流的场景。掌握并正确运用这些设计原则,将有助于我们编写出更加优雅和高效的 Go 代码。
|
Reference in New Issue
Block a user