Всем привет! Я являюсь создателем распределённого поисковика 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, и они заметно отличаются друг от друга.

Почему я решил написать свою библиотеку
Я решил отказаться от этой «комбайн-реализации» и написать свою собственную. В качестве основы был выбран эффективный низкоуровневый язык, а к нему уже можно строить биндинги на других языках. Это позволяет получить универсальную и при этом действительно эффективную единую реализацию, которую можно использовать и в других проектах.
Так появилась 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)
ahdenchik
03.09.2025 16:06API нужно делать на C, иначе кроме как из C++ вызвать это будет ни откуда нельзя
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 )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
kt97679
03.09.2025 16:06Это очень круто! Не думали на базе вашей библиотеки реализовать не только текстовый, но и аудио-видео чаты?
DEgITx Автор
03.09.2025 16:06Ну для этого собственно в том числе и идёт разделение данных: для этого как раз функции обмена бинарными данными. В основе обычные сокеты, то что делегирует на себя библиотека это в основном поиск и нахождение участников и контроль над этим всем. Так что да, аудио-видео пожалуйста).
ahdenchik
03.09.2025 16:06Как у этой системы с защитой от флуда? Другими словами, смогу я, поправив код библиотеки, положить всю вашу сеть?
Интересуюсь потому что лично мне не известны по-настоящему распределённые системы, которые от этого защищены.
whocoulditbe
Ссылка на проект с utm-меткой
_source=chatgpt.com"
. В связи с этим сразу вопрос - сколько кода было написано LLM, а сколько человеком?DEgITx Автор
Юзалось для спелчекера и оформления)
По поводу кода, использовалось вспомогательно, почему бы и нет? Много Валидации, документации, юнит тестов и всего прочего.
kale
А какое это имеет значение?
Kenya-West
"Ты абсолютно прав! В данный момент проект имеет недостаток - он не имеет значения, написан он ChatGPT или нет. Сейчас перепишу весь проект, чтобы он имел значение"