Всем привет! Я являюсь создателем распределённого поисковика rats-search на базе DHT (GitHub). Его принцип работы довольно прост: поисковик собирает торренты у всех участников сети и формирует большую распределённую базу для поиска, включая метаданные (например, описания и прочую информацию).

В этой статье я хочу рассказать о своей новой библиотеке для построения распределённых приложений (p2p), где знание IP-адресов участников не обязательно, а поиск ведётся через различные протоколы — DHT, mDNS, peer-exchange и другие. Думаю, с учётом постоянных неприятностей, которые происходят вокруг, это может оказаться полезным ;).

Немного истории

rats-search — проект довольно старый, я написал его много лет назад на Electron. Сейчас появилась необходимость переписать p2p-составляющую на C++ для улучшения качества связи и производительности.

Для начала я ради интереса попробовал перенести всё на libp2p и был неприятно удивлён его производительностью. Буквально при ~100 пирах процессор (Ryzen 5700) был загружен на 100% в момент шагов по DHT сети, а потребление памяти достигало 500–600 МБ. Конечно, я понимаю, что libp2p использует модные корутинные библиотеки типо it- и тянет за собой 300–400 МБ зависимостей в node_modules, но это же не дело.

Да, это JS, и сравнивать его напрямую с C++ некорректно, но даже JS-приложения обычно не дают таких плохих результатов. Более того, libp2p поддерживает не все языки: какие-то реализации урезаны, какие-то отсутствуют вовсе. Например, в версии для C++ протокол mDNS просто не реализован. В итоге эталонными считаются только реализации для Go и JS, и они заметно отличаются друг от друга.

Реализации на всех языках у libp2p это полный разнобой, повезло больше всего go и javascript
Реализации на всех языках у libp2p это полный разнобой, повезло больше всего go и javascript

Почему я решил написать свою библиотеку

Я решил отказаться от этой «комбайн-реализации» и написать свою собственную. В качестве основы был выбран эффективный низкоуровневый язык, а к нему уже можно строить биндинги на других языках. Это позволяет получить универсальную и при этом действительно эффективную единую реализацию, которую можно использовать и в других проектах.

Так появилась librats — новая библиотека на C++, которая станет будущим ядром Rats, а также может применяться и в любых других распределённых приложениях.

Что уже реализовано

  • Поиск участников через DHT

  • Коммуникация между участниками

  • Обмен данными / файлами / директориями

  • Протокол mDNS для поиска участников в локальных сетях

  • Historical peers — база данных участников для повторных подключений

  • Протокол gossipsub — для построения обмена сообщениями в больших распределённых сетях

  • Обмен пирами (peer exchange)

  • Поддержка пользовательских (кастомных) протоколов

  • ICE / STUN протоколы

  • Шифрование на основе протокола noise (Curve25519 + ChaCha20-Poly1305)

Поддерживаемые платформы и языки

  • Сборка под Windows, macOS, Linux, Android

  • Компиляторы: GCC, Clang, MSVC

  • Использование: напрямую в C++, а также в C и Java через JNI; в Android — через NDK

Примеры

Теперь давайте рассмотрим технические примеры и решения некоторых задач.

Базовый пример: создание клиента

#include "librats.h"
#include <iostream>
#include <thread>
#include <chrono>

int main() {
    // Создаём простой P2P клиент
    librats::RatsClient client(8080);
    
    // Настройка колбэка при подключении
    client.set_connection_callback([](socket_t socket, const std::string& peer_id) {
        std::cout << "✅ Новый пир подключился: " << peer_id << std::endl;
    });
    
    // Настройка колбэка для сообщений
    client.set_string_data_callback([](socket_t socket, const std::string& peer_id, const std::string& message) {
        std::cout << "? Сообщение от " << peer_id << ": " << message << std::endl;
    });
    
    // Запускаем клиент
    if (!client.start()) {
        std::cerr << "Не удалось запустить клиент" << std::endl;
        return 1;
    }
    
    std::cout << "? librats клиент запущен на порту 8080" << std::endl;
    
    // Подключение к другому пиру (необязательно)
    // client.connect_to_peer("127.0.0.1", 8081);
    
    // Отправляем сообщение всем подключенным пирам
    client.broadcast_string_to_peers("Hello from librats!");
    
    // Оставляем программу работать
    std::this_thread::sleep_for(std::chrono::minutes(1));
    
    return 0;
}

