11.1.2 文件传输和接口设计
1 网络模型
1.1 网络模型框图

核心原理:
- 可以开多个subReactor,每个subReactor对应一个线程
- mainReactor负责处理新连接,并将新连接均匀分配给subReactor,后续该新连接由对应的 subReactor处理read,send等操作。
- ThreadPool的线程池是不是只给单一的subReactor使用,而是可以给多个subReactor同时使用。
- 线程池的线程在执行任务的时候,数据回发给客户端是通过TcpConnection的 send接口,但需要 注意的是,此时的send接口不是直接调用Linux的网络发送接口,而是先把数据缓存起来, 由 subReactor来发送数据。
1.2 网络模型核心类
对于muduo的核心类和函数,这里我们只讲解涉及到的类和函数。
Channel
Channel:处理socket fd描述符,封装read_callback 、 write_callback 、 close_callback 、 error_callback 等IO事件
EpollPoller
EpollPoller:负责监听文件描述符事件是否触发以及返回发生事件的文件描述符以及具体事件的模块就是 Poller。在multi-reactor模型中,有多少reactor就有多少EpollPoller,EpollPoller给外部提供的最重要的方法:
TimeStamp poll(int timeoutMs, ChannelList *activeChannels)
这个函数可以说是Poller的核心了,当外部调用 poll 方法的时候,该方法底层其实是通过 epoll_wait 获取 这个事件监听器上发生事件的fd及其对应发生的事件,我们知道每个fd都是由一个Channel封装的,通过哈希表 channels_ 可以根据fd找到封装这个fd的Channel。将事件监听器监听到该fd发生的事件写进这个 Channel中的revents成员变量中。然后把这个Channel装进 activeChannels 中(它是一个 vector)。这样,当外界调用完 poll 之后就能拿到事件监听器的监听结果 ( activeChannels_ )。
EventLoop
EventLoop:EpollPoller是封装了和事件监听有关的方法和成员,调用一次 Poller::poll 方法它就能给你 返回事件监听器的监听结果(发生事件的fd 及其 发生的事件),EventLoop则是负责实现“循环”,负责驱动 “循环”的重要模块!!Channel和Poller其实相当于EventLoop的手下,EventLoop整合封装了二者并向上提 供了更方便的接口来使用。
EventLoopThread:实际的线程,EventLoop的循环工作在EventLoopThread线程。
void EventLoopThread::threadFunc()
{
EventLoop loop;
....
loop.loop(); // EventLoop循环工作
...
}
或者主线程里
int main(int argc, char *argv[]) {
....
EventLoop loop; //主循环
....
HttpServer server(&loop, addr, "HttpServer", num_event_loops);
server.start();
//主线程的
loop.loop(); //main reactor
return 0;
}
TcpServer
tcp的网络服务,基于muduo开发必须掌握其函数接口的作用。
- void setThreadNum(int numThreads):设置io线程数量
- void start():启动服务
- void setConnectionCallback(const ConnectionCallback& cb):设置新连接和断开连接回调
- void setMessageCallback(const MessageCallback& cb):设置收到数据的回调
- void setWriteCompleteCallback(const WriteCompleteCallback& cb):设置写数据发送完毕的事件
连接的删除可以断点:TcpConnection::connectDestroyed(),后续触发TcpConnection析构后 ,在析构函 数里也会析构 socket_ 最终调用:sockets::close
ThreadPool
线程池,我们可以用做业务线程池。常用函数:
void start(int numThreads):设置线程池的线程数量,并启动线程池
void run(Task task):把任务放入线程池执行,比如:
thread_pool_.run(std::bind(&CHttpConn::OnRead, http_conn, buf));
2 代码框架
如何从0添加代码


要添加新应用就是在这application层级的CMakeLists.txt添加即可,比如现在我们要基于muduo从0构建图 床项目,我们写成
add_subdirectory(tc_http_srv)
然后在application层级新建tc_http_srv目录,并添加main.cc和CMakeLists.txt。 即是:

// main.cc
#include <iostream>
int main()
{
std::cout << "hello tuchuang\n";
return 0;
}
CMakeLists.txt
ADD_EXECUTABLE(tc_http_srv main.cc)
在tc-src/build目录下重新编译:

编译出来新的应用程序,后续我们基于tc_http_srv开始沟通图床项目。
后续CMakeLists.txt还涉及的函数,下面举例,实际目录按项目要求来:
# AUX_SOURCE_DIRECTORY 查找在某个路径下的所有源文件,并可以取别名为比如BASE_LIST
# CMAKE_CURRENT_SOURCE_DIR 代表当前处理的源文件目录的路径
AUX_SOURCE_DIRECTORY(${CMAKE_CURRENT_SOURCE_DIR}/base BASE_LIST)
# 包含的头文件路径INCLUDE_DIRECTORIES
#INCLUDE_DIRECTORIES(/usr/include/fastdfs /usr/include/fastcommon
/usr/local/include/hiredis /usr/include/mysql)
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/base)
#顶层路径 CMAKE_SOURCE_DIR
INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR})
#muduo库文件路径
#muduo库文件路径
LINK_DIRECTORIES(${CMAKE_SOURCE_DIR}/lib)
# 依赖其他库时要加上对应的库名
TARGET_LINK_LIBRARIES(tc_http_srv muduo_net mysqlclient pthread)
CMAKE_CURRENT_SOURCE_DIR:当前正在处理的源目录的路径.
CMAKE_SOURCE_DIR: 最外层CMakeLists.txt所在目录
知识扩展
MD5
MD5即Message-Digest Algorithm 5(信息-摘要算法5),用于确保信息传输完整一致。是计算机广泛使用 的杂凑算法之一(又译摘要算法、哈希算法),主流编程语言普遍已有MD5实现。
理论上MD5是不可逆的,而且MD5本来也不是作加密使用,而是用来校验数据的完整性,只是因为其不可逆 且稳定、快速的特点,被广泛用于对明文密码的加密。
但是简单密码来说,破解者完全可以将一定范围内的密码字典全部计算出来之后存为数据库,之后直接查询 进行破解
Token验证
Token 的中文有人翻译成 “令牌”,意思就是,你拿着这个令牌,才能过一些关卡。
Token是一个用户自定义的任意字符串。在成功提交了开发者自定义的这个字符串之后,Token的值会保存 到服务器后台。只有服务器和客户端前端知道这个字符串,于是Token就成了这两者之间的密钥,它可以让 服务器确认请求是来自客户端还是恶意的第三方。
使用基于 Token 的身份验证方法,在服务端不需要存储用户的登录记录。大概的流程是这样的:
- 客户端使用用户名跟密码请求登录
- 服务端收到请求,去验证用户名与密码
- 验证成功后,服务端生成一个 Token,这个Token可以存储在内存、磁盘、或者数据库里,再把这个 Token 发送给客户端
- 客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage
- 客户端每次向服务端请求资源的时候需要带着服务端签发的 Token
- 服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数 据
简单而言,本服务器程序是采用随机数+base64+md5生成的token,对于过期时间是通过redis的key超时机 制实现。
更多推荐




所有评论(0)