大道至简, 返璞归真.
前言
在发表这篇博文的前夕, 还有一些小伙伴在提问一些以下相关的问题:
-
性能怎么样?
-
是否容易上手?
-
开发目标在哪?
-
如何反馈问题?
- 对比行业内的lua开源项目有何优势?
等等, 以上问题会在本文中一一介绍.
CF的起因
首先来聊聊情怀这个东西! 相信每一个行业内的从业者都或多或少有过一个梦, 这个梦叫做: "我到时候要开发一个XXX"!其实作者当初也是一样.
每当半夜(凌晨)在加班、看文档、调试的时候, 总会搜索到一些几年前或十几年前的框架或入门demo。例如: tinyhttp, 链接的源码是一些同学fork的镜像站。
每次看到这些内容或多或少都会激起心中那一丝丝快熄灭的热情, 也许这就是最后对技术的渴望?
就是在动手创建项目之前还反复问过自己是否要做? 能坚持下去么?也许被喷都是一种奢望?
在心里一一回答了这些问题后, 在2018年末创建了本项目.
说句实话! 一个网络开发框架最难的不是实现某个功能, 而是从零开始一步一步添砖加瓦的造轮子!
作为一个网络开发框架, 最重要的两个功能肯定是需要的! 定时器库、事件驱动库. 如何抉择?选项有2个: libev / libuv .
libev 成熟稳定、轻量级、unix like支持、容易嵌入;
libuv 比libev更加优秀,增加了许多功能(线程池、信号、同步、锁等等),封装更加完善, 并且增加了windows支持;
从cf框架开发之初选型来看, libuv绝对是目前最优解. 但是作者偏偏选择了libev. 也从此开始, 艰辛的底层开发之路就此展开.
首先, 作者不让使用者C/C++进行实际业务开发! 这样做会让使用者有较高的开发成本与学习成本, 而选择一门较好的脚本语言就显得尤为重要.
作者对Lua还算是稍微熟悉一点, 所以就选了Lua作为业务脚本语言。至于Lua语言的优势这里就不说了, 网上大把文章夸它的.
现在既然脚本语言已经选定, 那么就开始写代码吧!Let's Lua.
CF的编写之路
1. 网络层
首先, 我们来看一段C封装给Lua调用的API代码:
LUAMOD_API int
luaopen_tcp(lua_State *L){
luaL_checkversion(L);
/* 添加SSL支持 */
SSL_library_init();
SSL_load_error_strings();
// CRYPTO_set_mem_functions(xmalloc, xrealloc, xfree);
// OpenSSL_add_ssl_algorithms();
/* 添加SSL支持 */
luaL_newmetatable(L, "__TCP__");
lua_pushstring (L, "__index");
lua_pushvalue(L, -2);
lua_rawset(L, -3);
lua_pushliteral(L, "__mode");
lua_pushliteral(L, "kv");
lua_rawset(L, -3);
luaL_Reg tcp_libs[] = {
{"read", tcp_read},
{"write", tcp_write},
{"ssl_read", tcp_sslread},
{"ssl_write", tcp_sslwrite},
{"stop", tcp_stop},
{"start", tcp_start},
{"close", tcp_close},
{"listen", tcp_listen},
{"connect", tcp_connect},
{"ssl_connect", tcp_sslconnect},
{"new", tcp_new},
{"new_ssl", ssl_new},
{"free_ssl", ssl_free},
{"new_server_fd", new_server_fd},
{"new_client_fd", new_client_fd},
{NULL, NULL}
};
luaL_setfuncs(L, tcp_libs, 0);
luaL_newlib(L, tcp_libs);
return 1;
}
以上是TCP实现的C代码的片段, 有兴趣阅读源码的小伙伴请点击这里;
众所周知Lua没有原生的Socket. 那么就需要框架编写者自己抽象底层逻辑重新实现一套API.
简单的封装Lua C库谁都会, 而且也算不上是什么难事. 但是我们的目的是将底层同步阻塞Socket hook为非阻塞, 这时候难点就来了!
大家都知道libev是基于react模型的事件驱动网络库, 所有注册事件后的业务逻辑都是以回调的形式触发. 那不就变成node-lua代码了吗?(笑)
这时候, 作者想了个点子来解决这个问题! 执行流程如下:
每次需要做一些同步操作的时候, 就调用C API注册回调事件.
为当前注册的所有事件创建一个Lua协程保存上下文并让出当前协程执行权.
- 等到注册事件被触发后, 调用C API恢复协程继续执行.
简单来说就是将C层次的异步回调逻辑封装为Lua层的同步非阻塞, 保证不因为IO问题阻塞线程.
下面提供一段socket同步非阻塞的伪代码, 经供参考:
function TCP:recv(bytes)
local current_co = co_self()
self.read_co = read_ev(function()
-- do action
-- stop timer_ev
-- wakeup(current_co) 恢复执行权
end)
self.timer_co = self.timer_ev(function()
-- do action
-- stop read_ev
-- wakeup(current_co) 恢复执行权
end)
tcp_start(io, EV_READ, self.read_co)
timer_start(timer, 3秒超时, self.timer_co)
return co_yield() -- 让出执行权
end
一个Lua版的Socket EV_READ伪代码大致的处理流程如上, 想看实际处理逻辑请看这里。
同理, Socket write/connect/listen等等API直接照抄就行(UDP也大同小异). (其实这里有个小插曲就是SSL SOCKET的坑, 但是由于篇幅问题就不说了.)
细心的小伙伴可能发现代码同时注册了Socket与Timer事件, Socket非阻塞操作不能解决read与connect超时的问题. 所以cf框架干脆就封装彻底一点.
至此, Socket算是已经算是基本hook与封装完成了. 接下来就可以开始写应用层协议了.
2. 应用层协议
现在Socket终于能正常使用了, 那么面临的新问题就又来了。
libev没有自带异步dns
dns都还需要使用者自己封装, 这个坑真是填的无比难受! 好在网络上有前辈实现了Lua版的异步dns, 作者稍微看明白之后就借用了过来封装内部使用.
这样cf也算是有了深度定制的异步dns库了吧!(虽然并不完善, 但是足够使用)
一个网络库是否流行, 基本上就得看生态. 那么协议层的轮子又得造起来:
- httpd与httpc
- mysql
- redis
- mqtt
- websocket
其中一些协议为各位前辈那边借过来适配后定制的, 简单的协议则是直接花1-2小时直接手写出来的。
3. 封装与易用性
为了不让API那么封闭与提升cf的可用性, 作者决定将mysql与redis进行初步封装.
封装包括大家常用的功能, 连接池、面向对象操作、无需手动管理session生命周期等等. 简化编程思想包袱来提升开发效率.
至于内部Socket更是让框架来解决释放问题确保文件描述数量限制的情况下也是可以正常使用. (其实是不喜欢依赖gc被动close fd与free内存)
CF是啥?
如果你耐心看完了第一部分介绍, 那么你就应该对cf有了一个大概的了解.
cf全称为: CoreFramework, 是一个基于libev的Lua网络开发框架. 在其内部实现了多种网络协议与第三方库用来帮助使用者进行项目原型的快速开发.
cf 在httpd使用上尊崇前、后端分离的解决方案, 仅实现了基本的view路由并且不支持rest风格的API路由. 虽然这样可能会引来宇多人的诟病.
cf 的httpd内嵌websocket支持, 方便使用者在复用端口的同时也可以享受长连接编写的乐趣.
更多的介绍, 请大家项目地址的Wiki
CF能做什么?
-
基于容器技术的微服务场景(swarm/kubernetes); —— 推荐
-
游戏服务器的前端代理层; —— 推荐
-
内存/CPU资源较为紧缺的云服务器; —— 推荐
-
对性能要求较高的无状态集群; —— 推荐
- 海量长连接(websocket)Agent集群; —— 推荐
CF使用到的技术栈?
传输层: TCP/UDP
会话层: SSL Client支持
协议层: dns/webocket/http/mqtt/redis/mysql/smtp
工具库: Timer/TASK
第三方库: Libev、openssl/libressl、lua-5.3、jemalloc/tcmalloc(可选)
CF如何安装?
cf 目前支持绝大部分Unix like操作系统, 作者是在Mac上进行开发, 所以Mac支持是必须的.
cf测试的Linux为Centos, 所以基本上基于Linux内核的操作系统编译后的运行也没什么问题(export 增加/usr/local/lib)
同时,作者还贴心的为大家做了一个简单Dockerfile. 文件在项目根目录下, 大家下载直接使用即可。
当然, 如果你不想制作Dockerfile,也可以使用Docker命令直接拉去作者制作好放在docker hub的镜像. candymi/cfweb
使用详情与使用方法请参考Docker安装和编译安装
CF 如何运行呢?
测试运行
bash#: ./cfadmin
后台运行
bash#: ./cfadmin
退出
killall cfadmin
ctrl + c
文档在哪?
作者为大家贴心的写了一篇详细到不能再详细的文档, 以此来获取大家的点赞与关注.
作者还为喜欢阅读源码的同学准备了充足的中文注释与英文注释, 结合起来方便大家快速了解CF工作方式(中/英注释结合易于理解一些专属词汇).
回答之前的问题:
Q. 性能怎么样?
A. 性能还不错, 但是具体数值请自行测试.
Q. 是否容易上手?
A. 学习lua 一小时入门 -> cf 一小时入门
Q. 开这个项目的初衷是什么?
A. 其实在前面已经回答过了.
Q. 开发目标在哪?
A. Wiki 里有TODO项
Q. 如何反馈问题?
A. Wiki 里有Q & A项
Q. 对比行业内的lua开源项目有何优势?
A. CF对比其它lua开发项目更深入改变用户使用习惯! 简化框架上手难度, 将框架都黑盒子透明化. 无需学习复杂的设计模式与理念.
Q. CF的开发理念是什么?
A. CF项目的目标不是竞争, 而是明白明白简单为美. 当你习惯了它, 也许你就会上瘾.
使用示例
-
容器部署
-
Dockerfile快速构建开发环境
- cf k8s中的应用示例
-
-
cf web的初始化与使用
-
cf web Websocket应用指南
- cf web 中间件开发指南
精彩截图
希望
也许你正在使用其它开发框架, 但是这不妨碍你对cf的督促.
也许你正在试用它, 这不妨碍你与作者沟通你的想法.
也许你正在吐槽它的缺点,请来issue尽情吐槽.
文档与地址
项目文档
项目地址