В основе работы лежит передача трёх типов данных:

  • бинарных,

  • текстовых,

  • JSON-данных.

Можно выбирать слушателей и настраивать тип передачи данных в зависимости от задачи.

Определение собственного протокола

Можно установить собственный протокол. Это позволяет автоматически находить только тех участников, которые работают именно с вашим протоколом или приложением.

#include "librats.h"
#include <iostream>

int main() {
    librats::RatsClient client(8080);
    
    // Настройка пользовательского протокола для вашего приложения
    client.set_protocol_name("my_app");
    client.set_protocol_version("1.0");
    
    std::cout << "Protocol: " << client.get_protocol_name() 
              << " v" << client.get_protocol_version() << std::endl;
    std::cout << "Discovery hash: " << client.get_discovery_hash() << std::endl;
    
    client.start();
    
    // Запуск DHT-обнаружения с пользовательским протоколом
    if (client.start_dht_discovery()) {
        // Объявляем о своём присутствии
        client.announce_for_hash(client.get_discovery_hash());
        
        // Поиск других пиров, использующих тот же протокол
        client.find_peers_by_hash(client.get_discovery_hash(), 
            [](const std::vector<std::string>& peers) {
                std::cout << "Found " << peers.size() << " peers" << std::endl;
            });
    }
    
    return 0;
}

Построение простого чата

Довольно типичная задача — организация чата. Для начала можно использовать простые функции, без применения mesh-сети.

#include "librats.h"
#include <iostream>
#include <string>

int main() {
    librats::RatsClient client(8080);
    
    // Настройка обработчиков сообщений с использованием современного API
    client.on("chat", [](const std::string& peer_id, const nlohmann::json& data) {
        std::cout << "[CHAT] " << peer_id << ": " << data["message"].get<std::string>() << std::endl;
    });
    
    client.on("user_join", [](const std::string& peer_id, const nlohmann::json& data) {
        std::cout << "[JOIN] " << data["username"].get<std::string>() << " присоединился" << std::endl;
    });
    
    // Колбэк при подключении
    client.set_connection_callback([&](socket_t socket, const std::string& peer_id) {
        std::cout << "✅ Пир подключился: " << peer_id << std::endl;
        
        // Отправляем приветственное сообщение
        nlohmann::json welcome;
        welcome["username"] = "User_" + client.get_our_peer_id().substr(0, 8);
        client.send("user_join", welcome);
    });
    
    client.start();
    
    // Отправляем чат-сообщение
    nlohmann::json chat_msg;
    chat_msg["message"] = "Hello, P2P chat!";
    chat_msg["timestamp"] = std::time(nullptr);
    client.send("chat", chat_msg);
    
    return 0;
}

Работа с mesh-сетями

Если требуется построить более крупную сеть (например, для обмена сообщениями между большим количеством узлов), можно использовать протокол gossipsub.

#include "librats.h"
#include <iostream>

int main() {
    librats::RatsClient client(8080);
    
    // Настройка обработчиков сообщений для топиков
    client.on_topic_message("news", [](const std::string& peer_id, const std::string& topic, const std::string& message) {
        std::cout << "? [" << topic << "] " << peer_id << ": " << message << std::endl;
    });
    
    client.on_topic_json_message("events", [](const std::string& peer_id, const std::string& topic, const nlohmann::json& data) {
        std::cout << "? [" << topic << "] Событие: " << data["type"].get<std::string>() << std::endl;
    });
    
    // Уведомления о присоединении/выходе пиров
    client.on_topic_peer_joined("news", [](const std::string& peer_id, const std::string& topic) {
        std::cout << "➕ " << peer_id << " присоединился к " << topic << std::endl;
    });
    
    client.start();
    client.start_dht_discovery();
    
    // Подписка на топики
    client.subscribe_to_topic("news");
    client.subscribe_to_topic("events");
    
    // Публикация сообщений
    client.publish_to_topic("news", "Breaking: librats is awesome!");
    
    nlohmann::json event;
    event["type"] = "celebration";
    event["reason"] = "successful_connection";
    client.publish_json_to_topic("events", event);
    
    std::cout << "? Пиров в 'news': " << client.get_topic_peers("news").size() << std::endl;
    
    return 0;
}

