目录
- [1. 项目架构概述](#1. 项目架构概述)
-
- [1.1 介绍一下你这个高并发服务器项目](#1.1 介绍一下你这个高并发服务器项目)
- [1.2 请详细解释什么是Reactor模型?为什么选择主从Reactor模式而不是单Reactor模式?](#1.2 请详细解释什么是Reactor模型?为什么选择主从Reactor模式而不是单Reactor模式?)
- [1.3 解释"One Thread One Loop"的设计思想及其优势](#1.3 解释"One Thread One Loop"的设计思想及其优势)
- [1.4 为什么将服务器组件和应用层协议支持分离设计?](#1.4 为什么将服务器组件和应用层协议支持分离设计?)
- [2. 核心组件实现细节](#2. 核心组件实现细节)
-
- [2.1 Buffer模块为什么使用vector而不是固定数组?如何实现高效的数据管理?](#2.1 Buffer模块为什么使用vector而不是固定数组?如何实现高效的数据管理?)
- [2.2 Channel、Poller、EventLoop三者之间的关系是怎样的?](#2.2 Channel、Poller、EventLoop三者之间的关系是怎样的?)
- [2.3 Connection模块如何管理连接的生命周期?如何处理连接异常?](#2.3 Connection模块如何管理连接的生命周期?如何处理连接异常?)
- [2.4 定时器模块为什么选择时间轮而不是小根堆?](#2.4 定时器模块为什么选择时间轮而不是小根堆?)
- [2.5 详细解释时间轮+shared_ptr实现定时刷新机制的原理](#2.5 详细解释时间轮+shared_ptr实现定时刷新机制的原理)
- [3. 网络编程](#3. 网络编程)
-
- [3.1 epoll的LT和ET模式有什么区别?本项目为什么选择LT模式?](#3.1 epoll的LT和ET模式有什么区别?本项目为什么选择LT模式?)
- [3.2 如何保证非阻塞I/O的正确性?read/write返回0或-1分别表示什么?非阻塞I/O与阻塞I/O的区别是什么?为什么要用非阻塞I/O?](#3.2 如何保证非阻塞I/O的正确性?read/write返回0或-1分别表示什么?非阻塞I/O与阻塞I/O的区别是什么?为什么要用非阻塞I/O?)
- [3.3 为什么需要忽略SIGPIPE信号?还有什么其他信号需要处理?](#3.3 为什么需要忽略SIGPIPE信号?还有什么其他信号需要处理?)
- [3.4 eventfd在EventLoop中起什么作用?与pipe相比有什么优势?](#3.4 eventfd在EventLoop中起什么作用?与pipe相比有什么优势?)
- [3.5 TCP三次握手和四次挥手的过程是什么?在你的服务器中如何体现?](#3.5 TCP三次握手和四次挥手的过程是什么?在你的服务器中如何体现?)
- [3.6 什么是I/O多路复用?select、poll、epoll有什么区别?](#3.6 什么是I/O多路复用?select、poll、epoll有什么区别?)
- [4. 多线程与并发控制](#4. 多线程与并发控制)
-
- [4.1 如何保证Connection的所有操作都在其绑定的EventLoop线程中执行?](#4.1 如何保证Connection的所有操作都在其绑定的EventLoop线程中执行?)
- [4.2 EventLoop的任务队列如何实现线程安全?](#4.2 EventLoop的任务队列如何实现线程安全?)
- [4.3 LoopThreadPool如何分配连接给子Reactor?负载均衡策略是什么?](#4.3 LoopThreadPool如何分配连接给子Reactor?负载均衡策略是什么?)
- [4.4 多线程环境下如何保证线程安全?](#4.4 多线程环境下如何保证线程安全?)
- [5. HTTP协议实现](#5. HTTP协议实现)
-
- [5.1 HTTP请求解析的状态机设计是如何工作的?](#5.1 HTTP请求解析的状态机设计是如何工作的?)
- [5.2 如何处理HTTP长连接和短连接?](#5.2 如何处理HTTP长连接和短连接?)
- [5.3 正则表达式在HTTP路由中起什么作用?性能影响如何?](#5.3 正则表达式在HTTP路由中起什么作用?性能影响如何?)
- [5.4 HTTP协议的请求和响应格式是什么?](#5.4 HTTP协议的请求和响应格式是什么?)
- [5.5 如何解析HTTP请求?状态机的作用是什么?](#5.5 如何解析HTTP请求?状态机的作用是什么?)
- [6. 性能优化与稳定性](#6. 性能优化与稳定性)
-
- [6.1 如何避免大量连接时的内存碎片问题?](#6.1 如何避免大量连接时的内存碎片问题?)
- [6.2 在大文件传输时,如何避免内存占用过大?](#6.2 在大文件传输时,如何避免内存占用过大?)
- [6.3 业务处理耗时过长导致其他连接超时的问题如何解决?](#6.3 业务处理耗时过长导致其他连接超时的问题如何解决?)
- [7. 异常处理与稳定性](#7. 异常处理与稳定性)
-
- [7.1 服务器如何应对恶意连接或DDoS攻击?](#7.1 服务器如何应对恶意连接或DDoS攻击?)
- [7.2 如何保证服务器的7x24小时稳定运行?](#7.2 如何保证服务器的7x24小时稳定运行?)
- [7.3 遇到客户端突然断开连接,服务器如何处理?](#7.3 遇到客户端突然断开连接,服务器如何处理?)
- [8. 扩展与维护](#8. 扩展与维护)
-
- [8.1 如何扩展支持WebSocket协议?](#8.1 如何扩展支持WebSocket协议?)
- [8.2 如何实现关闭服务器?](#8.2 如何实现关闭服务器?)
- [9. 综合问题](#9. 综合问题)
-
- [9.1 如果让你重新设计这个系统,你会做哪些改进?](#9.1 如果让你重新设计这个系统,你会做哪些改进?)
- [9.2 这个服务器能支持的最大并发连接数受哪些因素限制?](#9.2 这个服务器能支持的最大并发连接数受哪些因素限制?)
- [9.3 如何测试服务器的并发性能?需要注意什么?](#9.3 如何测试服务器的并发性能?需要注意什么?)
- [9.4 如果让你设计一个支持百万并发连接的服务器,你会考虑哪些方面?](#9.4 如果让你设计一个支持百万并发连接的服务器,你会考虑哪些方面?)
- [9.5 这个项目中你遇到的最大技术挑战是什么?如何解决的?](#9.5 这个项目中你遇到的最大技术挑战是什么?如何解决的?)
1. 项目架构概述
1.1 介绍一下你这个高并发服务器项目
(1)这是一个基于C++11实现的高性能并发服务器框架,模仿了muduo库的"One Thread One Loop"主从Reactor架构。核心特点包括:
- 采用主从Reactor模型:主Reactor负责接收新连接,子Reactor处理已连接套接字的I/O事件。
- 实现了完整的TCP服务器组件,包括连接管理、缓冲区、定时器、事件循环等。
- 支持HTTP应用层协议,可以快速搭建HTTP服务器。
- 使用epoll实现I/O多路复用,非阻塞I/O,支持高并发连接。
- 实现了智能指针管理的连接生命周期和超时自动释放机制。
1.2 请详细解释什么是Reactor模型?为什么选择主从Reactor模式而不是单Reactor模式?
- Reactor模式是一种事件驱动的设计模式,通过一个或多个输入同时传递给服务器的请求处理模式。核心组件包括Reactor(事件分发器)、Handlers(事件处理器)和Acceptor(连接接受器)。
- 单Reactor单线程无法利用多核CPU,容易达到性能瓶颈;单Reactor多线程虽然可以利用多核,但单个Reactor承担所有事件监听和响应,高并发下容易成为瓶颈。
- 主从Reactor模式中,主Reactor只负责监听新连接,子Reactor处理已建立连接的I/O事件,实现职责分离,能更好地利用多核CPU,提高并发性能。
1.3 解释"One Thread One Loop"的设计思想及其优势
(1)每个线程运行一个事件循环(EventLoop),该循环负责监控和处理该线程分配的所有描述符的I/O事件。优势如下:
- 线程隔离:每个连接的所有操作都在同一个线程中完成,避免线程安全问题。
- 资源高效:减少线程间上下文切换和同步开销。
- 简化编程模型:开发者无需担心多线程并发问题。
- 可扩展性:容易增加更多子Reactor线程来处理更多连接。
1.4 为什么将服务器组件和应用层协议支持分离设计?
- 关注点分离:服务器组件专注于网络I/O、连接管理和事件驱动,协议模块专注于应用层协议解析。
- 可扩展性:易于支持多种应用层协议(HTTP、WebSocket、RPC等)。
- 复用性:同一个服务器组件可以支持不同的上层应用。
- 维护性:协议模块可以独立更新和扩展。
2. 核心组件实现细节
2.1 Buffer模块为什么使用vector而不是固定数组?如何实现高效的数据管理?
(1)优点:
- 动态扩容:vector可以自动扩容,适应不同大小的数据。
- 连续内存:保证内存连续性,提高缓存命中率。
(2)实现机制:
- 维护读偏移(reader_idx)和写偏移(writer_idx)。
- 提供EnsureWriteSpace保证足够写入空间。
- 当尾部空间不足时,如果头部有空闲空间,移动数据到头部;否则扩容。
- 避免频繁的内存分配和拷贝。
2.2 Channel、Poller、EventLoop三者之间的关系是怎样的?
Channel(描述符+事件回调) <--> Poller(epoll封装) <--> EventLoop(事件循环)
- Channel:封装文件描述符及其感兴趣的事件和回调函数。
- Poller:对epoll的封装,管理所有Channel的事件监控。
- EventLoop:事件循环核心,管理Poller和定时器,执行事件回调。
- 三者协作:Channel通过EventLoop注册到Poller,事件就绪后Poller通知EventLoop,EventLoop调用对应Channel的回调。
2.3 Connection模块如何管理连接的生命周期?如何处理连接异常?
- 状态管理:使用ConnStatu枚举管理连接状态(DISCONNECTED、CONNECTING、CONNECTED、DISCONNECTING)
- 异常处理:
- 读错误:非致命错误(EAGAIN/EINTR)返回0,其他错误触发关闭。
- 写错误:发送失败触发连接关闭。
- 连接关闭:处理剩余数据后释放资源。
- 资源释放:使用shared_ptr引用计数,确保资源正确释放。
2.4 定时器模块为什么选择时间轮而不是小根堆?
(1)时间轮(TimerWheel)优势:
- O(1)的添加、删除和触发操作。
- 适合大量定时任务场景。
- 减少遍历开销,特别适合短时定时任务。
(2)小根堆劣势:
- O(logN)的添加和删除操作。
- 每次触发需要调整堆结构。
- 本项目需求:定时任务通常是短时任务(30秒内),时间轮更合适。
2.5 详细解释时间轮+shared_ptr实现定时刷新机制的原理
(1)核心思想:通过shared_ptr引用计数延迟实际任务执行。
- 创建定时任务时,生成shared_ptr< TimerTask >。
- 任务添加到时间轮的指定槽位。
- 连接活跃时,刷新定时任务:创建新的shared_ptr到新槽位。
- 旧槽位触发时,旧shared_ptr析构,但引用计数不为0,不执行任务。
- 新shared_ptr在后续槽位触发时才真正执行任务。
- 这样实现了"刷新即延迟"的效果。
3. 网络编程
3.1 epoll的LT和ET模式有什么区别?本项目为什么选择LT模式?
(1)LT和ET模式介绍:
- LT(水平触发):描述符就绪时会持续通知,直到数据处理完。
- ET(边缘触发):描述符状态变化时只通知一次。
(2)选择LT的原因:
- 编程简单,不容易遗漏事件。
- 与阻塞/非阻塞I/O配合更灵活。
- 本项目Buffer设计可以一次性读取所有数据,避免LT的重复通知问题。
- 性能考虑:ET理论上更高效,但需要更复杂的缓冲区管理。
3.2 如何保证非阻塞I/O的正确性?read/write返回0或-1分别表示什么?非阻塞I/O与阻塞I/O的区别是什么?为什么要用非阻塞I/O?
(1)非阻塞read:
- 返回>0:成功读取数据。
- 返回0:对端关闭连接。
- 返回-1:检查errno。
- EAGAIN/EWOULDBLOCK:缓冲区无数据,不是错误。
- EINTR:被信号中断,应该重试。
- 其他:真正错误,关闭连接。
(2)非阻塞write:
- 返回>0:成功写入数据
- 返回0:表示写了0字节(可能缓冲区满?)
- 返回-1:错误处理类似read
(3)区别:
- 阻塞I/O:调用read/write时,如果数据未就绪,线程会一直等待。
- 非阻塞I/O:调用立即返回,如果数据未就绪,返回错误码(如EAGAIN)。
(4)使用非阻塞I/O的原因:
- 避免一个慢连接阻塞整个线程。
- 配合I/O多路复用,实现高并发。
- 实现真正的异步处理,提高CPU利用率。
- 避免死锁和线程饥饿问题。
3.3 为什么需要忽略SIGPIPE信号?还有什么其他信号需要处理?
- SIGPIPE:当向已关闭的连接写数据时,系统会发送SIGPIPE信号,默认行为是终止进程。
- 忽略SIGPIPE,通过write返回值判断连接状态更可控。
- 其他可能需要处理的信号:
- SIGCHLD:处理子进程终止(本项目未使用fork)。
- SIGALRM:定时器信号(本项目使用timerfd替代)。
- SIGINT/SIGTERM:优雅关闭服务器。
3.4 eventfd在EventLoop中起什么作用?与pipe相比有什么优势?
(1)作用:唤醒阻塞在epoll_wait上的线程,处理任务队列中的任务。工作机制:
- 向eventfd写入数据触发可读事件。
- EventLoop从eventfd读取数据,清空事件。
- 然后执行任务队列中的任务。
(2)与pipe比较的优势:
- 更轻量:单一文件描述符,pipe需要两个。
- 性能更好:内核开销小。
- 使用简单:专门的eventfd系统调用。
3.5 TCP三次握手和四次挥手的过程是什么?在你的服务器中如何体现?
(1)三次握手:
- 客户端SYN → 服务器。
- 服务器SYN+ACK → 客户端。
- 客户端ACK → 服务器。
(2)四次挥手:
- 主动方FIN → 被动方。
- 被动方ACK → 主动方。
- 被动方FIN → 主动方。
- 主动方ACK → 被动方。
(3)服务器中的体现:
- Acceptor处理新连接(三次握手后)。
- Connection的Shutdown()实现优雅关闭(四次挥手)。
- 通过EPOLLRDHUP和EPOLLHUP事件检测连接关闭。
3.6 什么是I/O多路复用?select、poll、epoll有什么区别?
(1)I/O多路复用:
- 一个线程监控多个文件描述符,当某个描述符就绪时进行相应操作。
(2)select、poll、epoll区别对比:
| 特性 | select | poll | epoll |
|---|---|---|---|
| 时间复杂度 | O(n) | O(n) | O(1) |
| 最大连接数 | 有限制(1024) | 无限制 | 无限制 |
| 触发方式 | 水平触发 | 水平触发 | 水平/边缘触发 |
| 内核通知 | 轮询所有fd | 轮询所有fd | 回调通知 |
| 内存拷贝 | 每次调用都拷贝fd集合 | 每次调用都拷贝fd集合 | 使用mmap减少拷贝 |
4. 多线程与并发控制
4.1 如何保证Connection的所有操作都在其绑定的EventLoop线程中执行?
(1)核心机制:RunInLoop和QueueInLoop。
- 每个Connection绑定一个EventLoop。
- Connection的操作(Send、Shutdown等)都封装为任务。
- 如果当前线程是EventLoop线程,直接执行。
- 否则将任务加入EventLoop的任务队列。
- 通过eventfd唤醒EventLoop执行任务队列。
- 保证线程安全性:操作都在同一个线程执行。
4.2 EventLoop的任务队列如何实现线程安全?
(1)实现要点:
- 使用std::mutex保护任务队列。
- 添加任务时加锁,加入队列后解锁。
- 执行任务时交换队列,减少锁持有时间:
cpp
std::vector<Functor> functors;
{
std::unique_lock<std::mutex> lock(_mutex);
_tasks.swap(functors);
}
// 执行functors中的任务(已解锁)
- 使用eventfd保证及时唤醒
4.3 LoopThreadPool如何分配连接给子Reactor?负载均衡策略是什么?
(1)分配策略:轮询(round-robin)分配。实现方式:
cpp
EventLoop *NextLoop()
{
if (_thread_count == 0) return _baseloop;
_next_idx = (_next_idx + 1) % _thread_count;
return _loops[_next_idx];
}
- 优点:简单公平,每个子Reactor负载大致均衡。
- 缺点:没有考虑连接的实际负载情况。
4.4 多线程环境下如何保证线程安全?
- 连接与线程绑定:每个连接固定在一个EventLoop线程。
- 任务队列:跨线程操作通过任务队列传递。
- 锁的使用:
- 使用std::mutex保护共享数据结构。
- 使用std::unique_lock和条件变量。
- 原子操作:简单计数器使用原子类型。
- 智能指针:使用shared_ptr管理共享资源,自动引用计数。
5. HTTP协议实现
5.1 HTTP请求解析的状态机设计是如何工作的?
(1)状态枚举:
cpp
typedef enum {
RECV_HTTP_LINE, // 解析请求行
RECV_HTTP_HEAD, // 解析请求头
RECV_HTTP_BODY, // 解析请求体
RECV_HTTP_OVER, // 解析完成
RECV_HTTP_ERROR // 解析错误
} HttpRecvStatu;
(2)解析流程:
- 初始状态为RECV_HTTP_LINE。
- 解析完请求行,状态转为RECV_HTTP_HEAD。
- 解析完请求头,状态转为RECV_HTTP_BODY。
- 根据Content-Length解析请求体,完成后状态转为RECV_HTTP_OVER。
- 任何步骤出错,状态转为RECV_HTTP_ERROR。
5.2 如何处理HTTP长连接和短连接?
(1)判断逻辑:
cpp
bool Close() const {
// Connection头部为"keep-alive"时保持长连接
if (HasHeader("Connection") && GetHeader("Connection") == "keep-alive") {
return false; // 长连接
}
return true; // 短连接
}
(2)处理方式:
- 短连接:响应后立即关闭连接。
- 长连接:保持连接,继续处理后续请求。
- 超时机制:长连接也有超时时间,避免资源泄露。
5.3 正则表达式在HTTP路由中起什么作用?性能影响如何?
(1)作用:
- 匹配请求路径,实现灵活的路由规则。
- 示例:匹配/user/123格式。
cpp
server.Get("/user/(\\d+)", UserHandler);
(2)性能影响:
- 正则匹配比简单字符串匹配开销大。
- 但提供更强大的路由能力。
- 实际性能影响取决于正则复杂度和请求频率。
- 优化:常用路由可以缓存匹配结果。
5.4 HTTP协议的请求和响应格式是什么?
(1)HTTP请求格式:
cpp
GET /path HTTP/1.1\r\n
Header1: Value1\r\n
Header2: Value2\r\n
\r\n
Body...
(2)HTTP响应格式:
cpp
HTTP/1.1 200 OK\r\n
Header1: Value1\r\n
Header2: Value2\r\n
\r\n
Body...
(3)关键点:
- 请求行/状态行。
- 头部字段(键值对)。
- 空行分隔头部和主体。
- 消息主体。
5.5 如何解析HTTP请求?状态机的作用是什么?
(1)解析步骤:
- 解析请求行(方法、路径、版本)。
- 解析请求头(键值对)。
- 解析请求体(根据Content-Length或Transfer-Encoding)。
(2)状态机的作用:
cpp
enum HttpRecvStatu {
RECV_HTTP_LINE, // 正在解析请求行
RECV_HTTP_HEAD, // 正在解析请求头
RECV_HTTP_BODY, // 正在解析请求体
RECV_HTTP_OVER // 解析完成
};
- 跟踪解析进度。
- 处理不完整请求(数据分多次到达)。
- 错误恢复和状态重置。
6. 性能优化与稳定性
6.1 如何避免大量连接时的内存碎片问题?
- Buffer使用vector管理内存,减少小内存分配。
- Connection使用shared_ptr统一管理,避免内存泄漏。
- 使用对象池技术(可扩展):预分配Connection对象。
- 内存池:自定义内存分配器(可优化项)。
- 避免频繁的new/delete,使用智能指针自动管理。
6.2 在大文件传输时,如何避免内存占用过大?
- 流式处理:不一次性读取整个文件到内存。
- 使用sendfile零拷贝技术(可扩展)。
- 分块传输:设置合适的Buffer大小。
- 流量控制:监控内存使用,避免OOM。
6.3 业务处理耗时过长导致其他连接超时的问题如何解决?
(1)问题分析:
- 业务处理阻塞EventLoop线程。
- 导致定时器任务无法及时执行。
- 其他连接可能被错误关闭。
(2)解决方案:
- 将耗时业务放到工作线程池处理。
- EventLoop线程只负责I/O和简单任务。
- 工作线程处理完后,通过任务队列返回结果。
- 本项目预留了接口,用户可自行实现工作线程池。
7. 异常处理与稳定性
7.1 服务器如何应对恶意连接或DDoS攻击?
- 连接数限制:单IP最大连接数。
- 频率限制:请求频率限制。
- 超时机制:快速释放空闲连接。
- 资源监控:监控CPU、内存、连接数。
- 优雅降级:压力大时拒绝新连接。
- 日志记录:记录异常行为。
7.2 如何保证服务器的7x24小时稳定运行?
- 资源管理:监控和限制资源使用。
- 错误恢复:异常捕获和恢复机制。
- 内存管理:防止内存泄漏和溢出。
- 日志系统:详细日志便于故障排查。
- 监控告警:关键指标监控和自动告警。
- 灰度发布:逐步发布新版本。
- 压力测试:上线前充分测试。
7.3 遇到客户端突然断开连接,服务器如何处理?
(1)检测机制:
- read()返回0表示对端关闭。
- EPOLLRDHUP事件(对端关闭写)。
- EPOLLHUP事件(连接挂断)。
(2)清理资源:
- 关闭socket文件描述符。
- 从epoll中移除监控。
- 释放Connection对象。
- 从连接池中移除。
(3)异常处理:
- 避免向已关闭的连接写数据(SIGPIPE)。
- 正确处理资源泄漏。
8. 扩展与维护
8.1 如何扩展支持WebSocket协议?
- 协议升级:在HTTP握手后升级到WebSocket。
- 帧解析:实现WebSocket帧格式解析。
- 心跳机制:保持连接活跃。
- 集成到现有架构:
- 创建WebSocketContext类似HttpContext。
- 在Connection中切换协议处理器。
- 复用现有的事件驱动框架。
8.2 如何实现关闭服务器?
- 信号处理:捕获SIGINT/SIGTERM。
- 停止接受新连接。
- 等待现有连接处理完成。
- 逐步关闭所有连接。
- 清理资源,退出进程。
9. 综合问题
9.1 如果让你重新设计这个系统,你会做哪些改进?
- 增加工作线程池,分离I/O和业务处理。
- 支持更丰富的协议(WebSocket、gRPC等)。
- 改进负载均衡策略,考虑连接活跃度。
- 增加监控和诊断工具。
- 优化内存管理,支持内存池。
- 支持配置热更新。
9.2 这个服务器能支持的最大并发连接数受哪些因素限制?
- 文件描述符限制(ulimit -n)。
- 内存限制(每个连接的内存占用)。
- CPU核心数(线程数优化)。
- 网络带宽。
- 操作系统网络栈配置(tcp_max_syn_backlog等)。
- 应用层协议处理开销。
9.3 如何测试服务器的并发性能?需要注意什么?
- 测试工具:webbench、wrk、ab等。
- 测试场景:不同并发数、请求大小、连接保持时间。
- 监控指标:QPS、响应时间、CPU使用率、内存使用。
- 注意事项:
- 测试环境与生产环境一致。
- 避免测试工具成为瓶颈。
- 预热阶段和稳定阶段分离。
- 长时间稳定性测试。
9.4 如果让你设计一个支持百万并发连接的服务器,你会考虑哪些方面?
(1)架构设计:
- 分布式架构,多机负载均衡。
- 连接分散到多个进程/机器。
(2)内存优化:
- 每个连接内存消耗最小化
- 使用内存池和对象池
(3)CPU优化:
- CPU亲和性,减少上下文切换
- 使用RSS(接收端缩放)多队列网卡
(4)网络优化:
- 使用多端口监听
- TCP优化(快速打开、窗口缩放)
9.5 这个项目中你遇到的最大技术挑战是什么?如何解决的?
(1)挑战1:定时器刷新和取消的线程安全问题。解决方案:
- 使用时间轮+shared_ptr,刷新时创建新定时任务。
- 旧任务shared_ptr计数不为0,不会实际执行。
- 所有定时器操作都在EventLoop线程执行。
- 使用weak_ptr保存引用,避免循环引用。
- 效果:实现了线程安全的定时器,支持动态刷新和取消。
(2)挑战2:需要弄清楚函数bind的路线,在实现的过程当中函数bind太乱容易昏头。