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:
// 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:
-- 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 ditolakBedah Modul Protokol #
Mari kita telaah bagian-bagian pentingnya:
std::optional<PesanChat> parseBaris(const std::string&): tipe kembalianoptionalmembuat kasus “baris ditolak” eksplisit tanpa mekanisme galat tersembunyi — pemanggil wajib memeriksahas_value()sebelum memakai hasil, sehingga jalur sukses dan jalur tolak terbaca sama jelasnya.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.- 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.
susunBaris: satu-satunya tempat yang tahu bahwa bentuk kawatnya"PENGIRIM|ISI\n". Perubahan format kelak cukup menyentuh fungsi ini dan pasangannya.- Round-trip di
main: menyusun pesan menjadi bentuk kawat, memangkas\n(persis perilakuterimaBaris), 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) .