Обмен данными

Библиотека поддерживает обмен данными, включая файлы и целые директории.

#include "librats.h"
#include <iostream>

int main() {
    librats::RatsClient client(8080);
    
    // Настройка колбэков передачи файлов
    client.on_file_transfer_progress([](const librats::FileTransferProgress& progress) {
        std::cout << "? Передача " << progress.transfer_id.substr(0, 8) 
                  << ": " << progress.get_completion_percentage() << "% завершено"
                  << " (" << (progress.transfer_rate_bps / 1024) << " KB/s)" << std::endl;
    });
    
    client.on_file_transfer_completed([](const std::string& transfer_id, bool success, const std::string& error) {
        if (success) {
            std::cout << "✅ Передача завершена: " << transfer_id.substr(0, 8) << std::endl;
        } else {
            std::cout << "❌ Ошибка передачи: " << error << std::endl;
        }
    });
    
    // Автоматическое принятие входящих файловых передач
    client.on_file_transfer_request([](const std::string& peer_id, 
                                      const librats::FileMetadata& metadata, 
                                      const std::string& transfer_id) {
        std::cout << "? Входящий файл: " << metadata.filename 
                  << " (" << metadata.file_size << " байт) от " << peer_id.substr(0, 8) << std::endl;
        return true; // Автопринятие
    });
    
    // Разрешаем запросы файлов из директории "shared"
    client.on_file_request([](const std::string& peer_id, const std::string& file_path, const std::string& transfer_id) {
        std::cout << "? Запрос: " << file_path << " от " << peer_id.substr(0, 8) << std::endl;
        return file_path.find("../") == std::string::npos; // Защита от выхода за пределы пути
    });
    
    client.start();
    
    // Настройка параметров передачи
    librats::FileTransferConfig config;
    config.chunk_size = 64 * 1024;       // чанки по 64KB
    config.max_concurrent_chunks = 4;    // 4 параллельных чанка
    config.verify_checksums = true;      // проверка целостности
    client.set_file_transfer_config(config);
    
    // Примеры передач (замените "peer_id" на реальный ID пира)
    // std::string file_transfer = client.send_file("peer_id", "my_file.txt");
    // std::string dir_transfer = client.send_directory("peer_id", "./my_folder");
    // std::string file_request = client.request_file("peer_id", "remote_file.txt", "./downloaded_file.txt");
    
    std::cout << "Файловая передача готова. Подключите пиров и обменивайтесь файлами!" << std::endl;
    
    return 0;
}

Настройка конфигурации

Например, можно задать конфигурацию для логирования:

#include "librats.h"
#include <iostream>

int main() {
    librats::RatsClient client(8080);
    
    // Включение и настройка логирования
    client.set_logging_enabled(true);
    client.set_log_file_path("librats_app.log");
    client.set_log_level("INFO");  // DEBUG, INFO, WARN, ERROR
    client.set_log_colors_enabled(true);
    client.set_log_timestamps_enabled(true);
    
    // Настройка ротации лог-файлов
    client.set_log_rotation_size(5 * 1024 * 1024);  // максимальный размер файла 5MB
    client.set_log_retention_count(3);               // хранить 3 старых лог-файла
    
    std::cout << "? Логирование в: " << client.get_log_file_path() << std::endl;
    std::cout << "? Уровень логирования: " << static_cast<int>(client.get_log_level()) << std::endl;
    std::cout << "? Цветное логирование: " << (client.is_log_colors_enabled() ? "Да" : "Нет") << std::endl;
    
    client.start();
    
    // Все операции librats теперь будут логироваться
    client.broadcast_string_to_peers("Это действие будет зафиксировано в логах!");
    
    // Очистка лог-файла при необходимости (раскомментируйте для использования)
    // client.clear_log_file();
    
    return 0;
}

