Di bagian 2, bentuk pesan di kawat tersebar di beberapa tempat: siarkan menyusun "PENGIRIM|ISI\n", parser memeriksanya di layaniClient. Ketika format berubah — misalnya menambah waktu kirim atau ruangan chat — kita harus menyunting beberapa lokasi dan berharap tidak ada yang terlewat. Bagian 3 memindahkan seluruh pengetahuan tentang bentuk pesan ke satu tempat: modul protokol dengan fungsi parseBaris dan susunBaris, lengkap dengan aturan yang tegas soal baris mana yang sah.

Protokolnya sengaja sederhana agar fokus kita pada mekanismenya, bukan pada panjang spesifikasinya:

  • Satu pesan = satu baris, diakhiri \n — keputusan line-based yang meniru protokol klasik seperti SMTP dan IRC.
  • Pesan chat berbentuk PENGIRIM|ISI; karakter | adalah pemisah tunggal pertama di baris.
  • Baris berawalan / adalah perintah bagi server, seperti /quit; baris perintah tidak pernah di-parse sebagai pesan.
  • Baris kosong, tanpa pemisah, atau dengan nama/isi kosong ditolak dengan jelas.

Parser Protokol #

protokol.cpp berisi modul protokol sekaligus skenario demo yang menguji kelima aturan di atas — baris sah diurai, baris salah bentuk ditolak satu per satu, dan sebuah pesan disusun-ulang lalu di-parse kembali untuk membuktikan round-trip-nya utuh:

cpp
// protokol.cpp — parser protokol line-based untuk chat server.
// Format pesan di kawat: "PENGIRIM|ISI\n"; perintah khusus diawali '/'.
// File ini mandiri: fungsi main() menjalankan skenario demo deterministik.
#include <cstdint>
#include <iostream>
#include <optional>
#include <string>
#include <vector>

namespace protokol {

struct PesanChat {
    std::string pengirim;
    std::string isi;
};

// Mengurai satu baris kawat "PENGIRIM|ISI". Mengembalikan nullopt bila
// baris bukan pesan chat yang sah (tanpa pemisah, nama kosong, atau baris
// kosong). Baris berawalan '/' adalah perintah dan diperlakukan terpisah.
inline std::optional<PesanChat> parseBaris(const std::string& baris) {
    if (baris.empty() || baris[0] == '/') {
        return std::nullopt;          // kosong atau perintah: bukan pesan chat
    }
    const std::size_t posisiPemisah = baris.find('|');
    if (posisiPemisah == std::string::npos) {
        return std::nullopt;          // tanpa pemisah: format salah
    }
    PesanChat pesan;
    pesan.pengirim = baris.substr(0, posisiPemisah);
    pesan.isi = baris.substr(posisiPemisah + 1);
    if (pesan.pengirim.empty() || pesan.isi.empty()) {
        return std::nullopt;          // nama atau isi kosong: ditolak
    }
    return pesan;
}

// Menyusun baris kawat dari pesan chat, lengkap dengan '\n' penutup.
inline std::string susunBaris(const PesanChat& pesan) {
    return pesan.pengirim + "|" + pesan.isi + "\n";
}

}  // namespace protokol

int main() {
    using protokol::PesanChat;
    using protokol::parseBaris;
    using protokol::susunBaris;

    std::cout << "-- demo parser protokol line-based\n";

    // 1) Baris sah diurai menjadi pengirim dan isi. Baris yang masuk sudah
    //    tanpa '\n' — sama seperti hasil terimaBaris() di chat-server.cpp.
    const std::vector<std::string> masukan = {
        "Andi|Halo semua, selamat sore",
        "Bela|Siap, kita mulai",
        "/quit",                        // perintah, bukan pesan
        "tanpa-pemisah",                // format salah
        "|isi tanpa pengirim",          // nama kosong
    };

    int sah = 0;
    int ditolak = 0;
    for (const std::string& baris : masukan) {
        const std::optional<PesanChat> pesan = parseBaris(baris);
        if (pesan.has_value()) {
            ++sah;
            std::cout << "OK     pengirim=\"" << pesan->pengirim
                      << "\" isi=\"" << pesan->isi << "\"\n";
        } else {
            ++ditolak;
            std::cout << "TOLAK  \"" << baris << "\"\n";
        }
    }

    // 2) Round-trip: susun ulang pesan dan pastikan bentuk kawatnya sama.
    PesanChat balasan{"Cici", "Sampai jumpa di sesi berikutnya"};
    const std::string kawat = susunBaris(balasan);   // bentuk kawat: + '\n'
    // Parser menerima baris tanpa '\n', jadi kawat dipangkas dulu.
    std::string barisBalik = kawat;
    if (!barisBalik.empty() && barisBalik.back() == '\n') {
        barisBalik.pop_back();
    }
    const std::optional<PesanChat> balik = parseBaris(barisBalik);
    std::cout << (balik.has_value() && balik->pengirim == "Cici" &&
                          balik->isi == balasan.isi
                      ? "ROUND-TRIP OK: "
                      : "ROUND-TRIP GAGAL: ")
              << "\"" << barisBalik << "\"\n";

    std::cout << "ringkasan: " << sah << " pesan sah, " << ditolak
              << " baris ditolak\n";
    return 0;
}

Output yang Dihasilkan:

text
-- demo parser protokol line-based
OK     pengirim="Andi" isi="Halo semua, selamat sore"
OK     pengirim="Bela" isi="Siap, kita mulai"
TOLAK  "/quit"
TOLAK  "tanpa-pemisah"
TOLAK  "|isi tanpa pengirim"
ROUND-TRIP OK: "Cici|Sampai jumpa di sesi berikutnya"
ringkasan: 2 pesan sah, 3 baris ditolak

Bedah Modul Protokol #

Mari kita telaah bagian-bagian pentingnya:

  1. std::optional<PesanChat> parseBaris(const std::string&): tipe kembalian optional membuat kasus “baris ditolak” eksplisit tanpa mekanisme galat tersembunyi — pemanggil wajib memeriksa has_value() sebelum memakai hasil, sehingga jalur sukses dan jalur tolak terbaca sama jelasnya.
  2. baris.find('|'): hanya pemisah PERTAMA yang membelah baris; isi pesan boleh memuat karakter apa pun setelahnya, termasuk | tambahan. Keputusan kecil ini mencegah pesan “total|bayar=3” terpotong di tempat yang salah.
  3. Pemeriksaan bertingkat: baris kosong dan perintah ditolak lebih dulu, lalu ketiadaan pemisah, lalu nama/isi kosong. Urutan ini membuat alasan penolakan selalu tunggal dan mudah dilacak.
  4. susunBaris: satu-satunya tempat yang tahu bahwa bentuk kawatnya "PENGIRIM|ISI\n". Perubahan format kelak cukup menyentuh fungsi ini dan pasangannya.
  5. Round-trip di main: menyusun pesan menjadi bentuk kawat, memangkas \n (persis perilaku terimaBaris), lalu mengurai kembali — bila hasilnya identik dengan pesan awal, protokolnya benar-benar simetris.

Dengan protokol terpusat dan teruji, tinggal satu pekerjaan untuk menuntaskan proyek: harness pengujian yang membuktikan seluruh sistem — parser, registri, broadcast, sampai shutdown — bekerja bersama seperti yang diharapkan.

Rujukan resmi: cppreference — std::optional , RFC 9110 §Message abstraction (sejarah line-based) .