文章目录
笔者在接触过go语言的gin框架后,就被如此简单的使用方式给震惊了,想到c++的网络编程,过于繁杂,就想找找有没有类 gin 的网络库 ,于是便找到了c++11异步restful网络框架wfrest。
#include "wfrest/HttpServer.h"
using namespace wfrest;
int main()
{
HttpServer svr;
// 发送数据
svr.GET("/hello", [](const HttpReq *req, HttpResp *resp)
{
resp->String("world\n");
});
// 获取http 请求的数据
svr.POST("/post", [](const HttpReq *req, HttpResp *resp)
{
std::string& body = req->body();
fprintf(stderr, "post data : %s\n", body.c_str());
});
// 文件读写都是异步
// 发送文件
svr.GET("/file", [](const HttpReq *req, HttpResp *resp)
{
resp->File("todo.txt");
});
// 上传文件
svr.POST("/upload", [](const HttpReq *req, HttpResp *resp)
{
std::string& body = req->body();
resp->Save("test.txt", std::move(body));
});
// 发送json数据
svr.GET("/json", [](const HttpReq *req, HttpResp *resp)
{
std::string json = R"(
{
"numbers": [1, 2, 3]
}
)";
resp->Json(json);
});
if (svr.start(8888) == 0)
{
getchar()
svr.stop();
} else
{
fprintf(stderr, "Cannot start server");
exit(1);
}
return 0;
}
这里是作者对此框架的介绍
有哪些值得学习的国内 c++ 开源项目? - Chanchan的回答 - 知乎
github仓库地址
可是对于小白来说,C++ 库不像go语言那样直接import,我也是搜罗了好久才勉强弄懂一点
下面是些前置知识
什么是库
库是写好的现有的,成熟的,可以复用的代码。现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常。
本质上来说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。库有两种:静态库(.a、.lib)和动态库(.so、.dll)。
回顾一下,将一个程序编译成可执行程序的步骤:
GCC编译器基本参数
- 预处理-Pre-Processing //.i文件
# -E 选项指示编译器仅对输入文件进行预处理
g++ -E test.cpp -o test.i //.i文件
- 编译-Compiling // .s文件
# -S 编译选项告诉 g++ 在为 C++ 代码产生了汇编语言文件后停止编译
# g++ 产生的汇编语言文件的缺省扩展名是 .s
g++ -S test.i -o test.s
- 汇编-Assembling // .o文件
# -c 选项告诉 g++ 仅把源代码编译为机器语言的目标代码
# 缺省时 g++ 建立的目标代码文件有一个 .o 的扩展名。
g++ -c test.s -o test.o
- 链接-Linking // bin文件
# -o 编译选项来为将产生的可执行文件用指定的文件名
g++ test.o -o test
其中库的生成就是拿汇编成的 .o目标文件进行有别于生成可执行文件的链接(通过 ar 命令)。
ar命令
-
用途说明
创建静态库 .a文件。
-
格式:
ar rcs libxxx.a xx1.o xx2.o //记住这个就行了,静态库创建最常用
-
常用参数
-
参数r:在库中插入模块(替换)。当插入的模块名已经在库中存在,则替换同名的模块。如果若干模块中有一个模块在库中不存在,ar显示一个错误消息,并不替换其他同名模块。默认的情况下,新的成员增加在库的结尾处,可以使用其他任选项来改变增加的位置.
-
参数c:创建一个库。不管库是否存在,都将创建。
-
参数s:创建目标文件索引,这在创建较大的库时能加快时间。
-
格式:
ar t libxxx.a //显示库文件中有哪些目标文件,只显示名称。
ar tv libxxx.a //显示库文件中有哪些目标文件,显示文件名、时间、大小等详细信息。
nm -s libxxx.a //显示库文件中的索引表。
ranlib libxxx.a //为库文件创建索引表。
其中创建静态库一般只有 rcs 参数
-
为什么需要库(library)
- 有时候我们会有多个可执行文件,他们之间用到的某些功能是相同的,我们想把这些共用的功能做成一个库,方便大家一起共享。
- 库中的函数可以被可执行文件调用,也可以被其他库文件调用。
- 库文件又分为静态库文件和动态库文件。
- 其中静态库相当于直接把代码插入到生成的可执行文件中,会导致体积变大,但是只需要一个文件即可运行。
- 而动态库则只在生成的可执行文件中调用库函数的位置填上相应的符号名其实也是个地址(类似于汇编中的call 符号名),当可执行文件被加载时会读取指定目录中的 .dll (Windows动态库文件后缀名)文件或 .so (linux动态库文件后缀名),加载到内存中空闲的位置,并且替换相应的“符号名”指向的地址为加载后的地址,这个过程称为重定向。这样以后函数被调用就会跳转到动态加载的地址去。
- 库文件搜索路径:
- Windows:可执行文件同目录,其次是环境变量%PATH%
- Linux:ELF格式可执行文件的RPATH,其次是/usr/lib等
静态库
- 命名规则
- Linux
- libxxx.a
- lib: 前缀
- .a: 后缀
- xxx: 名字, 由库的制作者定的
- libxxx.a
- windows
- libxx.lib
- Linux
静态库的创建
wakk@wakk-virtual-machine~/文档$ tree .
.
├── add.c
├── div.c
├── include
│ └── head.h
├── main.c
├── mult.c
└── sub.c
1 directory, 6 files
# 1. 将源文件生成 .o 文件
$ gcc -c sub.c add.c mult.c div.c -I ./include/
wakk@wakk-virtual-machine~/文档$ tree .
.
├── add.c
├── add.o
├── div.c
├── div.o
├── include
│ └── head.h
├── main.c
├── mult.c
├── mult.o
├── sub.c
└── sub.o
1 directory, 10 files
# 2. 将.o 文件打包 生成 库文件
# 语法: ar rcs 生成的库的名字 *.o文件
$ ar rcs libcalc.a *.o
wakk@wakk-virtual-machine~/文档$ ls
add.c add.o div.c div.o include `libcalc.a` main.c mult.c mult.o sub.c sub.o
# 发布
将 `libcalc.a` `head.h` 拷贝给使用者即可
静态库使用
.
├── include
│ └── head.h `静态库对应的头文件`
├── libcalc.a `静态库`
└── main.c `测试程序`
1 directory, 3 files
# 编译测试程序
$ gcc main.c -o app -I include/
/home/文档: In function `main':
main.c:(.text+0x38): undefined reference to `add'
main.c:(.text+0x58): undefined reference to `subtract'
main.c:(.text+0x78): undefined reference to `multiply'
main.c:(.text+0x98): undefined reference to `divide'
collect2: error: ld returned 1 exit status
# 错误原因: 编译的时候找不到对应的库,只有声明(在头文件里), 库中有函数定义(函数实现)
# 指定自己编译的库需要使用的参数:
-L: 指定库的路径
-l: 指定库的名字(掐头(lib) 去尾(.a))
$ gcc main.c -o app -I include/ -L ./ -l calc
动态库/共享库
- 命名规则
- linux:
- libxxx.so
- 前缀: lib
- 后缀: .so
- 库的名字: xxx, 制作库的人指定的
- libxxx.so
- windows:
- vs版
- libxxx.lib
- libxxx.dll
- 非vs版
- libxx.dll
- vs版
- linux:
动态库的创建
动态库的创建要+上-fPIC -shared参数,
-shared
选项用于生成动态链接库;-fpic
(还可写成-fPIC
)选项的功能是,令GCC
编译器生成动态链接库(多个目标文件的压缩包)时,表示各目标文件中函数、类等功能模块的地址使用相对地址,而非绝对地址。这样,无论将来链接库被加载到内存的什么位置,都可以正常使用。
具体为什么要加参考下面帖子
.
├── add.c
├── div.c
├── include
│ └── head.h
├── main.c
├── mult.c
└── sub.c
1 directory, 6 files
# 制作步骤:
# 1. 将源文件生成 .o 文件 -fpic/fPIC
$ gcc -c add.c div.c mult.c sub.c -fpic -I ./include/
# 2. 将 .o 文件 打包(gcc -shared)成 库文件 .so
$ gcc -shared *.o -o libcalc.so
wakk@wakk-virtual-machine~/文档 $ tree .
.
├── add.c
├── add.o
├── div.c
├── div.o
├── include
│ └── head.h
├── `libcalc.so` # 生成的动态库
├── main.c
├── mult.c
├── mult.o
├── sub.c
└── sub.o
# 发布
将 `libcalc.so` 和 `head.h` 发布给使用者
动态库使用
.
├── include
│ └── head.h
├── libcalc.so
└── main.c
$ gcc -I include/ main.c -L./ -lcalc -o app
$ ./app
./app: error while loading shared libraries: libcalc.so: cannot open shared object file: No such file or directory
# 使用命令检测可执行程序能不能加载到对应的动态库?
ldd 可执行程序名
$ ldd app
linux-vdso.so.1 => (0x00007ffde8d77000)
libhello.so => `not found`
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f289bd43000)
/lib64/ld-linux-x86-64.so.2 (0x00007f289c10d000)
原因及解决
库的工作原理
-
静态库
-
gcc main.c -o app -I include/ -L ./ -l calc
-
在最终的链接阶段, 链接器会将静态库 calc 打包到可执行程序 app中
-
在可执行程序执行的时候, app中所有的代码会被加载到内存
-
-
动态库:
-
gcc -I include/ main.c -L./ -lcalc -o app
-
在最终的链接阶段, 链接器不会将动态库 calc 打包到可执行程序 app中,
在上边的命令中只是检测动态库是否存在 -
在可执行程序执行的时候, app中所有的代码会被加载到内存, 不包括动态库代码
-
当库中的函数被调用的时候, 动态库被加载到内存(加载的时候需要知道动态库在什么位置)
-
库的加载是由动态链接器加载的
-
所以上面出现的问题是动态链接器没有找到对应的动态库
- 动态链接器是如何加载动态库的?
它先后搜索可执行程序文件的 DT_RPATH段
—> 环境变量LD_LIBRARY_PATH
—> /etc/ld.so.cache
文件列表 —> /lib/
, /usr/lib
目录找到库文件后将其载入内存。
解决
#1. 将动态库的路径放到环境变量 LD_LIBRARY_PATH 中
在终端执行下边的命令: (这是临时设置, 当前终端被关闭或切换到其他终端该设置就无效了)
export LD_LIBRARY_PATH=/home/文档:$LD_LIBRARY_PATH
永久设置: 将上边的命令写入到配置文件中
- 用户级别:
`~/.bashrc`
- 系统级别:
`/etc/profile`
配置完成之后, 需要让配置文件重新被加载
. source ~/.bashrc
. source /etc/profile
#2. 将动态库的路径更新配置文件 /etc/ld.so.cache 中
- 将动态库的路径添加到配置文件 /etc/ld.so.conf
- 将/etc/ld.so.conf中的信息更新/etc/ld.so.cache
- sudo ldconf
#3. 将动态库添加到对应的系统目录中
- /lib
- /usr/lib
这里采用export LD_LIBRARY_PATH=/home/文档:$LD_LIBRARY_PATH
暂时添加
然后便成功了
wfrest
wfrest 库的获取
就按照 GitHub 上的步骤一步一步来就好了,因为wfrest 是基于 sougou的workflow的,所以–recursive 的时候会去下载workflow ,但是者在获取的时候出现点问题,就是会在获取workflow时有点慢,所以我选择不加–recursive,之后再
cd wfrest
git clone https://github.com/sogou/workflow.git
之后接着按步骤来
一般来说在 sudo make install 后便可在
/usr/local/lib
里发现对应的静态库和动态库文件了
采用动态库
这里的demo.cpp就是github主页那个Quick start代码
.
└── demo.cpp
0 directories, 1 file
此时如果直接执行
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib //这里记得改成自己库文件所在目录
g++ demo.cpp -lwfrest -o app
会报
这是因为workflow库还没安装
到wfrest目录下再执行
cd workflow
make
sudo make install
此时 /usr/local/lib
中可以看见了
此时便是开始执行了
成功了
凡是报下面错的
./app: error while loading shared libraries: libworkflow.so: cannot open shared object file: No such file or directory
先检查一下路径
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib
加了没
采用静态库
不会,希望有大佬教教我
.
├── demo.cpp
├── libwfrest.a
└── libworkflow.a
0 directories, 3 files
g++ -static demo.cpp -L ./ -lwfrest -lworkflow -lpthread -I/usr/local/include -o app //最后报错的代码
动态库能用就行(手动狗头)