И можно поработать с конфигурацией сохранения данных:

#include "librats.h"
#include <iostream>

int main() {
    librats::RatsClient client(8080);
    
    // Set custom data directory for config files
    client.set_data_directory("./my_app_data");
    
    // Load saved configuration (if exists)
    if (client.load_configuration()) {
        std::cout << "? Loaded existing configuration" << std::endl;
    } else {
        std::cout << "? Using default configuration" << std::endl;
    }
    
    // Get our persistent peer ID
    std::cout << "? Our peer ID: " << client.get_our_peer_id() << std::endl;
    
    client.start();
    
    // Try to reconnect to previously connected peers
    int reconnect_attempts = client.load_and_reconnect_peers();
    std::cout << "? Attempted to reconnect to " << reconnect_attempts << " previous peers" << std::endl;
    
    // Configuration is automatically saved when client stops
    // Files created: config.json, peers.rats, peers_ever.rats
    
    // Manual save if needed
    client.save_configuration();
    client.save_historical_peers();
    
    std::cout << "? Configuration will be saved to: " << client.get_data_directory() << std::endl;
    
    return 0;
}

В коде предусмотрено сохранение не только конфигурации, но и истории подключений. Это полезно для будущих запусков: если нужно быстро восстановить соединения, можно воспользоваться сохранёнными данными.

Представьте ситуацию: «птица залетела» в сервера местного законопослушного провайдера и часть маршрутов или соединений внезапно оказалась заблокирована. В таком случае наличие базы известных пиров позволяет быстро восстановить работу сети без необходимости заново искать всех участников.

