您现在的位置是:网站首页> C/C++

Linux C编程笔记

  • C/C++
  • 2024-12-15
  • 398人已阅读
摘要

Linux C编程笔记


CMakeLists.txt简介

创建原始套接字转发数据

在 linux下C C++的头文件和库文件所在目录

linux下用C操作常规蓝牙和低功耗蓝牙

如何在Linux系统中使用原始套接字进行网络嗅探

如何在windows系统中使用原始套接字进行网络嗅探



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);


需要注意的是,在实际使用中,未经授权的网络嗅探可能涉及到隐私和安全问题。在自己的测试环境或者在合法授权的情况下(如网络安全监测)使用上述代码进行网络嗅探。同时,这只是一个简单的示例,更复杂的网络嗅探工具可能还需要处理数据包的校验和、协议解析等更多细节。
















上一篇:QT视频教程

下一篇:QT优秀的开源库

Top