
描述
概叙
acl 工程是一个跨平台(支持LINUX,WIN32,Solaris,MacOS,FreeBSD)的网络通信库及服务器编程框架,同时提供更多的实用功能库。通过该库,用户可以非常容易地编写支持多种模式(多线程、多进程、非阻塞、触发器、UDP方式、协程方式)的服务器程序,WEB 应用程序,数据库应用程序。此外,该库还提供了常见应用的客户端通信库(如:HTTP、SMTP、ICMP、redis、memcache、beanstalk、handler socket),常见流式编解码库:XML/JSON/MIME/BASE64/UUCODE/QPCODE/RFC2047 etc。
acl github: https://github.com/acl-dev/acl --(1.8K⭐) acl 库下载:https://gitee.com/zsxxsz/acl/tree/master --(51⭐) acl 文章主页:http://zsxxsz.iteye.com/ demo:https://github.com/acl-dev/demo qq 群:242722074
库组成
本工程主要包含 5 个库及大量示例。5 个库的说明如下:
lib_acl: 用 C 语言写的基础库,其它 4 个库均依赖于该库;lib_protocol: 用 C 语言写的一些网络应用协议库,主要实现了 http 协议及 icmp/ping 协议lib_acl_cpp: 该库用 C++ 语言封装了 lib_acl/lib_protocol 两个库,同时增加了一些其它有价值的功能应用。lib_fiber: 用C 语言编写的支持高性能、高并发的网络协程库lib_dict: 该库主要实现了 KEY-VALUE 的字典式存储库,该库另外还依赖于 BDB, CDB 以及 tokyocabinet 库。lib_tls: 该库封装了 openssl 库,使 lib_acl 的通信模式可以支持 ssl。
功能模块组成
Redis 客户端库
支持 redis 集群模式及非集群模式;支持连接池方式;按 redis 数据结构类型分成独立的 C++ 类;每个命令映射为 1 个至多个函数.
根据 redis 的数据结构类型,分成 12 个大类,每个大类提供不同的函数接口,这 12 个 C++ 类展示如下:
redis_key:redis 所有数据类型的统一键操作类;因为 redis 的数据结构类型都是基本的 KEY-VALUE 类型,其中 VALUE 分为不同的数据结构类型;redis_connectioin:与 redis-server 连接相关的类;redis_server:与 redis-server 服务管理相关的类;redis_string:redis 中用来表示字符串的数据类型;redis_hash:redis 中用来表示哈希表的数据类型;每一个数据对象由 “KEY-域值对集合” 组成,即一个 KEY 对应多个“域值对”,每个“域值对”由一个字段名与字段值组成;redis_list:redis 中用来表示列表的数据类型;redis_set:redis 中用来表示集合的数据类型;redis_zset:redis 中用来表示有序集合的数据类型;redis_pubsub:redis 中用来表示“发布-订阅”的数据类型;redis_hyperloglog:redis 中用来表示 hyperloglog 基数估值算法的数据类型;redis_script:redis 中用来与 lua 脚本进行转换交互的数据类型;redis_transaction:redis 中用以事务方式执行多条 redis 命令的数据类型(注:该事务处理方式与数据库的事务有很大不同,redis 中的事务处理过程没有数据库中的事务回滚机制,仅能保证其中的多条命令都被执行或都不被执行);
除了以上对应于官方 redis 命令的 12 个类别外,在 acl 库中还提供了另外几个类:
redis_command:以上 12 个类的基类;redis_client:redis 客户端网络连接类;redis_result:redis 命令结果类;redis_pool:针对以上所有命令支持连接池方式;redis_manager:针对以上所有命令允许与多个 redis-server 服务建立连接池集群(即与每个 redis-server 建立一个连接池);redis_cluster:支持 redis3.0 集群模式的类。
实践
搭建环境
我的环境: centos 7
Linux/UNIX: 编译器为 gcc,直接在终端命令行方式下分别进入 lib_acl/lib_protocol/lib_acl_cpp 目录下,运行 make 命令即可。
下载
$ git clone http://git.oschina.net/zsxxsz/acl.git
$ cd acl
编译静态库:
进入 lib_acl 目录,直接运行 make(使用 gcc 编译器), 正常情况下便可以在 lib 目录下生成libacl.a 静态库进入 lib_protocol 目录,直接运行 make(使用 gcc 编译器),正常情况下便可以在 lib 目录下生成 lib_protocol.a 静态库进入 lib_acl_cpp 目录,运行make ( 或者make static) 编译 libacl_cpp.a 静态库,便可 lib 目录下生成 libacl_cpp.a 编译动态库:
编译 libacl.so, libprotocol.so, libacl_cpp.so 的方式与编译静态库的方式有所不同,需要分别进入三个目录执行: make shared rpath=$ {lib_path},其中 shared 表示需要编译动态库,${lib_path} 需要用实际的目标路径替换,比如:make shared rpath=/opt/acl/lib,则会将动态库编译好后存放于 /opt/acl/lib 目录因为 lib_acl 是最基础的库,而 lib_protocol 依赖于 lib_acl,lib_acl_cpp 依赖于 lib_protocol 和 lib_acl,所在生成动态库时,需要注意生成顺序,编译顺序为::libacl.so --> libprotocol.so --> libacl_cpp.so。另外,在编译 libacl_cpp.so 时,还需要提前编译在 resource 目录下的 polarssl 库,编译完后再编译 libacl_cpp.so 同时需要指定 polarssl.lib 库所在的路径;如果不需要 SSL 通讯方式,则需要打开 lib_acl_cpp/Makefile 文件,去年编译选项:-DHAS_POLARSSL。
$ cd lib_acl
$ make
build ./lib/libacl.a ok!
creating libacl.so
skip build libacl.so; usage: make shared rpath=xxx
$ make shared rpath=/usr/lib
creating libacl.so
building for linux
build /usr/lib/libacl.so ok!
$ cd lib_protocol
$ make
build ./lib/libprotocol.a ok!
creating libprotocol.so
skip build libprotocol.so; usage: make shared rpath=xxx
$ make shared rpath=/usr/lib
creating libprotocol.so
building for linux
build /usr/lib/libprotocol.so ok!
$ cd lib_acl_cpp
$ make
create ./lib/libacl_cpp.a ok!
$ make shared rpath=/usr/lib
creating libacl_cpp.so
building for linux
build /usr/lib/libacl_cpp.so ok!
编译成单一库
在 acl 库目录下运行:make build_one,则会生成统一库:libacl_all.a 及 libacl_all.so,该库包含了 libacl,lib_protocol,lib_acl_cpp 三个库
$ cd acl
$ make build_one
rm -f libacl.a
ln -s libacl_all.a libacl.a
g++ -shared -o ./libacl_all.so release/acl_cpp/*.o \
release/protocol/*.o release/acl/*.o \
-lpthread -lz -lrt -ldl
rm -f libacl.so
ln -s libacl_all.so libacl.so
Over, libacl_all.a and libacl_all.so were built ok!
使用 cmake 编译
$mkdir build
$cd build
$cmake ..
$make
准备工程
必知
当前使用的是编译成单一库编辑器为clion,创建一个基于c++11的工程, 并且将lib_acl_cpp/include,/lib_acl/include,lib_protocol/include,以及liball_all.so复制到工程中。如下
编译cmakelists.txt
cmake_minimum_required(VERSION 3.16)
project(acl_redis)
set(CMAKE_CXX_STANDARD 11)
add_definitions("-Wall -g")
include_directories(${PROJECT_SOURCE_DIR}/include
${PROJECT_SOURCE_DIR}/include/lib_acl/include
${PROJECT_SOURCE_DIR}/include/lib_acl_cpp/include
${PROJECT_SOURCE_DIR}/include/lib_protocol)
link_directories(${PROJECT_SOURCE_DIR}/lib/acl)
aux_source_directory(. SRC_LIST)
add_executable(${PROJECT_NAME} ${SRC_LIST})
target_link_libraries (${PROJECT_NAME} libacl_all.so)
set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
使用 acl 库编写高效的 C redis 客户端应用
实践一
main.cpp内容
#include
#include"acl_cpp/lib_acl.hpp"
using namespace std;
int main()
{
acl::redis_string cmd_string;//redis中的string类型
acl::redis_client_cluster cluster;
const char *redis_addr="127.0.0.1:6379";//设置连接的数据库地址
int conn_timeout=10;//连接redis-server的超时时间(秒)
int rw_timeout=10;//与redis-server进行通信IO的超时时间(秒)
int max_threads=100;//最大线程线程数
cluster.set(redis_addr, max_threads, conn_timeout, rw_timeout);
acl::redis_client conn(redis_addr, conn_timeout, rw_timeout);//设置连接的客户端
cmd_string.set_client(&conn);//设置该string类型是连接那个客户端
acl::redis_string string_cmd(&conn);//使用初始化一个string
const char *key="name";
if(!string_cmd.set(key, "Shayne"))//设置一个string 类型的key value
{
const acl::redis_result *res=string_cmd.get_result();
cout<<"false "< return false; } cout<<"connected"< printf("set key: %s ok\r\n", key); return true; } 实践二 #include #include #include"acl_cpp/lib_acl.hpp" using namespace std; /* * @param conn {acl::redis_client&} redis 连接对象 * @return {bool} 操作过程是否成功 */ bool test_redis_string(acl::redis_client& conn, const char* key){ // 创建 redis string 类型的命令操作类对象,同时将连接类对象与操作类对象进行绑定 acl::redis_string string_operation(&conn); const char* value = "test_value"; // 添加 K-V 值至 redis-server 中 if(!string_operation.set(key, value)){ const acl::redis_result* res = string_operation.get_result(); printf("set key : %s error: %s\r\n", key, res ? res->get_error(): "unknown error"); return false; } printf("set key: %s ok !\r\n", key); // 需要重置连接对象的状态 string_operation.clear(); // 从 redis-server 中取得对应 key 的值 acl::string buf; if(!string_operation.get(key, buf)){ const acl::redis_result* res = string_operation.get_result(); printf("get key: %s error: %s\r\n", key, res ? res->get_error() : "unknown error"); return false; } printf("get key: %s ok, value: %s\r\n", key, buf.c_str()); // 探测给定 key 是否存在于 redis-server 中,需要创建 redis 的 key类对象,同时将 redis 连接对象与之绑定 acl::redis_key key_operation; key_operation.set_client(&conn); // 将连接对象与操作对象进行绑定 if (!key_operation.exists(key)){ if (conn.eof()){ printf("disconnected from redis-server\r\n"); return false; } printf("key: %s not exists\r\n", key); }else{ printf("key: %s exists\r\n", key); } // 删除指定 key 的字符串类对象 if (key_operation.del(key) < 0){ printf("del key: %s error\r\n", key); return false; }else{ printf("del key: %s ok\r\n", key); } return true; } /** * @param redis_addr {const char*} redis-server 服务器地址, * 格式为:ip:port,如:127.0.0.1:6379 * @param conn_timeout {int} 连接 redis-server 的超时时间(秒) * @param rw_timeout {int} 与 redis-server 进行通信的 IO 超时时间(秒) */ bool test_redis(const char *redis_addr, int conn_timeout, int rw_timeout){ acl::redis_client conn(redis_addr, conn_timeout, rw_timeout); const char*key = "test_key"; return test_redis_string(conn, key); } int main() { test_redis("127.0.0.1:6379", 10, 10); return 0; } mysql 参考例子:acl/lib_acl_cpp/samples/mysql 目录 简单使用 main.cpp // mysql.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include "lib_acl.h" #include "acl_cpp/lib_acl.hpp" const char* CREATE_TBL = "create table group_tbl\r\n" "(\r\n" "group_name varchar(128) not null,\r\n" "uvip_tbl varchar(32) not null default 'uvip_tbl',\r\n" "access_tbl varchar(32) not null default 'access_tbl',\r\n" "access_week_tbl varchar(32) not null default 'access_week_tbl',\r\n" "access_month_tbl varchar(32) not null default 'access_month_tbl',\r\n" "update_date date not null default '1970-1-1',\r\n" "disable integer not null default 0,\r\n" "add_by_hand integer not null default 0,\r\n" "class_level integer not null default 0,\r\n" "primary key(group_name, class_level)\r\n" ")"; static bool tbl_create(acl::db_handle& db) { if (db.tbl_exists("group_tbl")) { printf("table exist\r\n"); return (true); } else if (!db.sql_update(CREATE_TBL)) { printf("sql error\r\n"); return (false); } else { printf("create table ok\r\n"); return (true); } } // 添加表数据 static bool tbl_insert(acl::db_handle& db, int n) { const char* sql_fmt = "insert into group_tbl(group_name, uvip_tbl)" " values('中国人-%d', 'test')"; acl::string sql; sql.format(sql_fmt, n); if (!db.sql_update(sql.c_str())) return (false); const acl::db_rows* result = db.get_result(); if (result) { const std::vector for (size_t i = 0; i < rows.size(); i++) { const acl::db_row* row = rows[i]; for (size_t j = 0; j < row->length(); j++) printf("%s, ", (*row)[j]); printf("\r\n"); } } db.free_result(); return (true); } // 查询表数据 static int tbl_select(acl::db_handle& db, int n) { const char* sql_fmt = "select * from group_tbl where" " group_name='中国人-%d' and uvip_tbl='test'"; acl::string sql; sql.format(sql_fmt, n); if (!db.sql_select(sql.c_str())) { printf("select sql error\r\n"); return (-1); } printf("\r\n---------------------------------------------------\r\n"); // 列出查询结果方法一 const acl::db_rows* result = db.get_result(); if (result) { const std::vector for (size_t i = 0; i < rows.size(); i++) { if (n > 100) continue; const acl::db_row* row = rows[i]; for (size_t j = 0; j < row->length(); j++) printf("%s, ", (*row)[j]); printf("\r\n"); } } // 列出查询结果方法二 for (size_t i = 0; i < db.length(); i++) { if (n > 100) continue; const acl::db_row* row = db[i]; // 取出该行记录中某个字段的值 const char* ptr = (*row)["group_name"]; if (ptr == NULL) { printf("error, no group name\r\n"); continue; } printf("group_name=%s: ", ptr); for (size_t j = 0; j < row->length(); j++) printf("%s, ", (*row)[j]); printf("\r\n"); } // 列出查询结果方法三 const std::vector if (rows) { std::vector for (; cit != rows->end(); cit++) { if (n > 100) continue; const acl::db_row* row = *cit; for (size_t j = 0; j < row->length(); j++) printf("%s, ", (*row)[j]); printf("\r\n"); } } int ret = (int) db.length(); // 释放查询结果 db.free_result(); return (ret); } // 删除表数据 static bool tbl_delete(acl::db_handle& db, int n) { const char* sql_fmt = "delete from group_tbl where group_name='中国人-%d'"; acl::string sql; sql.format(sql_fmt, n); if (!db.sql_update(sql.c_str())) { printf("delete sql error\r\n"); return (false); } for (size_t i = 0; i < db.length(); i++) { const acl::db_row* row = db[i]; for (size_t j = 0; j < row->length(); j++) printf("%s, ", (*row)[j]); printf("\r\n"); } // 释放查询结果 db.free_result(); return (true); } int main(void) { acl::log::stdout_open(true); // 允许将错误日志输出至屏幕 acl::string line; acl::stdin_stream in; acl::stdout_stream out; acl::db_handle::set_loadpath("/usr/lib64/mysql/libmysqlclient_r.so"); // 设置动态库加载的全路径 const char* dbaddr = "192.168.0.21:3306"; const char* dbname = "city"; const char* dbuser = "root", *dbpass = "123456"; acl::db_mysql db(dbaddr, dbname, dbuser, dbpass); int max = 100; // 允许将错误日志输出至屏幕 acl_msg_stdout_enable(1); if (!db.open()) { printf("open db(%s) error\r\n", dbname); getchar(); return 1; } printf("open db %s ok\r\n", dbname); if (!tbl_create(db)) { printf("create table error\r\n"); getchar(); return 1; } ACL_METER_TIME("---begin insert---"); for (int i = 0; i < max; i++) { bool ret = tbl_insert(db, i); if (ret) printf(">>insert ok: i=%d, affected: %d\r",i, db.affect_count()); else printf(">>insert error: i = %d\r\n", i); } printf("\r\n"); ACL_METER_TIME("---end insert---"); ACL_METER_TIME("---begin select---"); int n = 0; for (int i = 0; i < max; i++) { int ret = tbl_select(db, i); if (ret >= 0) { n += ret; printf(">>select ok: i=%d, ret=%d\r", i, ret); } else printf(">>select error: i = %d\r\n", i); } printf("\r\n"); printf(">>select total: %d\r\n", n); ACL_METER_TIME("---end select---"); ACL_METER_TIME("---begin delete---"); for (int i = 0; i < max; i++) { bool ret = tbl_delete(db, i); if (ret) printf(">>delete ok: %d, affected: %d\r", i, (int) db.affect_count()); else printf(">>delete error: i = %d\r\n", i); } printf("\r\n"); printf("mysqlclient lib's version: %ld, info: %s\r\n", db.mysql_libversion(), db.mysql_client_info()); ACL_METER_TIME("---end delete---"); printf("Enter any key to exit.\r\n"); getchar(); return 0; } 数据库连接池 int main(void) { acl::db_handle::set_loadpath("/usr/lib64/mysql/libmysqlclient_r.so"); // 设置动态库加载的全路径 const char* dbaddr = "192.168.0.21:3306"; const char* dbname = "city"; const char* dbuser = "root", *dbpass = "123456"; acl::db_pool* dbpool = new acl::mysql_pool(dbaddr, dbname, dbuser, dbpass); // 创建 mysql 连接池 acl::db_handle* dbhandle = dbpool->peek_open(); // 从连接池中获取一个数据库连接 if (dbhandle == nullptr) { printf("peek db connection error\r\n"); delete dbhandle; return 0; } tbl_create(*dbhandle); tbl_insert(*dbhandle, 6); tbl_select(*dbhandle, 6); tbl_delete(*dbhandle, 6); dbpool->put(dbhandle); // 归还数据库连接给连接池 delete dbpool; // 删除连接池对象 return 0; } 源码分析 初始化lib_acl/sample/acl #include #include struct ACL_VSTREAM { }; static char *version = "3.5.1-5 20200627"; typedef struct ACL_VSTREAM ACL_VSTREAM; static pthread_t acl_var_main_tid = (pthread_t) -1; const char *acl_version(); unsigned long acl_main_thread_self(); int main() { printf("current acl version: %s\r\n", acl_version()); printf("ACL_VSTREAM's size: %d\r\n", (int) sizeof(ACL_VSTREAM)); printf("main tid: %lu\r\n", acl_main_thread_self()); printf("tid: %lu\r\n", (unsigned long) pthread_self()); return 0; } unsigned long acl_main_thread_self(){ return ((unsigned long) acl_var_main_tid); } const char *acl_version() { return version; } socket 服务端1 #include "acl_cpp/lib_acl.hpp" #include int main(int argc, char* argv[]) { acl::server_socket server; const char * addr = "127.0.0.1:9001"; acl::acl_cpp_init(); if (!server.open(addr)) { printf("open %s error %s\r\n", addr, acl::last_serror()); return 1; }else printf("open %s ok\r\n", addr); while (true) { acl::socket_stream* client = server.accept(); //阻塞,直到有连接到了(接收客户端连接并创建客户端连接流) if (client == NULL) { printf("accept failed: %s\r\n", acl::last_serror()); break; } acl::string buf; if (!client->gets(buf)) { printf("gets error, status: %s\r\n", acl::last_serror()); delete client; continue; } printf("gets: %s, length: %zu\r\n", buf.c_str(), buf.length()); delete client; printf("close client ok\r\n"); } return (0); } 客户端1 #include "acl_cpp/lib_acl.hpp" #include "lib_acl.h" #include #include int main(int argc, char* argv[]) { acl::socket_stream client; const char * addr = "127.0.0.1:9001"; acl::acl_cpp_init(); // 连接远程服务器并打开网络连接流 if (!client.open(addr, 0, 0)){ printf("open %s error %s\n", addr, acl::last_serror()); return 1; }else{ printf("open %s ok\r\n", addr); } const char * req = "echo1\r\n" "echo2\r\0" "echo3\r\n" "read_delay1\r\n" "read_delay2\r\n" "read_delay3\r\n" "read_delay4\r\n" "read_delay5\r\n" "quit\r\n"; // 探测连接是否正常 if (client.alive()) printf("check: ok, status: %s\r\n", acl::last_serror()); else printf("check: disconnected, status: %s\r\n", acl::last_serror()); if (client.write(req, 100) == -1) { printf("1, write error\r\n"); return -1; } return (0); }