脚本之家
你与百万开发者在一起
![]()
![]()
本项目仅仅是个人的玩具项目, 其中缺陷很多, 问题也很多. 首先整个网络部分是自己看了两三章UNP自己封出来的. 老版本还有一点点错误处理, 新版本完全忽略了错误处理. 另外就是过度设计, 许多的地方没有必要预留变更空间, 我都预留了. 项目地址:https://github.com/lzxZz/dlagon ![]()
http通信
一个web服务器, 其通信是基于请求/响应的. 因此一个最基本的web服务器如下图所示:
![]()
当然了,web是基于HTTP协议的, 因此请求和响应的格式都需要符合HTTP协议的格式. 请求的协议格式由浏览器控制, 响应的格式由服务端控制. 因此在开发的时候, 我们需要控制的只有响应的格式. 但是请求的格式也是需要关注的. 对于不同的参数我们需要进行不同的控制.
一个最基本的HTTP响应如下所示:
- [/code]
- [*][code]Hello World
复制代码 麻雀虽小, 五脏俱全, 响应中的三个部分(响应行, 响应头, 响应体)都是有的. 需要注意的是,在响应头和响应体之间有一个空行. (更多的信息请查看http协议的格式, 这里有一点偏离主题了).
![]()
初代机版本
一个web框架在通信上也无非是和上面做的事情一样. 框架和应用的区别就在于框架封装了应用的稳定部分流程. 假如从接受数据开始,一直编写到数据发送的web应用. 其中要经过以下步骤:
- 数据读取
- 协议解析(长链接还可能需要处理粘包的问题)
- cookie读取, 判断属于哪一个session
- 请求转发到具体的处理函数(对于不同的页面有不同的处理, 例如静态页面和动态页面就需要分开处理)
- 具体处理, 生成响应
- 将响应转化为字符串序列
- 数据发送
这七个步骤, 除去4,5 步是根据需求变更的, 其他的5个步骤都是固定的. 那么框架所作的任务就是将固定的5个步骤封装起来. 预留出接口, 等待用户(web开发人员)编写处理函数, 注册路由信息即可. 在一些现代的web框架中, 路由的注册往往是通过文件来实现的. 这样一来, 代码中会变动的只有处理函数了.
因此, dlagon的结构如下图所示:
![]()
其中缺少了cookie和session的处理, 不过也无伤大雅, 开发的时候自己加上即可.
其中需要用户编写的部分就是路由和处理函数部分了.路由表我使用了的形式,为请求路径,为处理函数指针. 在项目的中可以看到定义.
这个版本在能够完成静态文件(只有html, js, css三种文件格式)服务之后, 渐渐的就停止了开发. 然后就对项目又一次的进行了重构. 参考了的洋葱模型.
![]()
目前开发版本
上个版本中有不够灵活的问题, 例如我想添加和部分, 就需要对整个框架进行修改, 在路由和解析中间强行加入一个组件.违背了设计模式中的对扩展开放, 对修改封闭的原则. 因此新的架构如下:
![]()
目前这个版本正处于开发之中, 在分支中.
![]()
IServer接口
首先介绍核心的接口.
- IServer(IProtocolObjectFactory *factory,
复制代码- INetServerSocketAdapter *server,
复制代码
- // IServer() = delete; // 保留默认构造函数, 用于工厂构造.
复制代码
- * @brief 用户使用的唯一程序, 其作用为绑定本地任意ip的端口,并开始监听,处理
复制代码- server_socket_->Bind(port);
复制代码- server_socket_->Listen(1024);
复制代码- INetClientSocketAdapter *client = server_socket_->Accept();
复制代码- std::thread th(Work, this, client);
复制代码
- static void Work(IServer *self, INetClientSocketAdapter *client){
复制代码
- std::string str = client->Receviced();
复制代码- Request *req = self->factory_->RequestFromString(str);
复制代码- Response *res = self->factory_->GetResponse();
复制代码
- self->midware_->WorkFlow(*req, *res);
复制代码- const std::string result = res->ToString();
复制代码- IProtocolObjectFactory *factory_ ;
复制代码- INetServerSocketAdapter *server_socket_ ;
复制代码
首先介绍三个成员
- 是一个抽象工厂, 用来将读取到的消息转化为对应的消息对象.
- 是一个网络套接字, 使用了适配器模式, 方便替换其实现为别的网络库.
- 是中间件部分, 所有的消息都通过中间件进行处理.
然后就是方法.这是本类中最最核心的内容. 因为需要用到多线程, 线程只能绑定静态成员函数. 因此函数的首个参数就是指针.
然后调用网络客户端的方法, 接收信息.
然后使用协议的构造工厂, 构造出请求对象和响应对象.(由于没有做长链接的处理, 所有的套接字都是用一次, 因此没有粘包问题, 这里也就没有特殊处理. 如果加上了长链接这个是需要特殊处理的.)
之后就是调用中间件处理请求. 同时也将响应对象传递进去.
处理完之后, 调用响应对象的方法, 然后将信息发送回去.
最后清理一下内存(这里打算使用智能指针, 将内存管理全部交给RAII).
接下来介绍一下中间件
![]()
Midware
- dlagon/interface/midware.h
复制代码- * 在服务器流程中, 会自动的沿着中间件链一直执行, 通过返回值判断是否继续执行
复制代码- void SetNext(Midware *next){
复制代码
- void WorkFlow(const Request &req, Response &res);
复制代码- virtual MidwareState Handler(const Request &req, Response &res) = 0;
复制代码- Midware *next_ = nullptr;
复制代码 这个组件十分简单,
- 指针, 指向下一个中间件.
- 纯虚函数, 继承后重载该函数,来进行业务流程.
- 函数, 用于判断是否继续执行.
实现如下:
- void Midware::WorkFlow(const Request &req, Response &res){
复制代码- MidwareState state = Handler(req, res);
复制代码- if (state == MidwareState::kContinue){
复制代码- next_->Handler(req, res);
复制代码 这里在使用职责链的时候,做了一些变化, 并没有使用常见的成员变量表示是否继续的方法, 因为考虑到多线程的问题, 而中间件又是单例的, 因此, 如果是数据成员, 就会存在数据竞争的问题. 因此我使用了函数的返回值作为状态标识.
以上就是整个框架的流程部分了. 后面的就是一些实现的细节内容. 只需要继承, 然后根据自己的需要编写流程函数.
此外还有关于的内容没有介绍, 都是一些脏活, 项目中用到的大致上有下面的内容:
- HTTP解析(字符串解析, 基本技能)
- MIME-Type(判断请求uri的后缀)
- cookie, session(这个在老版本有实现, 新版本暂时没有开始)
这里先说一个踩过的坑吧,请求的行分隔符是- [/code], 在使用 [code]getline
复制代码 函数的时候, 会自动的省略掉- [/code], 因此行结尾和空行一定是一个 [code]
复制代码 .
对于模板页, 配置文件等暂时都没有开发计划.本文作者:lzxZz
声明:本文为 脚本之家专栏作者 投稿,未经允许请勿转载。 写的不错?赞赏一下
![]()
长按扫码赞赏我
-END-
![]()
![]()
● ![]()
“互联网从此没有 BAT”
● ![]()
脚本之家粉丝福利,请查看!
● ![]()
[/url][url=http://mp.weixin.qq.com/s?__biz=MjM5NTY1MjY0MQ==&mid=2650745925&idx=4&sn=b57f206811a0a5831d7306a0ab8d93b3&chksm=befeb90b8989301d7e1a89bf478c18e48a01d9f87c2f1772ddadff9ed862d93a94de4199c1e6&scene=21#wechat_redirect]6月数据库排行:PostgreSQL和MongoDB分数罕见下降
● 微软劝你别再使用 IE 浏览器
● 痛的不只是华为,这家西方公司卡住中国芯片的脖子
● 一个39岁程序员的应聘被拒
● 入行AI,程序员为什么要学习NLP?
● 五款主流Linux发行版性能对比,不求最强但求稳
小贴士返回 上一级 搜索“Java 女程序员 大数据 留言送书 运维 算法 Chrome 黑客 Python JavaScript 人工智能 女朋友 MySQL 书籍 等关键词获取相关文章推荐。
|
|