您现在的位置是:网站首页> C/C++
Linux C编程笔记
- C/C++
- 2024-12-15
- 398人已阅读
Linux C编程笔记
CMakeLists.txt简介
CMakeLists详解
一、CMake简介
cmake 是一个跨平台、开源的构建系统。它是一个集软件构建、测试、打包于一身的软件。它使用与平台和编译器独立的配置文件来对软件编译过程进行控制。
二、常用命令
1. 指定cmake最小版本
cmake_minimum_required(VERSION 3.0)
这行命令是可选的,我们可以不写这句话,但在有些情况下,如果 CMakeLists.txt 文件中使用了一些高版本 cmake 特有的一些命令的时候,就需要加上这样一行,提醒用户升级到该版本之后再执行 cmake。
2. 设置项目名称
project(demo)
这个命令不是强制性的,但最好都加上。它会引入两个变量 demo_BINARY_DIR 和 demo_SOURCE_DIR,同时,cmake 自动定义了两个等价的变量 PROJECT_BINARY_DIR 和 PROJECT_SOURCE_DIR。
3. 设置编译类型
add_executable(demo demo.cpp) # 生成可执行文件
add_library(common STATIC util.cpp) # 生成静态库
add_library(common SHARED util.cpp) # 生成动态库或共享库
add_library 默认生成是静态库,通过以上命令生成文件名字,
在 Linux 下是:
demo
libcommon.a
libcommon.so
在 Windows 下是:
demo.exe
common.lib
common.dll
4. 指定编译包含的源文件
4.1 明确指定包含哪些源文件
add_library(demo demo.cpp test.cpp util.cpp)
4.2 搜索所有的cpp文件
aux_source_directory(. SRC_LIST) # 搜索当前目录下的所有.cpp文件
add_library(demo ${SRC_LIST})
aux_source_directory(dir VAR) 意思是发现一个目录 dir 下所有的源代码文件,并将列表存储在一个变量 VAR 中。
4.3 自定义搜索规则
file(GLOB SRC_LIST "*.cpp" "protocol/*.cpp")
add_library(demo ${SRC_LIST})
# 或者
file(GLOB SRC_LIST "*.cpp")
file(GLOB SRC_PROTOCOL_LIST "protocol/*.cpp")
add_library(demo ${SRC_LIST} ${SRC_PROTOCOL_LIST})
# 或者
aux_source_directory(. SRC_LIST)
aux_source_directory(protocol SRC_PROTOCOL_LIST)
add_library(demo ${SRC_LIST} ${SRC_PROTOCOL_LIST})
对于收集文件的指令
file(GLOB files LIST_DIRECTORIES false *)
其中,GLOB命令将所有匹配<globbing-expressions>(可选,假如不写,毛都匹配不到)的文件挑选出来,默认以字典顺序排序。
5. 查找指定的库文件
find_library(VAR name path) 查找到指定的预编译库,并将它的路径存储在变量中。
默认的搜索路径为 cmake 包含的系统库,因此如果是 NDK 的公共库只需要指定库的 name 即可。
find_library( log-lib log )
# Sets the name of the path variable.
# Specifies the name of the NDK library that you want CMake to locate.
类似的命令还有 find_file() 、 find_path() 、 find_program() 、 find_package()。
6. 设置包含的目录
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/include
)
Linux 下还可以通过如下方式设置包含的目录
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -I${CMAKE_CURRENT_SOURCE_DIR}")
7. 设置链接库搜索目录
link_directories(${CMAKE_CURRENT_SOURCE_DIR}/libs)
Linux 下还可以通过如下方式设置包含的目录
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${CMAKE_CURRENT_SOURCE_DIR}/libs")
8. 设置需要链接的库
在cmake语法中,link_libraries和target_link_libraries是很重要的两个链接库的方式,虽然写法上很相似,但是功能上有很大区别:
link_libraries用在add_executable之前,target_link_libraries用在add_executable之后。
link_libraries用来链接静态库,target_link_libraries用来链接导入库,即按照header file + .lib + .dll方式隐式调用动态库的.lib库。
link_libraries(${log-lib})
# ${log-lib}无需含有文件后缀,下同
target_link_libraries(demo ${log-lib} )
# 目标库
# 目标库需要链接的库
# log-lib 是上面 find_library 指定的变量名
在 Windows 下,系统会根据链接库目录,搜索 xxx.lib 文件,Linux 下会搜索 xxx.so 或者 xxx.a 文件,如果都存在会优先链接动态库(so 后缀)。
8.1 指定链接动态库或静态库
target_link_libraries(demo libface.a) # 链接libface.a
target_link_libraries(demo libface.so) # 链接libface.so
8.1 指定全路径
target_link_libraries(demo ${CMAKE_CURRENT_SOURCE_DIR}/libs/libface.a)
target_link_libraries(demo ${CMAKE_CURRENT_SOURCE_DIR}/libs/libface.so)
8.3 指定链接多个库
target_link_libraries(demo
${CMAKE_CURRENT_SOURCE_DIR}/libs/libface.a
boost_system.a
boost_thread
pthread)
9. 设置变量
9.1 set 直接设置变量的值
set(SRC_LIST main.cpp test_fun.cpp)
add_executable(demo ${SRC_LIST})
9.2 set 追加设置变量的值
set(SRC_LIST main.cpp)
set(SRC_LIST ${SRC_LIST} test.cpp)
add_executable(demo ${SRC_LIST})
9.3 list 追加或者删除变量的值
set(SRC_LIST main.cpp)
# SRC_LIST中追加 test_fun.cpp
list(APPEND SRC_LIST test_fun.cpp)
# SRC_LIST中删除 main.cpp
list(REMOVE_ITEM SRC_LIST main.cpp)
add_executable(demo ${SRC_LIST})
10. 条件控制
10.1 if…elseif…else…endif
逻辑判断和比较:
if (expression):expression 不为空(0,N,NO,OFF,FALSE,NOTFOUND)时为真
if (not exp):与上面相反
if (var1 AND var2)
if (var1 OR var2)
if (COMMAND cmd):如果 cmd 确实是命令并可调用为真
if (EXISTS dir) if (EXISTS file):如果目录或文件存在为真
if (file1 IS_NEWER_THAN file2):当 file1 比 file2 新,或 file1/file2 中有一个不存在时为真,文件名需使用全路径
if (IS_DIRECTORY dir):当 dir 是目录时为真
if (DEFINED var):如果变量被定义为真
if (var MATCHES regex):给定的变量或者字符串能够匹配正则表达式 regex 时为真,此处 var 可以用 var 名,也可以用 ${var}
if (string MATCHES regex)
数字比较:
if (variable LESS number):LESS 小于
if (string LESS number)
if (variable GREATER number):GREATER 大于
if (string GREATER number)
if (variable EQUAL number):EQUAL 等于
if (string EQUAL number)
字母表顺序比较:
if (variable STRLESS string)
if (string STRLESS string)
if (variable STRGREATER string)
if (string STRGREATER string)
if (variable STREQUAL string)
if (string STREQUAL string)
示例:
if(MSVC)
set(LINK_LIBS common)
else()
set(boost_thread boost_log.a boost_system.a)
endif()
target_link_libraries(demo ${LINK_LIBS})
# 或者
if(UNIX)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -fpermissive -g")
else()
add_definitions(-D_SCL_SECURE_NO_WARNINGS
D_CRT_SECURE_NO_WARNINGS
-D_WIN32_WINNT=0x601
-D_WINSOCK_DEPRECATED_NO_WARNINGS)
endif()
if(${CMAKE_BUILD_TYPE} MATCHES "debug")
...
else()
...
endif()
10.2 while…endwhile
while(condition)
...
endwhile()
10.3 foreach…endforeach
#start 表示起始数,stop 表示终止数,step 表示步长
foreach(loop_var RANGE start stop [step])
...
endforeach(loop_var)
示例:
foreach(i RANGE 1 9 2)
message(${i})
endforeach(i)
# 输出:13579
11. 打印信息
message(${PROJECT_SOURCE_DIR})
message("build with debug mode")
message(WARNING "this is warnning message")
message(FATAL_ERROR "this build has many error") # FATAL_ERROR 会导致编译失败
12. 包含其它 cmake 文件
include(./common.cmake) # 指定包含文件的全路径
include(def) # 在搜索路径中搜索def.cmake文件
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) # 设置include的搜索路径
三、常见变量
预定义变量
PROJECT_SOURCE_DIR: 工程的根目录
PROJECT_BINARY_DIR: 运行 cmake 命令的目录,通常是 ${PROJECT_SOURCE_DIR}/build
PROJECT_NAME: 返回通过 project 命令定义的项目名称
CMAKE_CURRENT_SOURCE_DIR: 当前处理的 CMakeLists.txt 所在的路径
CMAKE_CURRENT_BINARY_DIR: target 编译目录
CMAKE_CURRENT_LIST_DIR: CMakeLists.txt 的完整路径
CMAKE_CURRENT_LIST_LINE: 当前所在的行
CMAKE_MODULE_PATH: 定义自己的 cmake 模块所在的路径,SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake),然后可以用INCLUDE命令来调用自己的模块
EXECUTABLE_OUTPUT_PATH: 重新定义目标二进制可执行文件的存放位置
LIBRARY_OUTPUT_PATH: 重新定义目标链接库文件的存放位置
环境变量
使用环境变量
$ENV{Name}
写入环境变量
set(ENV{Name} value) # 这里没有“$”符号
系统信息
-CMAKE_MAJOR_VERSION: cmake 主版本号,比如 3.4.1 中的 3
-CMAKE_MINOR_VERSION: cmake 次版本号,比如 3.4.1 中的 4
-CMAKE_PATCH_VERSION: cmake 补丁等级,比如 3.4.1 中的 1
-CMAKE_SYSTEM: 系统名称,比如 Linux--2.6.22
-CMAKE_SYSTEM_NAME: 不包含版本的系统名,比如 Linux
-CMAKE_SYSTEM_VERSION: 系统版本,比如 2.6.22
-CMAKE_SYSTEM_PROCESSOR: 处理器名称,比如 i686
-UNIX: 在所有的类 UNIX 平台下该值为 TRUE,包括 OS X 和 cygwin
-WIN32: 在所有的 win32 平台下该值为 TRUE,包括 cygwin
主要开关选项
BUILD_SHARED_LIBS: 这个开关用来控制默认的库编译方式,如果不进行设置,使用 add_library 又没有指定库类型的情况下,默认编译生成的库都是静态库。如果 set(BUILD_SHARED_LIBS ON) 后,默认生成的为动态库
CMAKE_C_FLAGS: 设置 C 编译选项,也可以通过指令 add_definitions() 添加
CMAKE_CXX_FLAGS: 设置 C++ 编译选项,也可以通过指令 add_definitions() 添加
add_definitions(-DENABLE_DEBUG -DABC) # 参数之间用空格分隔
创建原始套接字转发数据
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#define BUFFER_SIZE 65536
// 模拟路由器的IP地址(实际中路由器有多个接口对应不同IP,这里简化示例)
const char* router_ip_str = "192.168.1.1";
int main() {
int sockfd;
char buffer[BUFFER_SIZE];
struct sockaddr_in source_addr;
socklen_t addrlen = sizeof(source_addr);
// 创建原始套接字,用于接收IP数据包
sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_IP);
if (sockfd < 0) {
perror("Socket creation failed");
return -1;
}
// 设置套接字选项,使能接收所有IP数据包(包含目的地址不是本机的)
int option_value = 1;
if (setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &option_value, sizeof(option_value)) < 0) {
perror("Setsockopt failed");
close(sockfd);
return -1;
}
while (1) {
// 接收IP数据包
int packet_size = recvfrom(sockfd, buffer, BUFFER_SIZE, 0, (struct sockaddr *)&source_addr, &addrlen);
if (packet_size < 0) {
perror("Recvfrom failed");
continue;
}
// 调用函数转发接收到的IP数据包
forward_ip_packet(buffer, packet_size);
}
close(sockfd);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <sys/ioctl.h>
// 路由表结构体(这里简单模拟,实际路由器的路由表更复杂)
typedef struct RouteTableEntry {
struct in_addr network;
struct in_addr netmask;
struct in_addr gateway;
char interface_name[IF_NAMESIZE];
} RouteTableEntry;
// 模拟的路由表(这里只是简单示例,实际中会动态构建和更新)
RouteTableEntry route_table[] = {
{
.network = {.s_addr = inet_addr("192.168.1.0")},
.netmask = {.s_addr = inet_addr("255.255.255.0")},
.gateway = {.s_addr = inet_addr("192.168.1.254")},
.interface_name = "eth0"
},
{
.network = {.s_addr = inet_addr("10.0.0.0")},
.netmask = {.s_addr = inet_addr("255.0.0.0")},
.gateway = {.s_addr = inet_addr("10.0.0.1")},
.interface_name = "eth1"
}
};
#define ROUTE_TABLE_SIZE (sizeof(route_table) / sizeof(RouteTableEntry))
// 转发IP数据包函数
void forward_ip_packet(char* buffer, int packet_size) {
struct iphdr* ip_header = (struct iphdr*)buffer;
// 修改源IP地址为路由器IP地址
struct in_addr router_ip;
if (inet_aton("192.168.1.1", &router_ip) == 0) {
perror("Invalid router IP address");
return;
}
ip_header->saddr = router_ip.s_addr;
// 减少TTL值
ip_header->ttl--;
// 根据目的IP查找路由表确定出口接口和下一跳地址(简单模拟)
struct in_addr dst_ip = {.s_addr = ip_header->daddr};
int found_route = 0;
char* out_interface = NULL;
struct in_addr next_hop;
for (int i = 0; i < ROUTE_TABLE_SIZE; i++) {
// 进行网络地址匹配判断(通过与运算)
if ((dst_ip.s_addr & route_table[i].netmask.s_addr) == route_table[i].network.s_addr) {
found_route = 1;
out_interface = route_table[i].interface_name;
next_hop = route_table[i].gateway;
break;
}
}
if (!found_route) {
printf("No route found for destination IP\n");
return;
}
// 获取出口网卡的索引
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
perror("Socket creation failed");
return;
}
struct ifreq ifr;
memset(&ifr, 0, sizeof(ifr));
strcpy(ifr.ifr_name, out_interface);
if (ioctl(sockfd, SIOCGIFINDEX, &ifr) < 0) {
perror("IOCTL failed to get interface index");
close(sockfd);
return;
}
close(sockfd);
// 创建原始套接字用于发送数据包
sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
if (sockfd < 0) {
perror("Socket creation failed");
return;
}
// 设置套接字选项,指定从哪个网卡发送数据包
int option_value = ifr.ifr_ifindex;
if (setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, &option_value, sizeof(option_value)) < 0) {
perror("Setsockopt failed to bind to device");
close(sockfd);
return;
}
// 构建目的地址结构体
struct sockaddr_in dst_addr;
memset(&dst_addr, 0, sizeof(dst_addr));
dst_addr.sin_family = AF_INET;
dst_addr.sin_port = 0;
dst_addr.sin_addr = next_hop;
// 发送数据包
if (sendto(sockfd, buffer, packet_size, 0, (struct sockaddr *)&dst_addr, sizeof(dst_addr)) < 0) {
perror("Sendto failed");
}
close(sockfd);
}
在 linux下C C++的头文件和库文件所在目录
在Linux系统下,C/C++ 的头文件和库文件通常位于以下几个目录:
头文件:
/usr/include: 这是系统默认的 C/C++ 头文件目录,包含了 C/C++ 标准库以及其他常用库的头文件,例如 stdio.h, iostream, algorithm 等。
/usr/local/include: 这个目录用于存放用户本地安装的软件包的头文件,以便与系统自带的头文件区分开来。
/usr/include/<软件包名称>: 一些软件包会将自己的头文件安装到 /usr/include 目录下的子目录中,例如 /usr/include/python3.8 存放 Python 3.8 的头文件。
/opt/<软件包名称>/include: 一些大型软件包会将自己的头文件安装到 /opt 目录下的子目录中。
库文件:
/usr/lib: 这是系统默认的库文件目录,包含了 C/C++ 标准库以及其他常用库的动态链接库和静态链接库,例如 libc.so, libstdc++.a 等。
/usr/local/lib: 这个目录用于存放用户本地安装的软件包的库文件,以便与系统自带的库文件区分开来。
/usr/lib/<架构>: 一些库文件会根据不同的 CPU 架构存放在不同的目录下,例如 /usr/lib/x86_64-linux-gnu 存放 x86_64 架构的库文件。
/usr/lib/<软件包名称>: 一些软件包会将自己的库文件安装到 /usr/lib 目录下的子目录中。
/opt/<软件包名称>/lib: 一些大型软件包会将自己的库文件安装到 /opt 目录下的子目录中。
需要注意的是:
以上只是一些常见的目录,具体的目录结构可能因 Linux 发行版和软件包的安装方式而有所不同。
可以使用 locate 命令查找特定头文件或库文件的路径,例如 locate stdio.h 或 locate libstdc++.so。
在编译 C/C++ 程序时,可以使用 -I 参数指定头文件目录,使用 -L 参数指定库文件目录,使用 -l 参数指定链接的库文件
linux下用C操作常规蓝牙和低功耗蓝牙
常规蓝牙确保你的系统已经安装了BlueZ库和开发包:
sudo apt-get install libbluetooth-dev
然后,这里是一个基本的C程序示例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>
#include <bluetooth/l2cap.h>
#define BLE_ATT_CID 4
int main() {
int device_id = hci_get_route(NULL);
int device_handle = 0;
int sock = 0;
struct sockaddr_l2 addr = { 0 };
char dest[18] = "XX:XX:XX:XX:XX:XX"; // 替换为你的BLE设备地址
// 打开蓝牙设备
device_handle = hci_open_dev(device_id);
if (device_handle < 0) {
perror("Failed to open device");
exit(1);
}
// 创建L2CAP socket
sock = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
if (sock < 0) {
perror("Failed to create L2CAP socket");
exit(1);
}
// 设置目标地址
addr.l2_family = AF_BLUETOOTH;
addr.l2_cid = htobs(BLE_ATT_CID);
str2ba(dest, &addr.l2_bdaddr);
// 连接到BLE设备
if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("Failed to connect");
close(sock);
exit(1);
}
printf("Connected to %s\n", dest);
// 发送数据
char message[] = "Hello, BLE!";
if (write(sock, message, sizeof(message)) < 0) {
perror("Failed to send data");
} else {
printf("Sent: %s\n", message);
}
// 接收数据
char buffer[1024] = { 0 };
int bytes_read = read(sock, buffer, sizeof(buffer));
if (bytes_read > 0) {
printf("Received: %s\n", buffer);
} else if (bytes_read < 0) {
perror("Failed to receive data");
}
// 关闭连接
close(sock);
return 0;
}
编译和运行这个程序:
gcc -o ble_example ble_example.c -lbluetooth
sudo ./ble_example
注意事项:
你需要将 XX:XX:XX:XX:XX:XX 替换为实际的BLE设备地址。
这个例子使用的是L2CAP协议,这是BLE通信的底层协议。在实际应用中,你可能需要实现更高层的GATT协议。
这个程序需要root权限运行,因为它直接访问蓝牙硬件。
这只是一个基本的例子。实际的BLE通信可能涉及服务发现、特征读写等更复杂的操作。
错误处理在这个例子中很简单,在实际应用中你可能需要更健壮的错误处理机制。
这个例子假设BLE设备已经配对和连接。在实际应用中,你可能需要先进行设备扫描和配对过程。
要深入了解BLE编程,建议查阅BlueZ的官方文档和更多的示例代码。同时,考虑使用一些更高级的库,如 gattlib,它提供了更方便的 API 来处理 BLE 通信。
低功耗蓝牙确保安装了必要的开发库:
sudo apt-get install libglib2.0-dev libgio2.0-cil-dev
然后,这里是一个基本的C程序示例,展示如何扫描BLE设备、连接设备、并读取特征值:
#include <stdio.h>
#include <glib.h>
#include <gio/gio.h>
#define BLUEZ_BUS_NAME "org.bluez"
#define BLUEZ_INTERFACE_ADAPTER1 "org.bluez.Adapter1"
#define BLUEZ_INTERFACE_DEVICE1 "org.bluez.Device1"
#define BLUEZ_INTERFACE_GATTCHAR1 "org.bluez.GattCharacteristic1"
static GMainLoop *main_loop = NULL;
static void on_characteristic_read(GObject *source_object, GAsyncResult *res, gpointer user_data) {
GVariant *value;
GError *error = NULL;
value = g_dbus_proxy_call_finish(G_DBUS_PROXY(source_object), res, &error);
if (error != NULL) {
g_print("Error reading characteristic: %s\n", error->message);
g_error_free(error);
return;
}
GVariant *array;
g_variant_get(value, "(ay)", &array);
gsize n;
const guchar *data = g_variant_get_fixed_array(array, &n, sizeof(guchar));
g_print("Characteristic value: ");
for (gsize i = 0; i < n; i++) {
g_print("%02x ", data[i]);
}
g_print("\n");
g_variant_unref(value);
g_main_loop_quit(main_loop);
}
static void on_device_connected(GObject *source_object, GAsyncResult *res, gpointer user_data) {
GError *error = NULL;
GVariant *result;
result = g_dbus_proxy_call_finish(G_DBUS_PROXY(source_object), res, &error);
if (error != NULL) {
g_print("Error connecting to device: %s\n", error->message);
g_error_free(error);
return;
}
g_print("Connected to device\n");
// 这里应该实现服务和特征发现
// 为了简化,我们假设已知特征的路径
const gchar *char_path = "/org/bluez/hci0/dev_XX_XX_XX_XX_XX_XX/service000a/char000b";
GDBusProxy *char_proxy = g_dbus_proxy_new_for_bus_sync(
G_BUS_TYPE_SYSTEM,
G_DBUS_PROXY_FLAGS_NONE,
NULL,
BLUEZ_BUS_NAME,
char_path,
BLUEZ_INTERFACE_GATTCHAR1,
NULL,
&error
);
if (error != NULL) {
g_print("Error creating characteristic proxy: %s\n", error->message);
g_error_free(error);
return;
}
g_dbus_proxy_call(
char_proxy,
"ReadValue",
g_variant_new("(a{sv})", NULL),
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
on_characteristic_read,
NULL
);
g_object_unref(char_proxy);
g_variant_unref(result);
}
static void on_device_found(GDBusProxy *proxy, GVariant *changed_properties, GStrv invalidated_properties, gpointer user_data) {
GVariantIter *iter;
const gchar *key;
GVariant *value;
g_variant_get(changed_properties, "a{sv}", &iter);
while (g_variant_iter_loop(iter, "{&sv}", &key, &value)) {
if (g_strcmp0(key, "Address") == 0) {
const gchar *address = g_variant_get_string(value, NULL);
g_print("Found device: %s\n", address);
// 连接到设备
g_dbus_proxy_call(
proxy,
"Connect",
NULL,
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
on_device_connected,
NULL
);
break;
}
}
g_variant_iter_free(iter);
}
int main(void) {
GError *error = NULL;
GDBusProxy *adapter_proxy;
main_loop = g_main_loop_new(NULL, FALSE);
adapter_proxy = g_dbus_proxy_new_for_bus_sync(
G_BUS_TYPE_SYSTEM,
G_DBUS_PROXY_FLAGS_NONE,
NULL,
BLUEZ_BUS_NAME,
"/org/bluez/hci0",
BLUEZ_INTERFACE_ADAPTER1,
NULL,
&error
);
if (error != NULL) {
g_print("Error creating adapter proxy: %s\n", error->message);
g_error_free(error);
return 1;
}
g_signal_connect(adapter_proxy, "g-properties-changed", G_CALLBACK(on_device_found), NULL);
// 开始扫描
g_dbus_proxy_call_sync(
adapter_proxy,
"StartDiscovery",
NULL,
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
&error
);
if (error != NULL) {
g_print("Error starting discovery: %s\n", error->message);
g_error_free(error);
return 1;
}
g_main_loop_run(main_loop);
g_object_unref(adapter_proxy);
g_main_loop_unref(main_loop);
return 0;
}
编译和运行这个程序:
gcc -o ble_example ble_example.c `pkg-config --cflags --libs glib-2.0 gio-2.0` -I/usr/include/dbus-1.0 -I/usr/lib/x86_64-linux-gnu/dbus-1.0/include
sudo ./ble_example
注意事项:
这个例子展示了如何扫描BLE设备、连接到设备、并读取一个特征值。你需要替换 XX_XX_XX_XX_XX_XX 为实际的设备地址,并修改特征路径以匹配你的设备。
这个程序需要root权限运行,因为它需要访问系统的D-Bus。
这只是一个基本的例子。实际的BLE应用可能需要更复杂的服务和特征发现过程。
错误处理在这个例子中很简单,在实际应用中你可能需要更健壮的错误处理机制。
这个例子使用了同步和异步的D-Bus调用。在实际应用中,你可能需要更多地使用异步调用以提高性能。
为了简化,这个例子没有实现写入特征或订阅通知的功能。这些功能可以用类似的方式实现。
要深入了解Linux下的BLE编程,建议查阅BlueZ的官方文档和D-Bus规范。同时,考虑使用一些更高级的库,如 bluez-dbus,它提供了更方便的API来处理BLE通信。
如何在Linux系统中使用原始套接字进行网络嗅探
基本原理
在 Linux 系统中,原始套接字可以绕过传输层协议(如 TCP 和 UDP),直接访问网络层协议(如 IP)。这使得可以捕获流经网络接口的所有 IP 数据包,包括 ICMP、TCP 和 UDP 等不同协议类型的数据包。通过对这些数据包进行分析,可以获取源 IP 地址、目的 IP 地址、端口号、协议类型等信息。
创建原始套接字
以下是一个简单的示例代码,用于创建一个原始套接字来捕获 IP 数据包。首先需要包含必要的头文件:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <errno.h>
然后创建原始套接字:
int sock_raw = socket(AF_INET, SOCK_RAW, IPPROTO_TCP);
if (sock_raw < 0) {
perror("socket");
return 1;
}
这里使用socket()函数创建一个原始套接字。参数AF_INET表示使用 IPv4 协议族,SOCK_RAW表示创建原始套接字,IPPROTO_TCP指定捕获的协议为 TCP。你也可以将其改为IPPROTO_UDP来捕获 UDP 协议的数据包或者IPPROTO_ICMP来捕获 ICMP 数据包。
设置套接字选项(可选)
为了能够正确捕获数据包,可能需要设置一些套接字选项。例如,设置IP_HDRINCL选项,它允许应用程序自己构建 IP 头:
int value = 1;
if (setsockopt(sock_raw, IPPROTO_IP, IP_HDRINCL, &value, sizeof(value)) < 0) {
perror("setsockopt");
return 1;
}
接收和分析数据包
可以使用recvfrom()函数来接收数据包:
struct sockaddr_in source, dest;
socklen_t saddrlen = sizeof(source);
socklen_t daddrlen = sizeof(dest);
char buffer[65536];
int data_size = recvfrom(sock_raw, buffer, 65536, 0, (struct sockaddr *)&source, &saddrlen);
if (data_size < 0) {
perror("recvfrom");
return 1;
}
接收到数据包后,可以对数据包进行分析。例如,对于 IP 数据包,可以通过解析 IP 头来获取源 IP 地址和目的 IP 地址:
struct iphdr *iph = (struct iphdr *)buffer;
unsigned short iphdrlen = iph->ihl*4;
struct in_addr source_ip = source.sin_addr;
struct in_addr dest_ip = dest.sin_addr;
printf("Source IP: %s\n", inet_ntoa(source_ip));
printf("Destination IP: %s\n", inet_ntoa(dest_ip));
这里iphdrlen表示 IP 头的长度,inet_ntoa()函数用于将网络字节序的 IP 地址转换为点分十进制的字符串格式,以便于打印和查看。
关闭套接字
当完成网络嗅探后,需要关闭原始套接字,以释放资源:
close(sock_raw);
需要注意的是,在实际使用中,未经授权的网络嗅探可能涉及到隐私和安全问题。在自己的测试环境或者在合法授权的情况下(如网络安全监测)使用上述代码进行网络嗅探。同时,这只是一个简单的示例,更复杂的网络嗅探工具可能还需要处理数据包的校验和、协议解析等更多细节。