Setelah menyelesaikan masalah mendasar IO dan sinkronisasi, kami akhirnya dapat mulai menulis kode dari tingkat logika atau bisnis. Meskipun LLM selalu cenderung menampilkan semua kode sekaligus, saya tetap mencoba mengikuti logika manusia saat menulis artikel ini, terlebih dahulu menentukan arah umum dan kemudian mengisi detailnya.
Di area pemrosesan pesan, konten berikut dapat dibagi menjadimengirim/Permintaan Pemberitahuan danmenghadapiMeminta pemberitahuan, bagian pertama lebih sederhana, kita mulaimengirimAwal.
Mengirim permintaan dan pesan sangat sederhana, kita hanya perlu membuat Request struktur, gunakan to_json Setelah mengubahnya menjadi objek JSON dump akhirnya menelepon send_message Itu saja.
// Send a Request to the other side, with a callback for the response.
void send_request(const std::string& method, const json& params, ResponseHandler callback) {
int id = next_id_++;
{
std::lock_guard<std::mutex> lock(callback_mutex_);
pending_callbacks_[id] = callback;
}
Request req{ id, method, params };
json j;
to_json(j, req);
send_message(j.dump());
}
// Send a notification to other side.
void send_notification(const std::string& method, const json& params = nullptr) {
Request req{ std::nullopt, method, params };
json j;
to_json(j, req);
send_message(j.dump());
}
Perbedaan antara permintaan dan notifikasi adalah kita mengharapkan mendapatkan nilai kembalian. Di sini saya menerapkannya dengan mendaftarkan panggilan balik untuk menangani nilai kembalian. Tidak ada permintaan sinkron dalam implementasi yang diberikan oleh Gemini. Penjelasannya adalah penerapan permintaan sinkron merepotkan dan rawan masalah. Mengingat sisi C++ lebih banyak menyediakan layanan daripada permintaan, saya terlalu malas untuk mengubahnya dan mengadopsi mode panggilan balik asinkron murni. Kunci mutex di atas memastikan bahwa tidak ada kondisi balapan yang akan terjadi ketika beberapa thread meminta secara bersamaan. Ketika respons terhadap permintaan diterima, kode berikut memanggil callback yang sesuai untuk menyelesaikan perulangan:
void handle_response(const Response& resp) {
ResponseHandler callback = nullptr;
{
std::lock_guard<std::mutex> lock(callback_mutex_);
auto it = pending_callbacks_.find(resp.id);
if (it != pending_callbacks_.end()) {
callback = std::move(it->second);
pending_callbacks_.erase(it);
}
}
if (callback) callback(resp);
}
Implementasi saat ini belum menambahkan mekanisme pembersihan batas waktu permintaan. Meskipun secara teori, panggilan balik yang tidak responsif akan memakan ruang, mengingat frekuensi permintaan aktif yang dimulai oleh server sangat rendah dan sebagian besar berjalan pada jaringan pipa lokal yang andal, tidak ada kebutuhan yang kuat untuk menambahkan mekanisme ini untuk saat ini.
Jika mengirim berarti menyampaikan pesan secara aktif, maka menerima pesan berarti penjadwalan pasif.process_queue mengambil peran penyortir: terus menerus inbox_ Keluarkan dari antrian IncomingMessagedan mendistribusikannya ke fungsi pemrosesan yang sesuai sesuai dengan jenis pesan:
// Main Loop Processor: Call this from your main thread/event loop.
// It processes messages from the inbox queue.
void process_queue() {
IncomingMessage msg;
while (inbox_.try_pop(msg)) {
if (std::holds_alternative<Request>(msg)) {
handle_request(std::get<Request>(msg));
} else if (std::holds_alternative<Response>(msg)) {
handle_response(std::get<Response>(msg));
} else if (std::holds_alternative<Error>(msg)) {
continue;
}
}
}
Pada versi sebelumnya,ThreadSafeQueue Jenis yang digunakan adalah sebagai berikut:
using IncomingMessage = std::variant<Request, Response>;
Setelah membaca spesifikasi JSON-RPC 2.0 dengan cermat, saya perhatikan bahwa ketika terjadi kesalahan penguraian atau permintaan yang tidak valid, karena server tidak dapat mengekstrak ID permintaan, yang dikembalikan Response tengah id Harus null. Sejak ini spesial Response selalu ditemani oleh error bidang dan tidak memiliki valid idalih-alih Response Bagaimana mengekspresikan kekosongan dalam struktur idlebih baik “menurunkan versi” langsung di lapisan Parser menjadi Error Kemudian masukkan ke dalam antrian pesan. Gemini akan melakukannya IncomingMessage didefinisikan ulang sebagai:
// A variant capable of holding any valid incoming message type.
using IncomingMessage = std::variant<Request, Response, Error>;
Kami telah memberikannya di bagian sebelumnya handle_response implementasi, penanganan permintaan handle_request Implementasi (awal) adalah sebagai berikut. Ia mencari fungsi handler di tabel pemetaan berdasarkan nama metode, memanggil fungsi tersebut, menangkap pengecualian, dan akhirnya mengirimkan respons:
void register_method(const std::string& name, RequestHandler handler) {
std::lock_guard<std::mutex> lock(map_mutex_);
method_handlers_[name] = handler;
}
void handle_request(const Request& req) {
RequestHandler handler = nullptr;
{
std::lock_guard<std::mutex> lock(map_mutex_);
if (method_handlers_.count(req.method)) {
handler = method_handlers_[req.method];
}
}
json result_json;
bool success = false;
std::string error_msg;
if (handler) {
try {
result_json = handler(req.params);
success = true;
} catch (const std::exception& e) {
error_msg = e.what();
}
} else {
error_msg = "Method not found";
}
json j_resp;
if (success) {
to_json(j_resp, Response::make_success(req.id, result_json));
} else {
int code = (error_msg == "Method not found") ? -32601 : -32603;
to_json(j_resp, Response::make_error(req.id, code, error_msg));
}
send_message(j_resp.dump());
}
Setelah menyelesaikan bagian ini, implementasi versi pertama saya pada dasarnya telah selesai. Namun, versi implementasi ini tidak dapat menangani metode asinkron. Jika fungsi asinkron dipanggil secara internal dalam suatu metode, kita tidak bisa membiarkan panggilan balik fungsi asinkron dipanggil sebelum mengirimkan respons.
Untuk mengatasi masalah ini, salah satu caranya adalah dengan meminta id Juga sebagai parameter metode, ini juga memungkinkan metode untuk secara aktif memanggil fungsi pengirim untuk merespons, bukan oleh handle_request tanggapan. Namun pengguna umumnya mungkin tidak memerlukannya id informasi yang ditulis Gemini Context struktur untuk secara khusus menangani masalah balasan metode asinkron: metode asinkron menerima a Context parameter konteks dan Params Parameter, tidak perlu lagi segera mengembalikan hasilnya, tetapi dapat dipanggil setelah dan setelah tugas selesai Context Obyek reply atau error Untuk membalas:
// Context passed to async request handlers.
// Allows handlers to reply or send errors asynchronously.
class Context {
public:
Context(Conn& c, std::optional<int> i) : conn_(c), id_(i) {}
void reply(json result);
void error(int code, std::string message, json data = nullptr);
std::optional<int> id() const { return id_; }
bool is_notification() const { return !id_.has_value(); }
private:
Conn& conn_;
std::optional<int> id_;
};
// Implement Context methods inline after Conn is defined.
inline void Context::reply(json result) {
if (id_.has_value()) {
conn_.send_response_success(id_.value(), std::move(result));
}
}
inline void Context::error(int code, std::string message, json data) {
if (id_.has_value()) {
conn_.send_response_error(id_.value(), code, std::move(message), std::move(data));
}
}
Dalam implementasi baru ini, metode asinkron akan berfungsi sebagai dasar untuk metode dan notifikasi sinkron. register_async_method Dengan menambahkan kemasan, kita dapat dengan mudah mendaftarkan metode RPC sinkron dan metode notifikasi:
using AsyncRequestHandler = std::function<void(Context, const json&)>;
using RequestHandler = std::function<json(const json&)>;
using ResponseHandler = std::function<void(const Response&)>;
using NotificationHandler = std::function<void(const json&)>;
void register_async_method(const std::string& name, AsyncRequestHandler handler) {
method_handlers_[name] = handler;
}
// Register a sync method.
// Wraps the sync handler into an async one.
void register_method(const std::string& name, RequestHandler handler) {
register_async_method(name, [handler](Context ctx, const json& params) {
try {
// Call user logic, get result, reply immediately
json res = handler(params);
ctx.reply(std::move(res));
} catch (const JsonRpcException& e) {
// Allow handler to throw JsonRpcException directly.
ctx.error(e.code, e.what(), e.data);
} catch (const std::exception& e) {
// Catch generic exceptions as Internal Error.
ctx.error(spec::kInternalError, e.what());
}
});
}
// Register a notification handler.
void register_notification(const std::string& name, NotificationHandler handler) {
register_async_method(name, [handler](Context ctx, const json& params) {
if (ctx.is_notification()) {
handler(params);
} else {
ctx.error(spec::kInvalidRequest, "Notification handler expects no id");
}
});
}
baru handle_request Implementasinya adalah sebagai berikut:
void handle_request(const Request& req) {
AsyncRequestHandler handler;
if (method_handlers_.count(req.method)) {
handler = method_handlers_[req.method];
}
if (handler) {
try {
// Pass Context to handler. It is responsible for replying.
handler(Context(*this, req.id), req.params);
} catch (const JsonRpcException& e) {
if (req.id.has_value()) {
send_response_error(req.id.value(), e.code, e.what(), e.data);
}
} catch (const std::exception& e) {
// User threw a generic exception -> Internal Error
if (req.id.has_value()) {
send_response_error(req.id.value(), spec::kInternalError, e.what());
}
}
} else {
// Method not found.
if (req.id.has_value()) {
send_response_error(req.id.value(), spec::kMethodNotFound, spec::msg_MethodNotFound);
}
}
}
Masih ada beberapa detail yang tertinggal, tetapi beginilah cara kerjanya. Berurusan dengan detail pengecualian dan pengujian unit dengan Gemini seharusnya menjadi hal yang paling memakan waktu setelah kode saya diselesaikan secara umum.
PakarPBN
A Private Blog Network (PBN) is a collection of websites that are controlled by a single individual or organization and used primarily to build backlinks to a “money site” in order to influence its ranking in search engines such as Google. The core idea behind a PBN is based on the importance of backlinks in Google’s ranking algorithm. Since Google views backlinks as signals of authority and trust, some website owners attempt to artificially create these signals through a controlled network of sites.
In a typical PBN setup, the owner acquires expired or aged domains that already have existing authority, backlinks, and history. These domains are rebuilt with new content and hosted separately, often using different IP addresses, hosting providers, themes, and ownership details to make them appear unrelated. Within the content published on these sites, links are strategically placed that point to the main website the owner wants to rank higher. By doing this, the owner attempts to pass link equity (also known as “link juice”) from the PBN sites to the target website.
The purpose of a PBN is to give the impression that the target website is naturally earning links from multiple independent sources. If done effectively, this can temporarily improve keyword rankings, increase organic visibility, and drive more traffic from search results.