Ну и напоследок, раз заявлены другие языки, пример на Java на Android. Я решил привести пример Activity целиком, фокусируем внимание на setupRatsClient():

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "LibRatsExample";
    private static final int PERMISSION_REQUEST_CODE = 1;
    
    private RatsClient ratsClient;
    private TextView statusText;
    private TextView messagesText;
    private EditText hostInput;
    private EditText portInput;
    private EditText messageInput;
    private Button startButton;
    private Button connectButton;
    private Button sendButton;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        initViews();
        checkPermissions();
        setupRatsClient();
    }
    
    private void initViews() {
        statusText = findViewById(R.id.statusText);
        messagesText = findViewById(R.id.messagesText);
        hostInput = findViewById(R.id.hostInput);
        portInput = findViewById(R.id.portInput);
        messageInput = findViewById(R.id.messageInput);
        startButton = findViewById(R.id.startButton);
        connectButton = findViewById(R.id.connectButton);
        sendButton = findViewById(R.id.sendButton);
        
        startButton.setOnClickListener(this::onStartClicked);
        connectButton.setOnClickListener(this::onConnectClicked);
        sendButton.setOnClickListener(this::onSendClicked);
        
        // Устанавливаем значения по умолчанию
        hostInput.setText("192.168.1.100");
        portInput.setText("8080");
        messageInput.setText("Hello from Android!");
        
        updateUI();
    }
    
    private void checkPermissions() {
        String[] permissions = {
            Manifest.permission.INTERNET,
            Manifest.permission.ACCESS_NETWORK_STATE,
            Manifest.permission.ACCESS_WIFI_STATE,
            Manifest.permission.CHANGE_WIFI_MULTICAST_STATE
        };
        
        boolean allGranted = true;
        for (String permission : permissions) {
            if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
                allGranted = false;
                break;
            }
        }
        
        if (!allGranted) {
            ActivityCompat.requestPermissions(this, permissions, PERMISSION_REQUEST_CODE);
        }
    }
    
    private void setupRatsClient() {
        try {
            // Включаем логирование
            RatsClient.setLoggingEnabled(true);
            RatsClient.setLogLevel("INFO");
            
            // Создаём клиент на порту 8080
            ratsClient = new RatsClient(8080);
            
            // Настраиваем колбэки
            ratsClient.setConnectionCallback(new ConnectionCallback() {
                @Override
                public void onConnection(String peerId) {
                    runOnUiThread(() -> {
                        appendMessage("Подключились к пиру: " + peerId);
                        updateUI();
                    });
                }
            });
            
            ratsClient.setStringCallback(new StringMessageCallback() {
                @Override
                public void onStringMessage(String peerId, String message) {
                    runOnUiThread(() -> {
                        appendMessage("Сообщение от " + peerId + ": " + message);
                    });
                }
            });
            
            ratsClient.setDisconnectCallback(new DisconnectCallback() {
                @Override
                public void onDisconnect(String peerId) {
                    runOnUiThread(() -> {
                        appendMessage("Отключились от пира: " + peerId);
                        updateUI();
                    });
                }
            });
            
            appendMessage("Клиент LibRats успешно создан");
            appendMessage("Версия: " + RatsClient.getVersionString());
            
        } catch (Exception e) {
            Log.e(TAG, "Не удалось создать RatsClient", e);
            appendMessage("Ошибка: " + e.getMessage());
        }
    }
    
    private void onStartClicked(View view) {
        if (ratsClient == null) return;
        
        try {
            int result = ratsClient.start();
            if (result == RatsClient.SUCCESS) {
                appendMessage("Клиент успешно запущен");
                appendMessage("Наш Peer ID: " + ratsClient.getOurPeerId());
                updateUI();
            } else {
                appendMessage("Не удалось запустить клиент: " + result);
            }
        } catch (Exception e) {
            Log.e(TAG, "Ошибка при запуске клиента", e);
            appendMessage("Ошибка при запуске клиента: " + e.getMessage());
        }
    }
    
    private void onConnectClicked(View view) {
        if (ratsClient == null) return;
        
        String host = hostInput.getText().toString().trim();
        String portStr = portInput.getText().toString().trim();
        
        if (host.isEmpty() || portStr.isEmpty()) {
            Toast.makeText(this, "Введите хост и порт", Toast.LENGTH_SHORT).show();
            return;
        }
        
        try {
            int port = Integer.parseInt(portStr);
            int result = ratsClient.connectWithStrategy(host, port, RatsClient.STRATEGY_AUTO_ADAPTIVE);
            
            if (result == RatsClient.SUCCESS) {
                appendMessage("Подключение к " + host + ":" + port);
            } else {
                appendMessage("Не удалось подключиться: " + result);
            }
        } catch (NumberFormatException e) {
            Toast.makeText(this, "Неверный номер порта", Toast.LENGTH_SHORT).show();
        } catch (Exception e) {
            Log.e(TAG, "Ошибка подключения", e);
            appendMessage("Ошибка подключения: " + e.getMessage());
        }
    }
    
    private void onSendClicked(View view) {
        if (ratsClient == null) return;
        
        String message = messageInput.getText().toString().trim();
        if (message.isEmpty()) {
            Toast.makeText(this, "Введите сообщение", Toast.LENGTH_SHORT).show();
            return;
        }
        
        try {
            // Получаем подключённых пиров
            String[] peerIds = ratsClient.getPeerIds();
            if (peerIds.length == 0) {
                Toast.makeText(this, "Нет подключённых пиров", Toast.LENGTH_SHORT).show();
                return;
            }
            
            // Отправляем первому подключённому пиру
            int result = ratsClient.sendString(peerIds[0], message);
            if (result == RatsClient.SUCCESS) {
                appendMessage("Отправлено: " + message);
                messageInput.setText("");
            } else {
                appendMessage("Не удалось отправить сообщение: " + result);
            }
        } catch (Exception e) {
            Log.e(TAG, "Ошибка при отправке сообщения", e);
            appendMessage("Ошибка при отправке сообщения: " + e.getMessage());
        }
    }
    
    private void appendMessage(String message) {
        Log.d(TAG, message);
        messagesText.append(message + "\n");
        
        // Прокрутка вниз
        messagesText.post(() -> {
            int scrollAmount = messagesText.getLayout().getLineTop(messagesText.getLineCount()) 
                              - messagesText.getHeight();
            if (scrollAmount > 0) {
                messagesText.scrollTo(0, scrollAmount);
            } else {
                messagesText.scrollTo(0, 0);
            }
        });
    }
    
    private void updateUI() {
        if (ratsClient == null) {
            statusText.setText("Статус: Не инициализирован");
            startButton.setEnabled(false);
            connectButton.setEnabled(false);
            sendButton.setEnabled(false);
            return;
        }
        
        try {
            int peerCount = ratsClient.getPeerCount();
            statusText.setText("Статус: " + peerCount + " пиров подключено");
            
            // Включение/отключение кнопок в зависимости от состояния
            startButton.setEnabled(true);
            connectButton.setEnabled(true);
            sendButton.setEnabled(peerCount > 0);
            
        } catch (Exception e) {
            statusText.setText("Статус: Ошибка");
            Log.e(TAG, "Ошибка обновления UI", e);
        }
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (ratsClient != null) {
            try {
                ratsClient.stop();
                ratsClient.destroy();
            } catch (Exception e) {
                Log.e(TAG, "Ошибка при уничтожении клиента", e);
            }
        }
    }
    
    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == PERMISSION_REQUEST_CODE) {
            boolean allGranted = true;
            for (int result : grantResults) {
                if (result != PackageManager.PERMISSION_GRANTED) {
                    allGranted = false;
                    break;
                }
            }
            
            if (!allGranted) {
                Toast.makeText(this, "Для LibRats необходимы сетевые разрешения", Toast.LENGTH_LONG).show();
            }
        }
    }
}

