mini_db
Código comentado línea a línea y paso a paso:
Entregas: mini_db.hpp, mini_db.cpp. Flags: c++ -Wall -Wextra -Werror (compila en C++98).
Qué pide (en palabras normales)
- Un servidor TCP en
127.0.0.1y el puerto del argumento que guarda clave → valor. Al estar listo imprimeready. - Comandos por línea:
POST k v→0·GET k→0 vo1·DELETE k→0o1· otra cosa →2. - Un cliente puede mandar varios comandos en la misma conexión. Claves y valores sin espacios, cada petición menos de 1000 caracteres.
- Al recibir SIGINT: guardar todo en el fichero del segundo argumento y salir. Al arrancar: si existe ese fichero, cargarlo.
- Tiene que ser eficiente:
std::map(ounordered_mapsi no es C++98).
Subject (enunciado original)
Assignment name: mini_db
Expected files : *. cpp *. hpp
-----------------------------------------------------------------
Write a program that takes as an argument a port and a path.
Your program must listen for clients to connect on 127.0.0.1 on the given port.
When your program is ready to accept incoming connections, it will print "ready\n" on
the standard output.
Your program will receive commands from the connected clients and will maintain a
database of key-values.
The connections are persistent, which means that a program can send multiple
commands in a single session.
The commands are the following:
POST:
synopsis: POST <key> <value>
description: add an entry to the db
return value: "0"
GET:
synopsis: GET <key>
description: retrieve an entry from the db
return value: "0 <value>" or "1" if the key does not exist
DELETE:
synopsis: DELETE <key>
description: erase an entry in the db
return value: return "0" if the key was successfully erased or "1" if the key does not exist
If a command is not recognized your program must respond "2".
Keys and values will be of arbitrary size and will not contain any white spaces,
but the size of a request will never exceed 1000 characters.
Your program must be efficient.
Data persistence:
When your program receives the SIGINT signal, it must save the current database (i.e. all the
key-value pairs) to the file path given as an argument and exit.
Each time your program starts, it will look for the presence of this file
and load it.
Hint: you can use nc to test your program.
You will find in this directory a file containing some network code in case you
have forgotten how it works.
Example:
$> ./mini_db 1111 .save &
$> nc localhost 1111 | cat -e
POST A B
O$
POST B C
0$
GET A
0 B$
GET C
1$
DELETE A
O$
DELETE C
1$
UNKNOWN_COMMAND
2$
^C
$> pkill -SIGINT mini_db
$> ./mini_db 1111 .save &
$> echo "GET B" | nc localhost 1111 | cat -e
0 C$
S>
4 piezas
1. La base de datos es un map y dos streams
while (in >> key >> value) // load
_data[key] = value;
out << it->first << ' ' << it->second << '\n'; // save
Sin espacios dentro de clave ni valor: >> parte por espacios y basta. El fichero es clave valor por línea, lo que load lee tal cual.
2. Un comando → una respuesta
ss >> cmd >> key >> value >> extra;
if (cmd == "POST" && !value.empty() && extra.empty()) ...
if (cmd == "GET" && !key.empty() && value.empty()) ...
Leo hasta 4 palabras: las que falten quedan vacías. Así compruebo a la vez el comando y su número de argumentos. Lo que no cuadre, 2. run no sabe de sockets: se prueba con un string.
3. SIGINT con bandera, no con trabajo dentro del handler
static volatile sig_atomic_t g_stop = 0;
static void on_sigint(int) { g_stop = 1; }
...
while (!g_stop) { select(...); ... }
db.save();
La señal interrumpe select (devuelve -1), el bucle mira la bandera y sale, y save corre con calma fuera del handler. Dentro de un handler no es seguro usar streams ni malloc.
4. Varios comandos por conexión
bufs[fd].append(buf, n);
while ((nl = bufs[fd].find('\n')) != std::string::npos)
{
reply = db.run(bufs[fd].substr(0, nl));
bufs[fd].erase(0, nl + 1);
send(fd, reply.c_str(), reply.size(), 0);
}
Un std::string por cliente acumula lo recibido; cada \n es un comando. La respuesta va solo al que preguntó (no es un chat).
mini_db función por función
| Función | Qué hace |
|---|---|
Db::load() | fichero → map con >> |
Db::save() | map → fichero, clave valor por línea |
Db::run(line) | un comando → su respuesta con \n |
on_sigint | levanta g_stop |
listen_on(port) | socket, SO_REUSEADDR, bind 127.0.0.1, listen |
serve(sock, db) | bucle select: accept, recv, run, send, hasta g_stop |
main | argumentos, load, signal, listen, ready, serve, save |
Trampas
readyconstd::endl(flush): el corrector espera esa línea antes de conectarse.SO_REUSEADDR: el ejemplo del subject mata el servidor y lo relanza en el mismo puerto al instante. Sin él,bindfalla.- Respuestas con
\n:nc | cat -emuestra0$. selectmachaca el conjunto:rd = allen cada vuelta.- El fichero puede no existir la primera vez: no es un error.
Otras soluciones
No hay otras publicadas que conozca: ni stc71 ni mohaben-1 tienen mini_db. El simulador 42_examshell trae una de referencia (C++11, guarda dentro del handler y no parte por \n).