WebServer04
我们先学习一下epoll:
select/poll/epoll - 知乎
接下来调整一下学习策略。这么多行代码,一行行去读、搞懂每个api如何调用也不现实,而且只读代码对能力的提升还是不如动手实操。接下来打算自己实现一个简单的回声服务器,接着融入多线程、I/O多路复用,理解了之后再回头看WebServer
许久没有打开项目了,中途配置cs144的lab环境,出现了一点问题:VSCode竟然不认识如cassert等库,连std都不认识了!写了一个简单的测试代码,虽然VSCode报错,但是可以正常编译并运行。看起来是VSCode的问题。
先禁用官方的C/C++扩展,使用clangd. 由于项目有makefile,所以直接用bear工具(生成编译数据库)
sudo apt update
sudo apt install bear
cd ~/项目
#清理旧编译文件
make clean
bear -- make #用bear生成compile_commands.json文件
Ctrl+Shift+P,找到Clangd:Restart Language Server,点击
但还是不行。我们检查一下compile_commands.json:
[{"arguments": ["/usr/bin/g++","-c","-g","-o","server","main.cpp"],"directory": "/home/kevin/cpp-projects/TinyWebServer","file": "/home/kevin/cpp-projects/TinyWebServer/main.cpp","output": "/home/kevin/cpp-projects/TinyWebServer/server"},{"arguments": ["/usr/bin/g++","-c","-g","-o","server","timer/lst_timer.cpp"],"directory": "/home/kevin/cpp-projects/TinyWebServer","file": "/home/kevin/cpp-projects/TinyWebServer/timer/lst_timer.cpp","output": "/home/kevin/cpp-projects/TinyWebServer/server"},{"arguments": ["/usr/bin/g++","-c","-g","-o","server","http/http_conn.cpp"],"directory": "/home/kevin/cpp-projects/TinyWebServer","file": "/home/kevin/cpp-projects/TinyWebServer/http/http_conn.cpp","output": "/home/kevin/cpp-projects/TinyWebServer/server"},{"arguments": ["/usr/bin/g++","-c","-g","-o","server","log/log.cpp"],"directory": "/home/kevin/cpp-projects/TinyWebServer","file": "/home/kevin/cpp-projects/TinyWebServer/log/log.cpp","output": "/home/kevin/cpp-projects/TinyWebServer/server"},{"arguments": ["/usr/bin/g++","-c","-g","-o","server","CGImysql/sql_connection_pool.cpp"],"directory": "/home/kevin/cpp-projects/TinyWebServer","file": "/home/kevin/cpp-projects/TinyWebServer/CGImysql/sql_connection_pool.cpp","output": "/home/kevin/cpp-projects/TinyWebServer/server"},{"arguments": ["/usr/bin/g++","-c","-g","-o","server","webserver.cpp"],"directory": "/home/kevin/cpp-projects/TinyWebServer","file": "/home/kevin/cpp-projects/TinyWebServer/webserver.cpp","output": "/home/kevin/cpp-projects/TinyWebServer/server"},{"arguments": ["/usr/bin/g++","-c","-g","-o","server","config.cpp"],"directory": "/home/kevin/cpp-projects/TinyWebServer","file": "/home/kevin/cpp-projects/TinyWebServer/config.cpp","output": "/home/kevin/cpp-projects/TinyWebServer/server"}
]
可以看到在文件中我们没有指定C++标准,也没有包含系统标准库头文件路径
更改如下:(JSON是不能有注释的,这里只是为了说明)
[{"arguments": ["/usr/bin/g++","-c","-g","-std=c++11", //添加C++11标准"-I/usr/include/c++/11", //系统标准库路径"-I/usr/include/x86_64-linux-gnu/c++/11", //架构相关标准库路径"-I/usr/include", //系统默认头文件路径"-o","server","main.cpp"],"directory": "/home/kevin/cpp-projects/TinyWebServer","file": "/home/kevin/cpp-projects/TinyWebServer/main.cpp","output": "/home/kevin/cpp-projects/TinyWebServer/server"},{"arguments": ["/usr/bin/g++","-c","-g","-std=c++11","-I/usr/include/c++/11","-I/usr/include/x86_64-linux-gnu/c++/11","-I/usr/include","-o","server","timer/lst_timer.cpp"],"directory": "/home/kevin/cpp-projects/TinyWebServer","file": "/home/kevin/cpp-projects/TinyWebServer/timer/lst_timer.cpp","output": "/home/kevin/cpp-projects/TinyWebServer/server"},{"arguments": ["/usr/bin/g++","-c","-g","-std=c++11","-I/usr/include/c++/11","-I/usr/include/x86_64-linux-gnu/c++/11","-I/usr/include","-o","server","http/http_conn.cpp"],"directory": "/home/kevin/cpp-projects/TinyWebServer","file": "/home/kevin/cpp-projects/TinyWebServer/http/http_conn.cpp","output": "/home/kevin/cpp-projects/TinyWebServer/server"},{"arguments": ["/usr/bin/g++","-c","-g","-std=c++11","-I/usr/include/c++/11","-I/usr/include/x86_64-linux-gnu/c++/11","-I/usr/include","-o","server","log/log.cpp"],"directory": "/home/kevin/cpp-projects/TinyWebServer","file": "/home/kevin/cpp-projects/TinyWebServer/log/log.cpp","output": "/home/kevin/cpp-projects/TinyWebServer/server"},{"arguments": ["/usr/bin/g++","-c","-g","-std=c++11","-I/usr/include/c++/11","-I/usr/include/x86_64-linux-gnu/c++/11","-I/usr/include","-o","server","CGImysql/sql_connection_pool.cpp"],"directory": "/home/kevin/cpp-projects/TinyWebServer","file": "/home/kevin/cpp-projects/TinyWebServer/CGImysql/sql_connection_pool.cpp","output": "/home/kevin/cpp-projects/TinyWebServer/server"},{"arguments": ["/usr/bin/g++","-c","-g","-std=c++11","-I/usr/include/c++/11","-I/usr/include/x86_64-linux-gnu/c++/11","-I/usr/include","-o","server","webserver.cpp"],"directory": "/home/kevin/cpp-projects/TinyWebServer","file": "/home/kevin/cpp-projects/TinyWebServer/webserver.cpp","output": "/home/kevin/cpp-projects/TinyWebServer/server"},{"arguments": ["/usr/bin/g++","-c","-g","-std=c++11","-I/usr/include/c++/11","-I/usr/include/x86_64-linux-gnu/c++/11","-I/usr/include","-o","server","config.cpp"],"directory": "/home/kevin/cpp-projects/TinyWebServer","file": "/home/kevin/cpp-projects/TinyWebServer/config.cpp","output": "/home/kevin/cpp-projects/TinyWebServer/server"}
]
-std=c++11告诉clangd用c++11标准解析代码
-I明确告诉clangd系统标准库头文件的位置
在其它项目中是否需要构建这个compile_commands.json文件?需要,因为clangd的功能完全依赖它提供的编译上下文。如果项目用的是MakeFile,那么通常使用bear工具,就和前文一样:
make clean
bear -- make
对于更大型的项目,一般都是使用CMake
所以CMake和MakeFile究竟是什么?它们都是构建工具,核心作用是帮我们自动化管理C/C++项目的编译流程。例如一个项目有100个文件,你每次修改都要编译100个文件太麻烦了。而且不同的人可能使用不同的编译选项。构建工具解决的就是这几个问题:自动化、增量编译(只编译修改的文件)、统一配置。
makefile:
#1.定义变量:编译选项、目标文件名、源文件
CXX = g++ #用g++编译
CXXFLAGS = -std=c++11 -Wall #编译选项:C++11 标准、开启警告
TARGET = myapp #最终生成的可执行文件名
SRC = main.cpp tool.cpp #所有源文件
#2.生成目标文件
$(TARGET): $(SRC) #myapp依赖于 main.cpp 和 tool.cpp$(CXX) $(CXXFLAGS) $(SRC) -o $(TARGET) #实际执行的编译命令(注意开头必须是Tab缩进)
# 3.清理编译产物
clean:rm -f $(TARGET) *.o #执行make clean时,删除可执行文件和中间文件
make #自动执行编译命令,生成myapp
make clean #清理编译产物(重新编译前用)
可见Makefile是比较简单的,实际上就是写命令+定义依赖。如果我们只修改了main.cpp,那么它只会编译main.cpp,再和tool.o链接。但是它的缺点也比较明显:需要手写规则(新增一个.cpp文件就要添加到SRC变量中),此外Windows系统下需要MinGW等工具支持。
而CMake则是更加高级的构建工具。它不直接编译代码,而是帮你自动生成Makefile(或者VisualStudio工程、XCode工程等)。CMake允许我们用跨平台的语法写一个CMakeLists.txt(必须要是这个名字),然后自动生成对应平台的构建文件。它的核心优势在于:跨平台(同一个CMakeLists.txt,在Linux生成Makefile,在Mac OS生成XCode工程,在Windows生成VisualStudio工程)、简化配置(自动找源文件、自动处理依赖)、支持复杂项目(处理多目录、动态/静态库、第三方依赖)
补充说一下动态库和静态库:
比喻:你发现你的一道错题涉及某个定理。静态库就是你把这个定理抄在题目旁边,动态库就是你在旁边批注“见课本第80页定理”。前者:你写的会多一点,但你看试卷复习的时候就不需要翻阅课本了。后者:你写的少一点,但看试卷复习的时候就需要课本了。
先回忆 C/C++ 程序的编译流程:源代码(.cpp)-> 编译(生成 .o 目标文件 -> 链接(合并 .o 文件+库文件)-> 可执行文件
静态库和动态库的核心区别在链接阶段:
静态库后缀名:Linux 下是 .a(archive),Windows 下是 .lib
链接过程:编译器会把静态库中你用到的代码段、函数直接复制到你的 .o 文件中,最终合并成一个独立的可执行文件
运行时:可执行文件不依赖任何外部库,直接运行,因为需要的代码都在自己体内
例:你用静态库 liblog.a(日志工具包),链接后生成 webserver 可执行文件 ——webserver 里已经包含了日志功能的代码,即使删除 liblog.a,webserver 依然能正常打印日志。
动态库后缀名:Linux 下是 .so(shared object),Windows 下是 .dll(dynamic link library)
链接过程:编译器不会复制库的代码,只会在你的可执行文件中记录 “我需要用到 xxx 动态库的 xxx 函数”,相当于留了一个引用地址
运行时:可执行文件启动后,系统会先查找对应的动态库(比如 /usr/lib/liblog.so),加载到内存中,然后通过引用地址调用库中的函数。如果找不到动态库,程序会直接报错退出,比如 Linux 下的 error while loading shared libraries: liblog.so: cannot open shared object file)
例:你用动态库 liblog.so,链接后生成 webserver——webserver 体积很小,但运行时必须依赖 liblog.so 存在于系统中;如果 liblog.so 被更新,比如修复了一个 bug,你不用重新编译 webserver,下次运行时会自动使用新的库
