Keylogger 和 mousetracker:我应该使用非阻塞 I/O 吗?

Keylogger and mousetracker: should I use non-blocking I/O?

我正在为 Windows 在 C/C++ 中编写一个简单的 keylogger/mouselogger。为此,我使用了 Win32 函数 LowLevelMouseProcLowLevelKeyboardProc.

如果相关,这里是 GitHub gist 我的代码,它是超基本的:定义事件回调并将其与 SIGINT 回调一起注册。我会在问题的最后添加一个摘要版本。

我的问题如下:为了尽量减少开销,我应该如何将这些事件保存到磁盘?

欢迎使用 C 或 C++ 回答。

每次收到新事件时简单地写入缓冲文件并在缓冲区已满时让文件处理刷新是否是一种好习惯?我听说过非阻塞 I/O 但微软的文档说有额外的开销。最后,我不确定是否应该为此创建第二个线程。

我想使用某种缓冲来避免许多小磁盘 I/O。理想情况下,我会在我的进程被终止之前写入磁盘一次。但是我不知道如何实现这个。

代码:

#include "pch.h"
#include <stdio.h>
#include <Windows.h>

HHOOK handle;
LRESULT CALLBACK lowLevelMouseProc(
    _In_ int    nCode,
    _In_ WPARAM wParam,
    _In_ LPARAM lParam
)
{
    MSLLHOOKSTRUCT* lp = (MSLLHOOKSTRUCT*)lParam;
    if (wParam == WM_MOUSEMOVE) {
        // Best way to save pt.x and pt.y to disk?
        printf("%d %d \n", lp->pt.x, lp->pt.y);
    }
    return CallNextHookEx(0, nCode, wParam, lParam);
}

int main()
{
    handle = SetWindowsHookExA(WH_MOUSE_LL, &lowLevelMouseProc, NULL, 0);
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0));
    UnhookWindowsHookEx(handle)
    return 0;
}

使用 2 个缓冲区。一种用于写入,一种用于读取(刷新到磁盘)。一旦满足某些条件(缓冲区已满,程序关闭,...),交换缓冲区并开始在单独的线程中刷新到磁盘。

这可能类似于:

#include <Windows.h>
#include <vector>
#include <thread>
#include <fstream>
#include <atomic>

struct Point
{
    long x, y;
};

class Buffer
{
public:
    Buffer(std::string _file = "log.txt", const size_t _buffer_size = 100000) : buffer_size(_buffer_size), file(_file)
    {
        points1.reserve(_buffer_size);
        points2.reserve(_buffer_size);
    }

    void write(Point p)
    {
        buf->push_back(p);
        if (buf->size() >= buffer_size && !thread_running.load())
            to_disk();
    }

private:
    const size_t buffer_size;
    const std::string file;
    std::atomic<bool> thread_running{ false };
    std::vector<Point> points1, points2;
    std::vector<Point> *buf = &points1, *other = &points2;


    void swap_buffer()
    {
        std::swap(buf, other);
    }

    void to_disk()
    {
        swap_buffer();
        auto tmp_buf = other;
        auto tmp_file = file;
        auto tmp_flag = &thread_running;
        auto fn = [tmp_buf, tmp_file, tmp_flag]() {
            tmp_flag->store(true);
            std::fstream f(tmp_file, std::ios::app);
            for (auto &v : *tmp_buf)
                f << v.x << ' ' << v.y << '\n';
            tmp_buf->clear();
            tmp_flag->store(false);
        };
        std::thread t(fn);
        t.detach();
    }
};
Buffer buffer("log.txt");


HHOOK handle;
LRESULT CALLBACK lowLevelMouseProc(
    _In_ int    nCode,
    _In_ WPARAM wParam,
    _In_ LPARAM lParam
)
{
    MSLLHOOKSTRUCT* lp = (MSLLHOOKSTRUCT*)lParam;
    if (wParam == WM_MOUSEMOVE) {
        buffer.write({ lp->pt.x, lp->pt.y });
    }
    return CallNextHookEx(0, nCode, wParam, lParam);
}

int main()
{
    handle = SetWindowsHookExA(WH_MOUSE_LL, &lowLevelMouseProc, NULL, 0);
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0));
    UnhookWindowsHookEx(handle);
    return 0;
}

在这种情况下,缓冲区会在达到特定大小限制时写入磁盘。这可以进一步优化,例如不检查每次写入的大小。

注意:在此示例中,省略了错误处理,并且应相应地管理内部缓冲区的生命周期。