Использование из C в виде библиотеки

На C доступны основные функции внутри библиотеки, их можно посмотреть:

https://github.com/DEgITx/librats/blob/master/src/librats_c.h - весь список, аналог вызовов из C++

пример использования когда на C:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>

#include "librats_c.h"

// Глобальный дескриптор клиента
static rats_client_t client = NULL;
static volatile int running = 1;

// Функции-колбэки
void on_peer_connected(void* user_data, const char* peer_id) {
    printf("Пир подключился: %s\n", peer_id);
}

void on_peer_disconnected(void* user_data, const char* peer_id) {
    printf("Пир отключился: %s\n", peer_id);
}

void on_string_message(void* user_data, const char* peer_id, const char* message) {
    printf("Сообщение от %s: %s\n", peer_id, message);
}

void signal_handler(int sig) {
    printf("Завершение работы...\n");
    running = 0;
}

int main(int argc, char* argv[]) {
    printf("Пример\n");
    printf("========================\n\n");
    
    // Установка обработчика сигнала
    signal(SIGINT, signal_handler);
    
    // Создаём клиента на порту 8080
    client = rats_create(8080);
    if (!client) {
        fprintf(stderr, "Не удалось создать клиента!\n");
        return 1;
    }
    
    // Настраиваем колбэки
    rats_set_connection_callback(client, on_peer_connected, NULL);
    rats_set_disconnect_callback(client, on_peer_disconnected, NULL);
    rats_set_string_callback(client, on_string_message, NULL);
    
    // Запускаем клиента
    if (rats_start(client) != RATS_SUCCESS) {
        fprintf(stderr, "Не удалось запустить клиента!\n");
        rats_destroy(client);
        return 1;
    }
    
    // Выводим наш Peer ID
    char* peer_id = rats_get_our_peer_id(client);
    if (peer_id) {
        printf("Наш Peer ID: %s\n", peer_id);
        rats_string_free(peer_id);
    }
    
    printf("Клиент запущен на порту 8080\n");
    printf("Нажмите Ctrl+C для остановки\n\n");
    
    // Если переданы аргументы командной строки — пробуем подключиться
    if (argc >= 3) {
        const char* host = argv[1];
        int port = atoi(argv[2]);
        printf("Подключение к %s:%d...\n", host, port);
        rats_connect(client, host, port);
    }
    
    // Главный цикл
    int counter = 0;
    while (running) {
        sleep(1);
        counter++;
        
        // Каждые 5 секунд отправляем сообщение
        if (counter % 5 == 0) {
            int peer_count = rats_get_peer_count(client);
            if (peer_count > 0) {
                char message[100];
                snprintf(message, sizeof(message), "Здарова: %d", counter / 5);
                
                int sent = rats_broadcast_string(client, message);
                printf("Широковещательная отправка %d пирам: %s\n", sent, message);
            } else {
                printf("Нет подключённых пиров. Запустите другой экземпляр командой:\n");
                printf("  %s localhost 8080\n", argv[0]);
            }
        }
    }
    
    // Очистка
    printf("Остановка клиента...\n");
    rats_stop(client);
    rats_destroy(client);
    
    printf("Баюшки!\n");
    return 0;
}

