Jack Huang's Blog


  • 首页

  • 标签

  • 归档

  • 搜索

CMake使用入门教程

发表于 2019-11-16 | 更新于 2022-10-26

近几年美国对中国的科技封杀十分严重,说不定哪天Windows操作系统就不让我们用了。因此,在构建C/C++项目时应考虑跨平台,哪天不让用Windows了,可以请容易的将项目移植到Linux上。不同系统平台有不同的C/C++编译器,不同编译器有不同的构建规则,针对每个平台的不同编译器编写构建规则十分复杂,幸好有CMake可简化构建规则的编写,实现一次编写,不同平台适用。下面简单介绍CMake的使用。

CMake简介

代码变成可执行文件,叫做编译(compile);先编译这个,还是先编译那个(即编译的安排),叫做构建(build)。

Make是最常用的构建工具,诞生于1977年,主要用于C语言的项目。但是实际上 ,任何只要某个文件有变化,就要重新构建的项目,都可以用Make构建。

Make工具有很多,例如 GNU Make ,QT 的 qmake ,微软的 MS nmake,BSD Make(pmake),Makepp,等等。这些 Make 工具遵循着不同的规范和标准,所执行的 Makefile 格式也千差万别。这样就带来了一个严峻的问题:如果软件想跨平台,必须要保证能够在不同平台编译。而如果使用上面的 Make 工具,就得为每一种标准写一次 Makefile ,这将是一件让人抓狂的工作。

CMake就是针对上面问题所设计的工具:它首先允许开发者编写一种平台无关的 CMakeList.txt 文件来定制整个编译流程,然后再根据目标用户的平台进一步生成所需的本地化 Makefile 和工程文件,如 Unix 的 Makefile 或 Windows 的 Visual Studio 工程。从而做到“Write once, run everywhere”。显然,CMake 是一个比上述几种 make 更高级的编译配置工具。一些使用 CMake 作为项目架构系统的知名开源项目有 VTK、ITK、KDE、OpenCV、OSG 等。

CMake教程入门

给工程起个名字

语法:

1
project(<PROJECT-NAME> [LANGUAGES] [<language-name>...])

该指令定义工程名称。例如:

1
project(UtilTool)

添加头文件目录INCLUDE_DIRECTORIES

语法:

1
include_directories([AFTER|BEFORE] [SYSTEM] dir1 [dir2 ...])

它相当于g++选项中的-I参数的作用,也相当于环境变量中增加路径到CPLUS_INCLUDE_PATH变量的作用。例如:

1
include_directories(${CMAKE_CURRENT_LIST_DIR}/include)

当头文件分散在不同层次和深度的目录中时,逐个使用include_directories命令添加包含目录十分麻烦,可使用如下方法递归加载各个目录:

