本项目C++为初学者的学习项目,从epoll到HTTP服务器到Web项目,除了用了boost的字符串处理,全部手撸,没有任何其他依赖。 实现了用户的注册、登录、充值、交易(转账)功能。
epoll服务器部分参考《Linux系统编程》以及boost的axio的example(尤其是HTTP协议解析部分)。
web后台是笔者按照自己的Java Web开发经验整出来的简单实现。
web前端使用bootstrap可视化编程做的,jq写的比较烂。
数据库采用的是linux文件系统,仅仅做了简单的文件加载以及保存。
本项目的主要目的为技术学习,最好是有大佬给出建议和指正。
第一次使用,进行注册,注册完毕后会记录登录状态。
我们可以先进行充值,然后查看钱包
参考环境
直接使用Cmake编译运行即可(笔者Cmake还没有系统学过,用的大概是最搓的手法)
运行参数: 项目地址 端口 前端文件夹地址 数据库文件夹地址 address port doc_root database_root
如: 172.x.X.22 8787 /home/b6/payhttp/resources/htmls /home/b6/payhttp/resources/database
├── CMakeLists.txt
├── main.cpp
├── readme.md
├── readme-img
├── resources – 静态资源
│-------- ├── database – 数据文件
│-------- └── htmls – 前端文件
├── server – 服务器代码
│-------- ├── include
│-------- │-------- ├── Constant.hpp – 一些常量
│-------- │-------- ├── EpollConnection.hpp – 客户端连接
│-------- │-------- ├── EpollConnectionManager.hpp – 连接管理器
│-------- │-------- ├── EpollServer.hpp – 服务入口
│-------- │-------- ├── Header.hpp – HTTP请求头kv对 (可换成map)
│-------- │-------- ├── RequestHandler.hpp – HTTP请求头预处理器
│-------- │-------- ├── Request.hpp – HTTP请求头
│-------- │-------- ├── RequestParser.hpp – HTTP请求头信息解析器(状态机)
│-------- │-------- └── Response.hpp – HTTP响应头
│-------- └── src – 对应源码
└── web – Web后台代码
│-------- ├── core – 运行所需的一些非业务核心类
│-------- │-------- ├── GlobalAutoIds.cpp – 主键生成器
│-------- │-------- ├── GlobalAutoIds.hpp
│-------- │-------- ├── Json.cpp – 蹩脚的Json解析器
│-------- │-------- ├── Json.hpp
│-------- │-------- └── RCode.hpp – 响应体的状态信息
│-------- └── src – Web三层架构代码
│-------- │-------- ├── controller – 控制层
│-------- │-------- └── service – 业务层
│-------- │-------- ├── dao – 持久层
│-------- │-------- ├── model – 数据模型
别名:
sfd:服务器socket fd
cfd:客户端socket fd
采用epoll_wait阻塞+ET模式+cfd非阻塞形式。
在while循环中进行epoll_wait,只监听读事件;
对于sfd事件,执行doAccept;
对于cfd事件,另开线程执行doRead,在doRead中完成本次请求的解析以及响应。
线程池后续完善
笔者比较熟悉Java,对于c++的web开发不是很了解,网上的文字资料似乎比较少。
开源代码虽然有,但是体量太大,短时间研究时间投入太大,因此笔者直接使用Java的思想写了一个。
一开始想用反射,然而c++本身不支持反射,自己再手撸一个反射有点难,于是做了一个比较笨拙的实现。
在Java中(以Tomcat为例),需要开发者使用注解或者是xml文件配置控制器与uri的映射关系,本质上都是“写死的代码”, xml配置只是方便了开发者,而注解配置看似动态,其实只是还是配死的。 因此其实只需要模拟这个配置过程即可。
controller目录下只有一个控制器BaseController(笔者暂时没做到控制器分离,后续描述原因)。
该控制器持有一个<uri,Handler> 的map容器,Handler是一个函数指针,表示处理对应的uri。
系统启动时,会执行注册方法,将BaseController下的所有<uri,Handler>加到map容器中,然后在服务器处理程序中通过uri获取到 对应的Handler函数指针,进行执行。
此处Web后端代码与服务器代码耦合,服务器代码需要使用Web后端提供的一个容器。(更像是依赖不符合常理,服务器程序依赖了上层逻辑)。
解决思路:<url,Handler>容器服务端代码持有,让Web后端实现进行注册。(一开始没设计好)
由于笔者本身技术水平有限,下面描述一下当初设计与实现的时候,为什么没能分离控制器。
可以看到,控制器只有一个BaseController,所有的处理都在一个方法里面,实际情况应该是UserController、TransactionController…
一般情况下,一个model类对应一个Controller。
之所以只有一个,是因为需要提供一个控制器方法的“扫描器”。(扫描器只是一个描述,表示能够让服务器代码感知到url与controller的对应关系。)
当前的扫描器就是一个map容器,而这个map容器被BaseController持有也好,被EpollServer持有也好,最终都需要控制器本身将自己注册进去。
Java的扫描器基于反射,比如说注解形式,服务器程序会在运行时扫描对应的注解,然后建立一个<url,controller>的容器。 由于注解的存在(或者说反射机制),无论你的控制器散落在哪,都能被扫描然后注册到对应容器中。
但是c++本身没有容器,现在考虑以下情况:
假如现在有两个控制器,UserController、TransactionController, 然后他们各自拥有5个<uri,handler>的方法,此时需要将这10个方法在运行时让EpollServer感知到。
所以最终选择了这么个笨拙的方法,只有一个控制器类,免除了扫描困扰。
前端渣渣,用的 bootstrap可视化 写的页面。
核心代码就是那些ajax请求,该部分比较简单。
只需要在BaseController::handle里面添加uri和其处理方法即可。
重申,该项目只是笔者学习C++之后,为了熟悉C++语法所做的一个小项目,肯定有诸多不合理的地方,感谢大佬批评指正 _