Заключение

Ну и напоследок — о производительности. Помните те 500–600 МБ памяти при работе p2p через libp2p? Давайте посмотрим, что получается с librats:

…не правда ли, результат явно лучше?

Надеюсь, эта библиотека окажется полезной. Спасибо за внимание! Возможно, я расширю статью по мере добавления биндингов на другие языки и реализации новых функций.

Комментарии (10)


  1. whocoulditbe
    03.09.2025 16:06

    Ссылка на проект с utm-меткой _source=chatgpt.com". В связи с этим сразу вопрос - сколько кода было написано LLM, а сколько человеком?


    1. DEgITx Автор
      03.09.2025 16:06

      Юзалось для спелчекера и оформления)

      По поводу кода, использовалось вспомогательно, почему бы и нет? Много Валидации, документации, юнит тестов и всего прочего.


    1. kale
      03.09.2025 16:06

      А какое это имеет значение?


      1. Kenya-West
        03.09.2025 16:06

        "Ты абсолютно прав! В данный момент проект имеет недостаток - он не имеет значения, написан он ChatGPT или нет. Сейчас перепишу весь проект, чтобы он имел значение"


  1. ahdenchik
    03.09.2025 16:06

    API нужно делать на C, иначе кроме как из C++ вызвать это будет ни откуда нельзя


    1. DEgITx Автор
      03.09.2025 16:06

      есть как раз для C, экспортируются все функции внутри библиотеки

      https://github.com/DEgITx/librats/blob/master/src/librats_c.h (весь список экспортируемых функций)
      https://github.com/DEgITx/librats/blob/master/src/librats_c.cpp

      это интерфейс как раз и используется для биндингов в другие языки

      можно посмотреть примеры использования внутри unit-теста ( https://github.com/DEgITx/librats/blob/master/tests/test_librats_c_api.cpp )


      1. DEgITx Автор
        03.09.2025 16:06

        в частности при линковке должно быть ок

        [builder@degitx lib]$ nm --defined-only librats.a|grep "T rats_"
        0000000000005bb0 T rats_accept_file_transfer
        0000000000002ac1 T rats_announce_for_hash
        00000000000021f5 T rats_broadcast_binary
        0000000000002794 T rats_broadcast_json
        0000000000004e2a T rats_broadcast_message
        00000000000005ca T rats_broadcast_string
        0000000000006294 T rats_cancel_file_transfer
        00000000000003f9 T rats_connect
        0000000000001aba T rats_connect_with_strategy
        000000000000011f T rats_create
        0000000000000326 T rats_destroy
        ...


        я добавил еще в сам пост пример использование C-шных вариантов API


  1. kt97679
    03.09.2025 16:06

    Это очень круто! Не думали на базе вашей библиотеки реализовать не только текстовый, но и аудио-видео чаты?


    1. DEgITx Автор
      03.09.2025 16:06

      Ну для этого собственно в том числе и идёт разделение данных: для этого как раз функции обмена бинарными данными. В основе обычные сокеты, то что делегирует на себя библиотека это в основном поиск и нахождение участников и контроль над этим всем. Так что да, аудио-видео пожалуйста).


  1. ahdenchik
    03.09.2025 16:06

    Как у этой системы с защитой от флуда? Другими словами, смогу я, поправив код библиотеки, положить всю вашу сеть?

    Интересуюсь потому что лично мне не известны по-настоящему распределённые системы, которые от этого защищены.