1
2
3
4
5
6
7
8
9
10
11
12
13
MARO(HEADER_DIRECTORIES return_list)
FILE(GLOB_RECURSE new_list src/*.h)
SET(dir_list "")
FOREACH(file_path ${new_list})
GET_FILENAME_COMPONENT(dir_path ${file_path} PATH)
SET(dir_list ${dir_list} ${dir_path})
ENDFOREACH()
LIST(REMOVE_DUPLICATES dir_list)
SET(${return_list} ${dir_list})
ENDMACRO()

HEADER_DIRECTORIES(header_dir_list)
include_directories(${CMAKE_CURRENT_LIST_DIR}/include ${header_dir_list})

添加需要链接的库文件目录LINK_DIRECTORIES

语法:

1
link_directories(directory1 directory2 ...)

它相当于g++命令的-L选项的作用,也相当于环境变量中增加LD_LIBRARY_PATH的路径的作用。

1
link_directories(${CMAKE_CURRENT_LIST_DIR}/lib)

向当前工程添加存放源文件的子目录ADD_SUBDIRECTORY

ADD_SUBDIRECTORY用于向当前工程添加存放源文件的子目录,并可以指定中间二进制和目标二进制存放的位置。语法如下:

1
2
ADD_SUBDIRECTORY(source_dir [binary_dir]
[EXCLUDE_FROM_ALL])

上面的例子定义了将 src 子目录加入工程,并指定了编译输出路径为 bin 目录。如果不指定 bin 目录,那么编译的结果都将存放在 build/src 目录。

设置要链接的库文件的名称TARGET_LINK_LIBRARIES

语法:

1
2
target_link_libraries(<target> [item1 [item2 [...]]]
[[debug|optimized|general] <item>] ...)

该指令的作用为将目标文件与库文件进行链接。例如:

1
TARGET_LINK_LIBRARIES(utiltool-example utiltool)

为工程生成目标文件

语法:

1
2
3
add_executable(<name> [WIN32] [MACOSX_BUNDLE]
[EXCLUDE_FROM_ALL]
source1 [source2 ...])

例如:

1
ADD_EXECUTABLE(utiltool-example examples/ConverterTest.cpp)

为工程生成共享库

语法:

1
2
3
add_library(<name> [STATIC | SHARED | MODULE]
[EXCLUDE_FROM_ALL]
[source1] [source2] [...])

该指令的主要作用就是将指定的源文件生成链接文件,然后添加到工程中去。例如:

1
ADD_LIBRARY(utiltool SHARED ${UTILTOOL_SOURCES})

为工程制作简单的安装脚本

语法:

1
2
3
4
5
6
7
8
9
install(TARGETS targets... [EXPORT <export-name>]
[[ARCHIVE|LIBRARY|RUNTIME|FRAMEWORK|BUNDLE|
PRIVATE_HEADER|PUBLIC_HEADER|RESOURCE]
[DESTINATION <dir>]
[PERMISSIONS permissions...]
[CONFIGURATIONS [Debug|Release|...]]
[COMPONENT <component>]
[OPTIONAL] [NAMELINK_ONLY|NAMELINK_SKIP]
] [...])

该命令为一个工程生成安装规则。TARGETS格式的install命令规定了安装工程中的目标(targets)的规则。有5中可以被安装的目标文件:ARCHIVE,LIBRARY,RUNTIME,FRAMEWORK,和BUNDLE。静态链接的库文件总是被当做ARCHIVE目标。模块库总是被当做LIBRARY目标。例如:

1
2
3
4
5
install(TARGETS utiltool 
RUNTIME DESTINATION bin
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib)
install(FILES ${UTILTOOL_HEADERS} DESTINATION include)

为工程设置变量

语法:

1
2
set(<variable> <value>
[[CACHE <type> <docstring> [FORCE]] | PARENT_SCOPE])

该指令用于给一般变量,缓存变量,环境变量赋值。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
# set up versioning.
set(BUILD_MAJOR "1")
set(BUILD_MINOR "0")
set(BUILD_VERSION "0")
set(BUILD_VERSION ${BUILD_MAJOR}.${BUILD_MINOR}.${BUILD_VERSION})

SET(UTILTOOL_HEADERS
src/GeodeticConverter.h
)

SET(UTILTOOL_SOURCES
src/GeodeticConverter.cpp
)

为工程设置预定义宏

语法:

1
add_definitions(-DFOO -DBAR ...)

该指令添加编译参数。例如:

1
2
# 添加WIN32宏定义
add_definitions(-DWIN32)

OPTION变量

语法:

1
2
option(<option_variable> "help string describing option"
[initial value])

该指令提供一个用户可以任选的选项,可在之后由用户通过CMake的GUI或者命令行进行更改。例如:

1
OPTION(UTILTOOL_EXAMPLES "Build the examples" ON)

修改默认的CMAKE_MODULE_PATH目录

CMAKE_MODULE_PATH是供find_package搜索第三方库用的。cmake的默认Modules目录在安装目录中:cmake-3.11.3-win64-x64\share\cmake-3.11\Modules。
如果要追加Modules目录,有3种方式:

1
2
3
4
5
SET(CMAKE_MODULE_PATH "${OpenSceneGraph_SOURCE_DIR}/CMakeModules;${CMAKE_MODULE_PATH}")

LIST(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_PREFIX}")

set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake)

find_package用法

通常情况下,包含第三方库需要写以下内容:

1
2
3
4
include_directories("${project_root_path}/include/")  
link_directories(./lib)
add_executable(myapp myapp.cpp)
target_link_libraries(myapp mylib)

如果引用的很多个第三方库,那么类似上面的内容会写很多,且如果自己的多个项目都引用了某个第三方库,那么我每个项目的CmakeList.txt都得写一遍,重复劳动很多。那么有没办法为每个第三方库只定义一次它的头文件和库文件信息,然后在自己的工程中只指定名称即可?(类似编译Java的Maven仓库)答案是当然可以,find_package帮你解决。

find_package定义在自己工程的CmakeList.txt中:

1
find_package( XXX CONFIG REQUIRED )

然后cmake就会在默认的Modules(即CMAKE_MODULE_PATH指定的目录)目录中搜索这个XXX第三方库。

搜索有两种模式:FindXXX.cmake和XXXConfig.cmake。前者叫做Module模式,后者叫做Config模式。

优雅的软件项目结构模板

请参考CMake—优雅地构建软件项目实践(1)。

完整简单示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# CMakeList.txt: UtilTool 的 CMake 项目,在此处包括源代码并定义
# 项目特定的逻辑。
#
cmake_minimum_required (VERSION 3.8)

project(UtilTool)

# set up versioning.
set(BUILD_MAJOR "1")
set(BUILD_MINOR "0")
set(BUILD_VERSION "0")
set(BUILD_VERSION ${BUILD_MAJOR}.${BUILD_MINOR}.${BUILD_VERSION})

include_directories(${CMAKE_CURRENT_LIST_DIR}/include)

link_directories(${CMAKE_CURRENT_LIST_DIR}/lib)

link_libraries(gsl)

# OS and compiler checks.
if(UNIX)
# linux / normal unix
add_definitions(-D_LINUX)
if(CYGWIN)
# Special Cygwin stuff here
elseif(APPLE)
# Special Apple stuff here
remove_definitions(-D_LINUX)
add_definitions(-D_DARWIN)
endif()
elseif(WIN32)
add_definitions(-DWIN32)
add_definitions(-D UtilTool_EXPORTS)
if(MINGW)
# Special MINGW stuff here
elseif(MSVC)
# Special MSVC stuff here
add_definitions(-D_WINSOCK_DEPRECATED_NO_WARNINGS)
else()
# No idea what it is, but there's a good chance it's too weird.
MESSAGE( FATAL_ERROR "Using unknown WIN32 compiler... NOT. Please add to build system." )
endif()
endif()

SET(UTILTOOL_HEADERS
src/GeodeticConverter.h
)

SET(UTILTOOL_SOURCES
src/GeodeticConverter.cpp
)

# mark headers as headers...
SET_SOURCE_FILES_PROPERTIES( ${UTILTOOL_HEADERS} PROPERTIES HEADER_FILE_ONLY TRUE )
# append to sources so that dependency checks work on headers
LIST(APPEND UTILTOOL_SOURCES ${UTILTOOL_HEADERS})

OPTION(UTILTOOL_SHARED "Build utiltool lib as shared." ON)
OPTION(UTILTOOL_DEP_ONLY "Build for use inside other CMake projects as dependency." OFF)

# 将源代码添加到此项目的共享库中。
if(UTILTOOL_SHARED)
ADD_LIBRARY(utiltool SHARED ${UTILTOOL_SOURCES})
endif()

# install into configured prefix
if(NOT UTILTOOL_DEP_ONLY)
install(TARGETS utiltool
RUNTIME DESTINATION bin
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib)
install(FILES ${UTILTOOL_HEADERS} DESTINATION include)
else()

endif()

# TODO: 如有需要,请添加测试并安装目标。
OPTION(UTILTOOL_EXAMPLES "Build the examples" ON)

if(UTILTOOL_EXAMPLES)
ADD_EXECUTABLE(utiltool-example examples/ConverterTest.cpp)
TARGET_LINK_LIBRARIES(utiltool-example utiltool)
endif()

参考链接

  1. Linux平台编译安装测试JSBSim,by jackhuang.
  2. 干货:构建C/C++良好的工程结构,by Froser.
  3. 基于CMake构建系统的C++工程框架,by zhongxiao_yao.
  4. CMake 手册详解(十九)install指令,by SirDigit.
  5. CMake中变量总结,by 拾荒志.
  6. cmake使用教程(十)-关于file,by saka.
  7. Recursive CMake search for header and source files,by stackoverflow.
  8. CMake shared library in subdirectory,by stackoverflow.
  9. CMake 语言和语法,by leosocy.
  10. 【cmake】——include_directories 和target_include_directories的区别,by 大川搬砖.
  11. CMake—优雅地构建软件项目实践(1),by Ethan.
  12. CMake测试,by dxa572862121.
  13. CMake–Set用法,by narjaja.
  14. CMake之message()函数的使用和打印变量值,by hp_cpp.
  15. A minimal CMake project template,by Matt Morse.
  16. [Build]cmake常用配置项,by 玄冬Wong.
  17. 简单介绍Cmake生成VS工程中的ALL_BUILD、INSTALL、ZERO_CHECK作用!!,by 醉逍遥_祥.

C++常用库简介

发表于 2019-11-15 | 更新于 2022-12-07

为熟练使用C++创建项目,应掌握常用的C++库。现总结常用的C++库如下:

数学库

gsl(GNU Scientific Library):GUN科学运算库

Blitz++:高效率的数值计算函数库

它的设计目的是希望建立一套既具像C++ 一样方便,同时又比Fortran速度更快的数值计算环境。通常,用C++所写出的数值程序, 比 Fortran慢20%左右,因此Blitz++正是要改掉这个缺点。方法是利用C++的template 技术,程序执行甚至可以比Fortran更快。

MTL(Matrix Template Library):矩阵模板库

专注于线性代数相关的计算任务,如各种形式矩阵的生成(对角,共轭,稀疏,对称等),相关的计算,变换,以及与一维向量的运算。

POOMA

一个免费的高性能的C++库,用于处理并行式科学计算。POOMA的面向对象设计方便了快速的程 序开发,对并行机器进行了优化以达到最高的效率,方便在工业和研究环境中使用。

CGAL:计算几何算法库

Computational Geometry Algorithms Library的目的是把在计 算几何方面的大部分重要的解决方案和方法以C++库的形式提供给工业和学术界的用户。
是一个大型C + +库的几何数据结构和算法,如Delaunay三角网,网格生成,布尔运算的多边形,以及各种几何处理算法。 CGAL是用来在各个领域:计算机图形学,科学可视化,计算机辅助设计与建模,地理信息系统,分子生物学,医学影像学,机器人学和运动规划,和数值方法。

Matlab的开源高仿 Octave

旨在解决线性和非线性的数值计算问题。也是跨平台的,源码中用了大量m4语言。

C++符号计算库 GiNaC

Eigen -目前较活跃

线性代数、矩阵、向量操作等运算的C++库。推荐使用。

C/C++数值计算库 mygsl

mygsl是一个基于GSL库的个性化开源数值计算项目.

IMSL

软件名称 IMSL C Numerical Library(不兼容vc6 编译器)。

分为统计库和数学库两部分。数学库包含应用数学和特殊函数。IMSL 程序库已成为数值分析解决方案的工业标准。 IMSL 程序库提供最完整与最值得信赖的函数库。 IMSL 数值程序库提供目前世界上最广泛被使用的 IMSL 算法,有超过 370 验证过、最正确与 thread-safe 的数学与统计程序。 IMSL FORTRAN 程序库提供新一代以 FORTRAN 90 为程序库基础的程序,能展现出最佳化的演算法能力应用于多处理器与其它高效能运算系统。

网络库

ACE

庞大、复杂,适合大型项目。开源、免费,不依赖第三方库,支持跨平台。

Asio

Asio基于Boost开发的异步IO库,封装了Socket,简化基于socket程序的开发。

开源、免费,支持跨平台。

POCO

POCO C++ Libraries 提供一套 C++ 的类库用以开发基于网络的可移植的应用程序,功能涉及线程、线程同步、文件系统访问、流操作、共享库和类加载、套接字以及网络协议包括:HTTP、 FTP、SMTP 等;其本身还包含一个 HTTP 服务器,提供 XML 的解析和 SQL 数据库的访问接口。POCO库的模块化、高效的设计及实现使得POCO特别适合嵌入式开发。在嵌入式开发领域,由于C++既适合底层(设备I/O、中断处理等)和高层面向对象开发,越来越流行。

libevent

Libevent是一个轻量级的开源高性能网络库,从它的官网标题:libevent-an event notification library就能知道它的机制是采用事件触发,封装了以下三种事件的响应:IO事件,定时器事件,信号事件。select模型来实现跨平台的操作,Windows环境下支持IOCP。Google的开源WEB浏览器Chromium在Mac和Linux版本中,也使用了Libevent,足见该库的质量。

libev

libev和libevent很像,按照作者的介绍,可以作为libevent的替代者,能够提供更高的性能。

libev是一个高性能事件循环,所实现的功能就是一个强大的reactor。

c++ sockets library

封装了sockets C API的C++类库。

支持SSL, IPv6, tcp 和 udp sockets, sctp sockets, http协议, 高度可定制的错误处理。

libcurl

libcurl是免费的轻量级的客户端网络库,支持DICT, FILE, FTP, FTPS, Gopher, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, POP3, POP3S, RTMP, RTSP, SCP, SFTP, SMTP, SMTPS, Telnet, TFTP. 支持SSL, HTTP POST, HTTP PUT, FTP上传, HTTP form上传,代理,cookies, 用户名与密码认证。

如果你开发的是客户端,libcurl是一个不错的选择。

clsocket

clsocket是一组轻量级的类,允许开发人员实现基于IP的网络程序。这个编译简单而且用起来也简单,推荐使用。

日志库

spdlog

参考链接

  1. C++数学库,by xigua1234.
  2. c++中关于M_PI,M_E的解释和使用,以及参考值,by SYITwin.
  3. GSL with CMake build support,by ampl.
  4. 在Visual Studio中使用GNU Scientific Library (GSL),by NULL_BOT.
  5. 开源免费的C/C++网络库(c/c++ sockets library) 七剑下天山,by 工程师WWW.
  6. 值得推荐的C/C++框架和库 (真的很强大),by tibet889.
  7. 有哪些值得推荐的c/c++开源框架与库,by 零声学院.
  8. C++ std::isnan等函数的使用,by ccf19881030.
  9. C/C++log日志库比较,by shy丶gril.

Web应用压力测试方法

发表于 2019-11-12 | 更新于 2019-11-25

Web应用在部署前,最好进行压力测试,以衡量其是否达到设计指标,判断其在何种极限条件下应用功能会失效。下面即介绍软件测试中压力测试,尤其是Web应用压力测试的相关知识。

压力测试

压力测试(英语:Stress testing)是针对特定系统或是组件,为要确认其稳定性而特意进行的严格测试。会让系统在超过正常使用条件下运作,然后再确认其结果。进行压力测试的原因可能包括:

  • 确认系统在什么条件下会损坏,以及安全使用条件。
  • 确认预测系统在什么条件下会损坏或其安全使用条件的数学模型是否准确。
  • 确认是否符合预期的规格。
  • 确认失效原因。
  • 确认系统在正常工作条件以外,是否可以正常运作。

软件测试中的压力测试是在超过正常运作条件以外的条件下运作系统,以确认健壮性的方式。压力测试对于关键任务软件格外的重要,但可以适用于各种的软件。压力测试一般较强调软件在高负载下的健壮性、可用性及异常处理,以及哪些在一般使用环境下算是正常行为。

例如一个网站设计容量是100个人同时点击,压力测试就要是采用120个同时点击的条件测试。

性能指标

web应用的性能指标主要有响应时间,吞吐量,并发量,性能计数器。

响应时间

响应时间指应用执行一个操作需要的时间,即从发出请求到最后收到响应数据所需要的时间。例如:打开一个网站的响应时间、数据库查询一条记录(有索引)的响应时间等等。

实践中通常采用的办法是重复请求,比如一个请求操作重复执行1万次,测试一万次执行的总响应时间之和,然后除以1万,就得到单次请求的响应时间。

吞吐量

吞吐量指单位时间内系统处理的请求数量,体现系统的整体处理能力。对于网站,可用“请求数/秒”、“页面数/秒”或“访问人数/天”、“处理业务数/小时”等来衡量。重要指标有TPS(每秒处理的事物数)、QPS(每秒查询的请求数)、HPS(每秒HTTP请求数)等。

并发量

并发量指系统能够同时处理的请求的数目,这个数字反映了系统的负载性能。对于网站而言,并发数指网站用户同时提交请求的用户数目。

性能计数器

性能计数器描述服务器或操作系统性能的一些数据指标。如System Load、对象与线程数、内存使用、CPU使用、磁盘与网络I/O等使用情况。通过对这些指标设置报警阈值,当监控系统发现性能计数器超过阈值时,就向开发人员和运维报警,及时发现异常并处理。

测试工具

压力测试工具有http_load、apache ab、siege。

参考链接

  1. 压力测试,by wikipedia.
  2. 压力测试 (软件),by wikipedia.
  3. 网站性能测试指标及网站压力测试,by 呦呦鹿鸣.
  4. Web 性能压测工具 ApacheBench(ab)使用总结,by juejin.

Axios请求超时处理方法

发表于 2019-11-11

Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。Vue2官方推荐使用Axios执行HTTP请求和处理HTTP响应。Axios具有如下特征:

  • 从浏览器中创建 XMLHttpRequests
  • 从 node.js 创建 http 请求
  • 支持 Promise API
  • 拦截请求和响应
  • 转换请求数据和响应数据
  • 取消请求
  • 自动转换 JSON 数据
  • 客户端支持防御 XSRF

下面介绍使用Axios的拦截功能实现Axios超时错误的统一处理。

Axios基本用法

执行 GET 请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 为给定 ID 的 user 创建请求
axios.get('/user?ID=12345')
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});

// 可选地,上面的请求可以这样做
axios.get('/user', {
params: {
ID: 12345
}
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});

执行 POST 请求:

1
2
3
4
5
6
7
8
9
10
axios.post('/user', {
firstName: 'Fred',
lastName: 'Flintstone'
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});

执行多个并发请求:

1
2
3
4
5
6
7
8
9
10
11
12
function getUserAccount() {
return axios.get('/user/12345');
}

function getUserPermissions() {
return axios.get('/user/12345/permissions');
}

axios.all([getUserAccount(), getUserPermissions()])
.then(axios.spread(function (acct, perms) {
// 两个请求现在都执行完成
}));

Axios拦截器

自定义 axios 实例添加拦截器,在请求或响应被 then 或 catch 处理前拦截它们。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// create an axios instance
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
// withCredentials: true, // send cookies when cross-domain requests
timeout: 5000 // request timeout
})

// 添加请求拦截器
service.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});

// 添加响应拦截器
service.interceptors.response.use(function (response) {
// 对响应数据做点什么
return response;
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error);
});

拦截timeout错误

在Axios响应拦截器中,处理timeout错误,代码如下:

1
2
3
4
5
6
7
8
9
10
11
service.interceptors.response.use(function (response) {
// 对响应数据做点什么
return response;
}, function (error) {
// 对响应错误做点什么
if(error.message.includes('timeout')){ // 判断请求异常信息中是否含有超时timeout字符串
console.log("错误回调", error);
alert("网络超时");
}
return Promise.reject(error);
});

参考链接

  1. Axios使用说明,by yunye.
  2. axios超时timeout拦截,by 亲爱的混蛋.

C++标准模板库STL入门

发表于 2019-11-10

最近C++编程中需要管理多个由同一个类实例化的对象,于是想到了C++的标准模板库STL,在此将STL的相关知识记录一下。

STL的来龙去脉

STL 历史可以追溯到 1972 年 C 语言在 UNIX 计算机上的首次使用。直到 1994 年,STL 才被正式纳入 C++ 标准中。

标准模板库(STL)是 C ++编程语言的软件库,影响了C ++标准库的许多部分。它提供了四个组件,分别称为算法,容器,仿函数和迭代器。

STL为 C ++提供了一组通用类,例如容器和关联数组,可以与任何内置类型和支持某些基本操作(例如复制和赋值)的任何用户定义类型一起使用。 STL算法独立于容器,从而大大降低了库的复杂性。

STL通过使用模板来实现其结果。这种方法提供的编译时多态性通常比传统的运行时多态性更有效。对现代 C ++编译器进行了调整,以最大程度地减少由于大量使用STL而引起的抽象损失。

STL是创建的第一个C ++通用算法和数据结构库,它牢记四个想法:通用编程,不损失效率的抽象性,冯·诺依曼计算模型和值语义。

STL 组件

STL 将“在数据上执行的操作”与“要执行操作的数据分开”,分别以如下概念指代:

  • 容器:包含、放置数据的地方。
  • 迭代器:在容器中指出一个位置、或成对使用以划定一个区域,用来限定操作所涉及到的数据范围。
  • 算法:要执行的操作。

容器

容器即用来存储并管理某类对象的集合。例如鱼缸是用来盛放金鱼的容器。标准模板库中容器主要分为序列容器(sequence containers)与关系容器(associative containers)。

序列容器包括:

  • vector
  • list
  • forward_list
  • deque
  • array

关联容器包括:

  • set
  • multiset
  • map
  • multimap
  • unordered_set
  • unordered_multiset
  • unordered_map
  • unordered_multimap

其他类型的容器包括:

  • bitset
  • valarray

迭代器

迭代器用于在一个对象群集的元素上进行遍历动作。对象群集可能是容器,也可能是容器的一部分。

迭代器的主要用途是为容器提供一组很小的公共接口。利用这个接口,某项操作可以行进至群集内的下一个元素。

每种容器都提供了各自的迭代器。迭代器了解该容器的内部结构,所以能够正确行进。迭代器的接口和一般指针类似。

迭代器的作用就是提供一个遍历容器内部所有元素的接口,因此迭代器的内部必须保存一个与容器相关联的指针,然后重载各种运算操作来方便遍历,其中最重要的就是∗运算符和->运算符,以及++,–等可能需要的运算符重载。

根据迭代器的操作方式的不同,迭代器分为五种:

  • 输入迭代器
  • 输出迭代器
  • 前向迭代器
  • 双向迭代器
  • 随机访问迭代器

迭代器的示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
#include <vector>

using namespace std;

int main()
{
vector<int> v; // 定义一个vector容器

v.push_back(1); // 向容器中添加3个元素
v.push_back(2);
v.push_back(3);

// 遍历向量的元素
vector<int>::iterator b = v.begin(); // 指向容器的第一个元素
vector<int>::iterator e = v.end(); // 指向容器尾元素的下一个位置

// C++11新标准的写法, auto关键字为类型推断,由编译器自动完成
// auto b = v.begin();
// auto e = v.end();

for (vector<int>::iterator iter = b; iter != e; ++iter)
{
cout << *iter << endl;
}

return 0;
}

算法

算法用来处理群集内的元素,可以出于不同目的搜寻、排序、修改、使用那些元素。所有容器的迭代器都提供一致的接口,通过迭代器的协助,算法程序可以用于任意容器。

STL 的一个特性是将数据和操作分离。数据由容器类别加以管理,操作则由可定制的算法定义。迭代器在两者之间充当“粘合剂”,以使算法可以和容器交互运作。

STL 的另一个特性即组件可以针对任意型别运作。“标准模板库”这一名称即表示“可接受任意型别”的模板,并且这些型别均可执行必要操作。

仿函数

STL中大量运用了仿函数。仿函数具有泛型编程强大的威力,是纯粹抽象概念的例证。

参考链接

  1. STL教程:C++ STL快速入门,by C语言中文网.
  2. STL是什么(STL简介),by C语言中文网.
  3. 标准模板库,by wikipedia.
  4. C++ STL初识及整理,by thinkChao.
  5. C++标准模板库(STL)迭代器的原理与实现,by wutao02.

异常处理的一些理解

发表于 2019-11-02

在编程过程,经常遇到的一个问题是如何优雅地处理异常。下面给出一些对这个问题的答案。

异常处理定义

异常处理,是编程语言或计算机硬件里的一种机制,用于处理软件或信息系统中出现的异常状况(即超出程序正常执行流程的某些特殊条件)。

异常处理与错误检测的区别

错误检测与异常处理区别在于:错误检测是在正常的程序流中,处理不可预见问题的代码,例如一个调用操作未能成功结束。

某些编程语言有这样的函数:当输入存在非法数据时不能被安全地调用,或者返回值不能与异常进行有效的区别。例如,C语言中的atoi函数(ASCII串到整数的转换)在输入非法时可以返回0。在这种情况下编程者需要另外进行错误检测(可能通过某些辅助全局变量如C的errno),或进行输入检验(如通过正则表达式),或者共同使用这两种方法。

通过异常处理,我们可以对用户在程序中的非法输入进行控制和提示,以防程序崩溃。从进程的视角,硬件中断相当于可恢复异常,虽然中断一般与程序流本身无关。从子程序编程者的视角,异常是很有用的一种机制,用于通知外界该子程序不能正常执行。如输入的数据无效(例如除数是0),或所需资源不可用(例如文件丢失)。如果系统没有异常机制,则编程者需要用返回值来标示发生了哪些错误。

异常处理的原则

为了更好的处理异常,应遵循以下三个原则:

  • 具体明确
  • 提早抛出
  • 延迟捕获

具体明确

捕获异常时具体明确有利于对异常进行恰当的处理。例如在Java中对同一try块定义多个catch块,以便对每种异常分别进行恰当的处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
File prefsFile = new File(prefsFilename);

try{
readPreferences(prefsFile);
}
catch (FileNotFoundException e){
// alert the user that the specified file
// does not exist
}
catch (EOFException e){
// alert the user that the end of the file
// was reached
}
catch (ObjectStreamException e){
// alert the user that the file is corrupted
}
catch (IOException e){
// alert the user that some other I/O
// error occurred
}

提早抛出

异常堆栈信息提供了导致异常出现的方法调用链的精确顺序,包括每个方法调用的类名,方法名,代码文件名甚至行数,以此来精确定位异常出现的现场。例如:

1
2
3
4
5
6
7
java.lang.NullPointerException
at java.io.FileInputStream.open(Native Method)
at java.io.FileInputStream.<init>(FileInputStream.java:103)
at jcheckbook.JCheckbook.readPreferences(JCheckbook.java:225)
at jcheckbook.JCheckbook.startup(JCheckbook.java:116)
at jcheckbook.JCheckbook.<init>(JCheckbook.java:27)
at jcheckbook.JCheckbook.main(JCheckbook.java:318)

通过逐步回退跟踪堆栈信息并检查代码,可以确定错误原因。

延迟捕获

捕获异常后怎么处理?最不该做的就是什么都不做。空的catch块等于把整个异常丢进黑洞,能够说明何时何处为何出错的所有信息都会永远丢失。把异常写到日志中还稍微好点,至少还有记录可查。但我们总不能指望用户去阅读或者理解日志文件和异常信息。

因此,不要过早捕获异常,而是在合适的层面捕获异常,以便你的程序要么可以从异常中有意义地恢复并继续下去,而不导致更深入的错误;要么能够为用户提供明确的信息,包括引导他们从错误中恢复过来。如果你的方法无法胜任,那么就不要处理异常,把它留到后面捕获和在恰当的层面处理。

参考链接

  1. 异常处理,by wikipedia.
  2. 如何优雅的处理异常(java)?,by zhihu.

Linux防SYN_Flood攻击的方法

发表于 2019-10-30 | 更新于 2023-01-20

最近管理的Linux服务器遭遇了SYN Flood攻击,因此研究一下防范该攻击的方法。

SYN Flood攻击的表现

使用ssh登录Linux服务器,在终端中输入netstat -antp能查看到有大量的链接时SYN_RECV状态,说明Linux服务器遭遇了SYN Flood攻击。

netstat常用参数

netstat常用参数如下:

  • -a (all)显示所有选项,默认不显示LISTEN相关
  • -t (tcp)仅显示tcp相关选项
  • -u (udp)仅显示udp相关选项
  • -n 拒绝显示别名,能显示数字的全部转化成数字。
  • -l 仅列出有在 Listen (监听) 的服務状态
  • -p 显示建立相关链接的程序名
  • -r 显示路由信息,路由表
  • -e 显示扩展信息,例如uid等
  • -s 按各个协议进行统计
  • -c 每隔一个固定时间,执行该netstat命令。

TCP连接状态

  • ESTABLISHED

指TCP连接已建立,双方可以进行方向数据传递

  • CLOSE_WAIT

这种状态的含义其实是表示在等待关闭。当对方close一个SOCKET后发送FIN报文给自己,你系统毫无疑问地会回应一个ACK报文给对方,此时则进入到CLOSE_WAIT状态。

  • LISTENING

指TCP正在监听端口,可以接受链接

  • TIME_WAIT

指连接已准备关闭。表示收到了对方的FIN报文,并发送出了ACK报文,就等2MSL后即可回到CLOSED可用状态了。

  • FIN_WAIT_1

FIN_WAIT_1和 FIN_WAIT_2状态的真正含义都是表示等待对方的FIN报 文。而这两种状态的区别是:FIN_WAIT_1状态实际上是当SOCKET在ESTABLISHED状态时,它想主动关闭连接,向对方发送了FIN 报文,此时该SOCKET即进入到FIN_WAIT_1 状态。而当对方回应ACK 报文后,则进入到FIN_WAIT_2状态,当然在实际的正常情况 下,无论对方何种情况下,都应该马上回应ACK报文,所以FIN_WAIT_1状态一般是比较难见到的,而FIN_WAIT_2 状态还有时常常可以用 netstat看到。

  • FIN_WAIT_2

FIN_WAIT_2 状态下的SOCKET,表示半连接,也即有一方要求close 连接,但另外还告诉对方,我暂时还有点数据需要传送给你,稍后再关闭连接。

  • LAST_ACK

是被动关闭一方在发送FIN报文后,最后等待对方的ACK报文。当收到ACK报文后,也即可以进入到CLOSED可用状态了

  • SYNC_RECEIVED

表示接受到了SYN报文,在正常情况下,这个状态是服务器端的SOCKET在建立TCP连接时的三次握手会话过程中的一个中间状态,很短暂,基本上用netstat你是很难看到这种状态的,除非你特意写了一个客户端测试程序,故意将三次TCP握手过程中最后一个ACK报文不予发送。

  • SYNC_SEND

已经主动发出连接建立请求。与SYN_RCVD遥想呼应,当客户端SOCKET执行CONNECT连接时,它首先发送SYN报文,因此也随即它会进入到了SYN_SENT状态,并等待服务端的发送三次握手中的第2个报文。

防范措施

首先确保ssh登录密码没有泄露,再修改系统相关配置。

剔出其他登录用户

当ssh登录密码泄露,会有其他用户登录,所以需要剔出其他登录用户,再修改密码和系统相关配置。剔出其他登录用户的方法如下:

  • 查看系统在线用户
1
2
3
4
5
[root@apache ~]# w 
14:15:41 up 42 days, 56 min, 2 users, load average: 0.07, 0.02, 0.00
USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT
root pts/0 116.204.64.165 14:15 0.00s 0.06s 0.04s w
root pts/1 116.204.64.165 14:15 2.00s 0.02s 0.02s –bash
  • 查看当前自己占用终端,防止把自己干掉了
1
2
[root@apache ~]# who am i 
root pts/0 2013-01-16 14:15 (116.204.64.165)
  • 用pkill 命令剔除对方
1
2
# 信号代码 -9 ,表示强制终止
[root@apache ~]# pkill -9 -t pts/1
  • 用w命令在看看干掉没。
1
2
3
4
[root@apache ~]# w 
14:19:47 up 42 days, 1:00, 1 user, load average: 0.00, 0.00, 0.00
USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT
root pts/0 116.204.64.165 14:15 0.00s 0.03s 0.00s w

抵御SYN攻击的方法

SYN攻击是利用TCP/IP协议3次握手的原理,发送大量的建立连接的网络包,但不实际建立连接,最终导致被攻击服务器的网络队列被占满,无法被正常用户访问。

修改SYN相关系统配置

Linux内核提供了若干SYN相关的配置,用命令: sysctl -a | grep syn 看到:

1
2
3
4
net.ipv4.tcp_max_syn_backlog = 1024 
net.ipv4.tcp_syncookies = 0
net.ipv4.tcp_synack_retries = 5
net.ipv4.tcp_syn_retries = 5

tcp_max_syn_backlog是SYN队列的长度,tcp_syncookies是一个开关,是否打开SYN Cookie 功能,该功能可以防止部分SYN攻击tcp_synack_retries和tcp_syn_retries定义SYN 的重试次数。  

加大SYN队列长度可以容纳更多等待连接的网络连接数,打开SYN Cookie功能可以阻止部分 SYN攻击,降低重试次数也有一定效果。

调整上述设置的方法是:

1
2
3
4
5
6
7
8
9
# 增加SYN队列长度到2048:
sysctl -w net.ipv4.tcp_max_syn_backlog=2048

# 打开SYN COOKIE功能:
sysctl -w net.ipv4.tcp_syncookies=1

# 降低重试次数:
sysctl -w net.ipv4.tcp_synack_retries=3
sysctl -w net.ipv4.tcp_syn_retries=3

为了系统重启动时保持上述配置,可将上述命令加入到/etc/rc.d/rc.local文件中。

iptables阻止syn flood攻击

防止同步包洪水(Sync Flood):

1
2
# –limit 1/s 限制syn并发数每秒1次,可以根据自己的需要修改
iptables -A FORWARD -p tcp –syn -m limit –limit 1/s -j ACCEPT

防止各种端口扫描:

1
iptables -A FORWARD -p tcp –tcp-flags SYN,ACK,FIN,RST RST -m limit –limit 1/s -j ACCEPT

Ping洪水攻击(Ping of Death):

1
iptables -A FORWARD -p icmp –icmp-type echo-request -m limit –limit 1/s -j ACCEPT

屏蔽 SYN_RECV 的连接:

1
iptables -A INPUT -p tcp -m tcp –tcp-flags SYN,RST,ACK SYN -m limit –limit 1/sec -j ACCEPT

禁止某IP访问:

1
iptables -I INPUT -s xxx.xxx.xxx.xx -j DROP

参考链接

  1. LINUX 服务器遭到SYN FLOOD攻击,by 晓风残梦.
  2. Linux netstat命令详解,by ggjucheng.
  3. netstat 的10个基本用法,by LCTT bazz2.
  4. netstat用法及TCP state解析,by vigarbuaa.
  5. Linux踢出其他正在SSH登陆用户,by 艾欧里亚.
  6. [Linux] killall 、kill 、pkill 命令详解,by 骑着蜗牛游世界.
  7. Linux中kill,pkill,killall和xkill命令汇总讲解,by simonGeek.
  8. Linux防止syn flood攻击,屏蔽 SYN_RECV 的连接,by Sphinx 中文站.
  9. iptables,by wangchujiang.
  10. TCP状态机,by 大蟒传奇.

不同语言的计时方法

发表于 2019-10-27

剖析代码性能时通常需要计时。下面记录不同语言的各种计时方法。

C++计时方法

传统计时方法的代码如下:

1
2
3
4
5
6
7
#include <ctime>
using namespace std;

clock_t start = clock();
// do something...
clock_t end = clock();
cout << "花费了" << (double)(end - start) / CLOCKS_PER_SEC << "秒" << endl;

C++11 标准的”最佳计时方法“的代码:

1
2
3
4
5
6
7
8
9
10
11
#include <chrono>   
using namespace std;
using namespace chrono;

auto start = system_clock::now();
// do something...
auto end = system_clock::now();
auto duration = duration_cast<microseconds>(end - start);
cout << "花费了"
<< double(duration.count())
<< "微秒" << endl;

Python计时方法

在Jupyter Notebook中,计时使用一个magic command:%timeit。

参考链接

  1. C++11 新的计时方法——std::chrono 大法好,by sicolex.

C++高性能网络编程

发表于 2019-10-26 | 更新于 2024-03-20

最近需要用C++编写一个网络程序,因此将网络编程的相关知识整理学习一下。

Socket编程

Socket 是对 TCP/IP 协议族的一种封装,是应用层与TCP/IP协议族通信的中间软件抽象层。从设计模式的角度看来,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

Socket 还可以认为是一种网络间不同计算机上的进程通信的一种方法,利用三元组(ip地址,协议,端口)就可以唯一标识网络中的进程,网络中的进程通信可以利用这个标志与其它进程进行交互。

Socket 起源于 Unix ,Unix/Linux 基本哲学之一就是“一切皆文件”,都可以用“打开(open) –> 读写(write/read) –> 关闭(close)”模式来进行操作。因此 Socket 也被处理为一种特殊的文件。

Socket类型

创建 socket 的时候需要指定 socket 的类型,一般有三种:

  • SOCK_STREAM:面向连接的稳定通信,底层是 TCP 协议,我们会一直使用这个。
  • SOCK_DGRAM:无连接的通信,底层是 UDP 协议,需要上层的协议来保证可靠性。
  • SOCK_RAW:更加灵活的数据控制,能让你指定 IP 头部

Socket编程接口

  • socket():创建socket
  • bind():绑定socket到本地地址和端口,通常由服务端调用
  • listen():TCP专用,开启监听模式
  • accept():TCP专用,服务器等待客户端连接,一般是阻塞态
  • connect():TCP专用,客户端主动连接服务器
  • send():TCP专用,发送数据
  • recv():TCP专用,接收数据
  • sendto():UDP专用,发送数据到指定的IP地址和端口
  • recvfrom():UDP专用,接收数据,返回数据远端的IP地址和端口
  • closesocket():关闭socket

Socket通信流程

TCP通信流程

图1 TCP通信流程

UDP通信流程

图2 UDP通信流程

字节序

不同的计算机对数据的存储格式不一样,比如 32 位的整数 0x12345678,可以在内存里从高到低存储为 12-34-56-78 或者从低到高存储为 78-56-34-12。

但是这对于网络中的数据来说就带来了一个严重的问题,当机器从网络中收到 12-34-56-78 的数据时,它怎么知道这个数据到底是什么意思?

解决的方案也比较简单,在传输数据之前和接受数据之后,必须调用 htonl/htons 或 ntohl/ntohs 先把数据转换成网络字节序或者把网络字节序转换为机器的字节序。

  • TCP 和 UDP 的端口是互不干扰的,也就是说系统可以同时开启 TCP 80 端口和 UDP 80 端口。
  • socket 不属于任何一层网络协议,它是对 TCP 层的封装,方便网络编程。

网络编程模型

常用的服务器应用分类如下:

服务器应用分类

图3 服务器应用分类

同步阻塞迭代模型

同步阻塞迭代模型是最简单的一种IO模型,其核心代码如下:

1
2
3
4
5
6
7
8
9
bind(srvfd);
listen(srvfd);
for(;;)
{
clifd = accept(srvfd,...); //开始接受客户端来的连接
read(clifd,buf,...); //从客户端读取数据
dosomthingonbuf(buf);
write(clifd,buf)//发送数据到客户端
}

上面的程序存在如下一些弊端:

  1. 如果没有客户端的连接请求,进程会阻塞在accept系统调用处,程序不能执行其他任何操作。(系统调用使得程序从用户态陷入内核态)
  2. 在与客户端建立好一条链路后,通过read系统调用从客户端接受数据,而客户端合适发送数据过来是不可控的。如果客户端迟迟不发生数据过来,则程序同样会阻塞在read调用,此时,如果另外的客户端来尝试连接时,都会失败。
  3. 同样的道理,write系统调用也会使得程序出现阻塞(例如:客户端接受数据异常缓慢,导致写缓冲区满,数据迟迟发送不出)。

多进程并发模型

多进程并发模型在同步阻塞迭代模型的基础上进行了一些改进,以避免是程序阻塞在read系统调用上。核心代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
bind(srvfd);
listen(srvfd);
for(;;){
clifd = accept(srvfd,...); //开始接受客户端来的连接
ret = fork();
switch( ret )
{
case -1 :
do_err_handler();
break;
case 0: // 子进程
client_handler(clifd);
break ;
default : // 父进程
close(clifd);
continue ;
}
}
void client_handler(clifd)
{
read(clifd,buf,...); //从客户端读取数据
dosomthingonbuf(buf);
write(clifd,buf)//发送数据到客户端
}

上述程序在accept系统调用时,如果没有客户端来建立连接,会阻塞在accept处。一旦某个客户端连接建立起来,则立即开启一个新的进程来处理与这个客户的数据交互。避免程序阻塞在read调用,而影响其他客户端的连接。

多线程并发模型

在多进程并发模型中,每一个客户端连接开启fork一个进程,若客户端连接较大,则系统依然将不堪负重。通过多线程(或线程池)并发模型,可以在一定程度上改善这一问题。

在服务端的线程模型实现方式一般有三种:

  • 按需生成(来一个连接生成一个线程)
  • 线程池(预先生成很多线程)
  • Leader follower(LF)

以第一种为例,其核心代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void *thread_callback( void *args ) //线程回调函数
{
int clifd = *(int *)args ;
client_handler(clifd);
}

void client_handler(clifd)
{
read(clifd,buf,...); //从客户端读取数据
dosomthingonbuf(buf);
write(clifd,buf)//发送数据到客户端
}
bind(srvfd);
listen(srvfd);
for(;;)
{
clifd = accept();
pthread_create(...,thread_callback,&clifd);
}

服务端分为主线程和工作线程,主线程负责accept()连接,而工作线程负责处理业务逻辑和流的读取等。因此,即使在工作线程阻塞的情况下,也只是阻塞在线程范围内,对继续接受新的客户端连接不会有影响。

第二种实现方式,通过线程池的引入可以避免频繁的创建、销毁线程,能在很大程序上提升性能。

但多线程模型先天具有如下缺点:

  • 稳定性相对较差。一个线程的崩溃会导致整个程序崩溃。
  • 临界资源的访问控制,在加大程序复杂性的同时,锁机制的引入会是严重降低程序的性能。性能上可能会出现“辛辛苦苦好几年,一夜回到解放前”的情况。

IO多路复用模型

多进程模型和多线程(线程池)模型每个进程/线程只能处理一路IO,在服务器并发数较高的情况下,过多的进程/线程会使得服务器性能下降。而通过多路IO复用,能使得一个进程同时处理多路IO,提升服务器吞吐量。这是一种进程预先告知内核的能力,让内核发现进程指定的一个或多个IO条件就绪了,就通知进程。使得一个进程能在一连串的事件上等待。

IO复用的实现方式目前主要有select、poll和epoll。

select和poll的原理基本相同:

  • 注册待侦听的fd(这里的fd创建时最好使用非阻塞)

  • 每次调用都去检查这些fd的状态,当有一个或者多个fd就绪的时候返回

  • 返回结果中包括已就绪和未就绪的fd

相比select,poll解决了单个进程能够打开的文件描述符数量有限制这个问题:select受限于FD_SIZE的限制,如果修改则需要修改这个宏重新编译内核;而poll通过一个pollfd数组向内核传递需要关注的事件,避开了文件描述符数量限制。此外,select和poll共同具有的一个很大的缺点就是包含大量fd的数组被整体复制于用户态和内核态地址空间之间,开销会随着fd数量增多而线性增大。

epoll的出现,解决了select、poll的缺点:

  • 基于事件驱动的方式,避免了每次都要把所有fd都扫描一遍。

  • epoll_wait只返回就绪的fd。

  • epoll使用nmap内存映射技术避免了内存复制的开销。

  • epoll的fd数量上限是操作系统的最大文件句柄数目,这个数目一般和内存有关,通常远大于1024。

总结:

  • select:支持注册 FD_SETSIZE(1024) 个 socket。
  • poll: poll 作为 select 的替代者,最大的区别就是,poll 不再限制 socket 数量。
  • epoll:epoll 能直接返回具体的准备好的通道,时间复杂度 O(1)。

参考链接

  1. C++网络框架和库,by 帝王铠.
  2. C++ 高性能服务器网络框架设计细节,by 范蠡.
  3. 值得推荐的C/C++框架和库 (真的很强大),by tibet889.
  4. 有哪些值得推荐的c/c++开源框架与库,by 零声学院.
  5. 有什么适合提高 C/C++ 网络编程能力的开源项目推荐?,by zhihu.
  6. 网络库libevent、libev、libuv对比,by 小麒麟666.
  7. libevent、libev、libuv、IOCP、asio、muduo优劣分析、QT下编译libevent静态库,by 雒珣.
  8. Reactor 模式简介,by lotabou.
  9. IO设计模式:Reactor和Proactor对比,by 大CC.
  10. Proactor和Reactor模型,by wiseAaron.
  11. 如何深刻理解reactor和proactor?,by zhihu.
  12. Libevent 编程指南,by senlin.
  13. 网络编程(三):从libevent到事件通知机制,by auxten.
  14. 使用 libevent 和 libev 提高网络应用性能,by Martin C. Brown.
  15. 兄déi,libuv了解一下,by 宋光宇.
  16. Libuv 源码分析(2):源码一览,by Tubumu.
  17. IO设计模式:Actor、Reactor、Proactor,by _raindrop.
  18. Proactor网络编程模式歪解,by WUYU.
  19. 深入理解Reactor模式,by 独木の白帆.
  20. socket 编程基础知识,by cizixs.
  21. 一文读懂高性能网络编程中的I/O模型,by Jack Jiang.
  22. 几种典型的服务器网络编程模型归纳(select poll epoll),by DrStream.
  23. Linux IO模型和网络编程模型,by wade&luffy.
  24. 开源免费的C/C++网络库(c/c++ sockets library) 七剑下天山,by 工程师WWW.
  25. c++ 网络编程(一)TCP/UDP windows/linux 下入门级socket通信 客户端与服务端交互代码,by aircraft.

地形数据格式总结

发表于 2019-10-20 | 更新于 2019-11-15

地形数据是能够表示地球表面高低起伏状态的数据,即具有高程信息的数据。数字高程模型(DEM)是一种对空间起伏变化的连续表示方法,是一种特殊的 DatasetGrid 数据模型,每个网格的值为高程值,而且有标准的颜色表来表示,这对分幅 DEM 图像的合成很有帮助。

数字地面模型(digital terrain model,DTM)就是以数字的形式来表示实际地形特征的空间分布。有时所指的地形特征点仅指地面点的高程,就将这种数字地形描述称为数字高程模型(digital elevation model,DEM)。最初是于1958年由美国麻省理工学院Miller教授提出。数字地面模型广泛用于遥感,地理信息系统,大地测量和电子地图等领域。

常用的地形数据主要有:etopo、GTOPO30、GEBCO以及SRTM。按照精度从低到高排序如下:

  • etopo5:GMT 网格数据之 etopo5
  • etopo2:GMT 网格数据之 ETOPO2v2
  • etopo1:GMT 网格数据之 ETOPO1
  • GEBCO:GEBCO:全球水深数据
  • GTOPO30:GMT 网格数据之 GTOPO30
  • SRTM30_PLUS:全球地形起伏数据:SRTM30_PLUS
  • SRTM15_PLUS:全球地形起伏数据:SRTM15_PLUS
  • SRTM3:高精度地形网格数据 SRTM
  • ASTER GDEM:全球数字高程数据:ASTER GDEM
数据源 空间分辨率 覆盖范围 陆地/海洋
etopo5 5 弧分 全球 陆地 + 海洋
etopo2 2 弧分 全球 陆地 + 海洋
etopo1 1 弧分 全球 陆地 + 海洋
GEBCO 1 弧分 / 30 弧秒 全球 陆地 + 海洋
GTOPO30 30 弧秒 全球 陆地
SRTM30_PLUS 30 弧秒 (约 1km) 纬度 [-81,81] 陆地 + 海洋
SRTM15_PLUS 15 弧秒 (约 500m) 纬度 [-81,81] 陆地 + 海洋
SRTM 3 弧秒 (约 90m) 纬度 [-60,60] 陆地
ASTER GDEM 1 弧秒 (约 30m) 纬度 [-83,83] 陆地

ETOPO

ETOPO是一种地形高程数据。该数据由NGDC美国地球物理中心发布(U.S. National Geophysical Data Center )。与srtm、aster gdem一样,均为高程数据,所不同的是它还包括海洋海底地形数据。

ETOPO地形数据有五种规格, ETOPO1的效果最好,ETOPO2、ETOPO5数据尽管可以使用,但目前已不推荐采用。其中ETOPO1约三百多兆的压缩包, ETOPO2约九十兆。

ETOPO1

ETOPO1包含了全球地形和海洋深度,采样间隔为1弧分,是目前精度最高的global relief数据;其分为两个版本,Ice Surface和Bedrock,两个版本基本一致。不同之处在于在处理南极洲和Greenland地形时,前者给出的是加上冰盖层之后的高程,后者给出的是岩床的高程。

对于每个版本又细分为 grid-registered和cell-registered,其中grid-registered是权威版本,cell-registered是衍生版本,因而推荐下载使用grid-registered版本。

GEBCO

GEBCO数据的全称是General Bathymetric Chart of the Oceans( 全球海洋通用水深数据) ,是由国际海道测量组织(IHO) 和政府间海洋学委员会(IOC) 联合发布的最全面的世界大洋海底地形数据,也是当今海洋模式中最常用的海洋水深数据之一。

SRTM

SRTM 即航天飞机雷达地形测绘使命。航天地形测绘是指以人造地球卫星、宇宙飞船、航天飞机等航天器为工作平台,对地球表面所进行的遥感测量。以往的航天测绘由于其精度有限,一般只能制作中、小比例尺地图。SRTM则是美国太空总署(NASA)和国防部国家测绘局(NIMA)以及德国与意大利航天机构共同合作完成联合测量,由美国发射的“奋进”号航天飞机上搭载SRTM系统完成。本次测图任务从2000年2月11日开始至22日结束,共进行了11天总计222小时23分钟的数据采集工作,获取北纬60度至南纬56度之间总面积超过1.19亿平方公里的雷达影像数据,覆盖地球80%以上的陆地表面。

SRTM系统获取的雷达影像的数据量约9.8万亿字节,经过两年多的数据处理,制成了数字地形高程模型(DEM)。SRTM产品2003年开始公开发布,经历多修订,目前最新的版本为V4.1版本。

SRTM地形数据主要包含两类数据:SRTM3和SRTM1。

  • SRTM3精度为3弧秒,即90m一个点,包括非洲、北美、南美、欧亚、澳大利亚以及部分岛屿;
  • SRTM1精度为1弧秒,即30m一个点,仅限美国地区;

所有的数据被分为1度1度的单元,共计上万个数据文件。文件名代表了该单元左下角的位置;不同网站下载的数据文件名不同,但文件名类似N37W105的 数据文件,其单元左下角的坐标为(37N,105W);每个SRTM3数据文件包含了12011201个采样点;高程数据单位为m,参考水准面为 WGS84大地水准面模型;网格划分采用gridline registration方式,因而单元的南北边及左右边与相邻单元的边重合。

全国DEM 1km、500m和250m数据(SRTM 90m)

中国海拔高度(DEM)空间分布数据来源于美国奋进号航天飞机的雷达地形测绘SRTM(Shuttle Radar Topography Mission,SRTM)数据。SRTM数据有现实性强、免费获取等优点,全球许多应用研究都采用SRTM数据开展环境分析。该数据集为基于最新的SRTM V4.1数据经重采样生成,包括1km、500m和250m三种精度的全国一张图数据。数据采用WGS84椭球投影。

hgt转tiff

1
gdal_translate -of GTiff N40E120.hgt N40E120.tif

ASTER GDEM

ASTER GDEM,即先进星载热发射和反射辐射仪全球数字高程模型,与SRTM一样为数字高程DEM,其全球空间分辨率为30米。该数据是根据 NASA的新一代对地观测卫星Terra的详尽观测结果制作完成的。其数据覆盖范围为北纬83°到南纬83°之间的所有陆地区域,达到了地球陆地表面的99%。号称是“迄今最完整的全球地形数据”。

ASTER GDEM:空间分辨率:1弧度秒 (约30 米),精度:垂直精度20米,水平精度30米,SRTM数据的纬度覆盖范围是[-60,60],ASTER GDEM数据的纬度覆盖范围为[-83,83];SRTM的空间分辨率一般为90m,只有美国境内存在空间分辨率为30m的数据;ASTER GDEM的空间分辨率为30m;

参考链接

  1. 全国DEM 1km、500m和250m数据(SRTM 90m),by 资源环境数据云平台.
  2. Digital_elevation_model,by wikipedia.
  3. GMT地形数据,by YF2015.
  4. 全球地形起伏数据总结,by SeisMan.
  5. Cesium卫星地图和高程数据切片经验总结,by jackhuang.
  6. 高精度地形网格数据 SRTM,by seisman.
  7. 工具gdal2srtmtiles的安装使用,by 四域公子.
  8. 地球椭球,by wikipedia.
  9. 椭球体的重要参数和公式,by cyqian.
上一页1…323334…53下一页

Jack Huang

521 日志
67 标签
© 2025 Jack Huang
由 Hexo 强力驱动
|
主题 — NexT.Muse