mini_db · Optim

▶ Aprender mini_db (Optim) paso a paso · explicación de mini_db

Código línea a línea

Tres columnas: el código, el trozo del subject o del main que obliga a escribir esa línea, y el por qué.

mini_db.hpp
CódigoSubjectPor qué
#ifndef MINI_DB_HPPExpected files : *. cpp *. hppGuarda: que el .hpp no se incluya dos veces.
# define MINI_DB_HPP
# include <map>will maintain a database of key-values.std::map: la base de datos.
# include <string>std::string.
class Dbwill maintain a database of key-values.La base de datos: lo que sabe hacer (cargar, guardar, ejecutar un comando) y lo que guarda.
{
public:
Db(const std::string &path);a port and a pathNace sabiendo en qué fichero vive.
void load();Each time your program starts, it will look for the presence of this file and load it.Fichero → map.
void save() const;it must save the current databaseMap → fichero. const: no cambia nada.
std::string run(const std::string &line);Your program will receive commands from the connected clientsUn comando → su respuesta.
private:
std::string _path;to the file path given as an argumentEl fichero.
std::map<std::string, std::string> _data;a database of key-values.
Your program must be efficient.
Clave → valor. Un map busca en O(log n): eficiente.
};
#endifFin de la guarda.
mini_db.cpp
CódigoSubjectPor qué
#include "mini_db.hpp"El .cpp implementa la clase del .hpp.
#include <iostream>it will print "ready\n" on the standard output.std::cout, std::cerr.
#include <fstream>to the file path given as an argumentstd::ifstream, std::ofstream.
#include <sstream>POST <key> <value>std::istringstream: partir el comando por espacios.
#include <csignal>When your program receives the SIGINT signalsignal, SIGINT, sig_atomic_t.
#include <cstdlib>a portatoi, exit.
#include <cstring>memset.
#include <unistd.h>close.
#include <sys/select.h>The connections are persistentselect, fd_set: varios clientes a la vez.
#include <sys/socket.h>listen for clients to connectsocket, bind, listen, accept, send, recv, setsockopt.
#include <netinet/in.h>127.0.0.1sockaddr_in, htons, htonl, INADDR_LOOPBACK.
static volatile sig_atomic_t g_stop = 0;When your program receives the SIGINT signalLa bandera que levanta la señal. volatile sig_atomic_t: el único tipo seguro de tocar desde un handler. static: solo en este fichero.
static void on_sigint(int)When your program receives the SIGINT signal, it must save the current databaseEl handler solo levanta la bandera: guardar aquí dentro no es seguro (streams, malloc…). El bucle del servidor verá la bandera y guardará.
{
g_stop = 1;
}
static void fatal()Error de sistema antes de servir: mensaje y fuera.
{
std::cerr << "Fatal error" << std::endl;Por stderr.
exit(1);
}
// ---------- la base de datos: un map y un fichero ----------
Db::Db(const std::string &path) : _path(path) {}a pathSolo guarda la ruta. Nada se carga aún.
void Db::load()Each time your program starts, it will look for the presence of this file and load it.
{
std::ifstream in(_path.c_str());it will look for the presence of this fileSi no existe, el stream está mal y el while no entra: no es un error.
std::string key, value;
while (in >> key >> value)Keys and values will be of arbitrary size and will not contain any white spaces,Sin espacios dentro: >> separa por espacios y basta. Para cuando no quedan dos palabras.
_data[key] = value;add an entry to the dbCrea o sobreescribe.
}
void Db::save() constit must save the current database (i.e. all the key-value pairs) to the file path given as an argument
{
std::ofstream out(_path.c_str());to the file path given as an argumentCrea o trunca el fichero.
for (std::map<std::string, std::string>::const_iterator it = _data.begin(); it != _data.end(); ++it)all the key-value pairsTodo el map. const_iterator porque save es const (C++98: sin auto).
out << it->first << ' ' << it->second << '\n';clave valor por línea: justo lo que load lee con >>.
}
// una línea de comando -> la respuesta, con su '\n'
std::string Db::run(const std::string &line)Your program will receive commands from the connected clientsUna línea, una respuesta. No sabe de sockets: fácil de probar.
{
std::istringstream ss(line);Para leer la línea palabra a palabra.
std::string cmd, key, value, extra;POST <key> <value>Comando, clave, valor y lo que sobre.
ss >> cmd >> key >> value >> extra;will not contain any white spaces,Lo que falte queda vacío: así compruebo cuántas palabras venían.
if (cmd == "POST" && !value.empty() && extra.empty())POST <key> <value>POST con exactamente clave y valor.
{
_data[key] = value;add an entry to the dbCrea o sobreescribe.
return ("0\n");return value: "0"Con el \n: el cliente lee líneas.
}
if (cmd == "GET" && !key.empty() && value.empty())GET <key>GET con solo una clave.
{
if (!_data.count(key))"1" if the key does not exist
return ("1\n");
return ("0 " + _data[key] + "\n");"0 <value>"
}
if (cmd == "DELETE" && !key.empty() && value.empty())DELETE <key>
return (_data.erase(key) ? "0\n" : "1\n");return "0" if the key was successfully erased or "1" if the key does not existerase(clave) devuelve cuántos borró: 1 o 0.
return ("2\n");If a command is not recognized your program must respond "2".Todo lo demás.
}
// ---------- el servidor: select sobre 127.0.0.1:port ----------
static int listen_on(int port)Your program must listen for clients to connect on 127.0.0.1 on the given port.Crea el socket de escucha y lo devuelve.
{
int sock = socket(AF_INET, SOCK_STREAM, 0);IPv4 + TCP.
if (sock < 0)
fatal();
int yes = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));$> ./mini_db 1111 .save &Poder reabrir el mismo puerto nada más matar el servidor (como en el ejemplo). Sin esto, bind falla un rato con "address already in use".
sockaddr_in addr;La dirección donde escucho.
memset(&addr, 0, sizeof(addr));A ceros antes de rellenar.
addr.sin_family = AF_INET;IPv4.
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);127.0.0.1INADDR_LOOPBACK = 127.0.0.1, a orden de red.
addr.sin_port = htons(port);on the given portA orden de red.
if (bind(sock, (sockaddr *)&addr, sizeof(addr)) < 0 || listen(sock, 128) < 0)listen for clients to connectbind: me quedo esa dirección. listen: cola de 128 conexiones pendientes.
fatal();
return (sock);
}
static void serve(int sock, Db &db)The connections are persistent, which means that a program can send multiple commands in a single session.El bucle select: acepta, lee, responde. Hasta que llegue SIGINT.
{
fd_set all, rd;all: fds abiertos. rd: copia que select rellena con los que tienen algo que leer.
int maxfd = sock;select necesita el fd más alto + 1.
std::map<int, std::string> bufs;a program can send multiple commands in a single sessionLo recibido de cada cliente que aún no acaba en \n.
FD_ZERO(&all);Conjunto vacío…
FD_SET(sock, &all);…con el socket de escucha dentro.
while (!g_stop)When your program receives the SIGINT signalLa señal corta el select y levanta la bandera: salgo del bucle.
{
rd = all;select modifica el conjunto: le paso una copia.
if (select(maxfd + 1, &rd, NULL, NULL, NULL) < 0)Espera a que algún fd tenga datos. Sin timeout.
continue ;Error (p. ej. la señal): compruebo la bandera y, si no, sigo esperando.
for (int fd = 0; fd <= maxfd; fd++)Reviso todos los fds.
{
if (!FD_ISSET(fd, &rd))Este no tiene nada.
continue ;Error (p. ej. la señal): compruebo la bandera y, si no, sigo esperando.
if (fd == sock)listen for clients to connectLectura en el socket de escucha = conexión nueva.
{
int client = accept(sock, NULL, NULL);Un fd nuevo para el cliente. Su dirección no me importa.
if (client < 0)Ya estamos sirviendo: no es fatal.
continue ;Error (p. ej. la señal): compruebo la bandera y, si no, sigo esperando.
FD_SET(client, &all);select lo vigila a partir de ahora.
if (client > maxfd)
maxfd = client;
bufs[client].clear();Buffer vacío para él (crea la entrada en el map).
continue ;Error (p. ej. la señal): compruebo la bandera y, si no, sigo esperando.
}
char buf[1024];the size of a request will never exceed 1000 characters.Un trozo: recv devuelve lo que haya, no necesariamente una línea.
int n = recv(fd, buf, sizeof(buf), 0);
if (n <= 0)0 = el cliente cerró; -1 = error. Fuera.
{
FD_CLR(fd, &all);
close(fd);Sin leaks de fd.
bufs.erase(fd);Y su buffer.
continue ;Error (p. ej. la señal): compruebo la bandera y, si no, sigo esperando.
}
bufs[fd].append(buf, n);a program can send multiple commands in a single sessionPego lo nuevo. append(ptr, n): buf no acaba en \0.
size_t nl;Posición del próximo \n.
while ((nl = bufs[fd].find('\n')) != std::string::npos)Cada línea completa es un comando. Pueden venir varias en un recv.
{
std::string reply = db.run(bufs[fd].substr(0, nl));Your program will receive commands from the connected clientsLa línea sin su \n.
bufs[fd].erase(0, nl + 1);Quito la línea ya tratada; queda lo que venga detrás.
send(fd, reply.c_str(), reply.size(), 0);La respuesta al que preguntó.
}
}
}
}
int main(int argc, char **argv)Write a program that takes as an argument a port and a path.
{
if (argc != 3)Programa + puerto + ruta.
{
std::cerr << "Wrong number of arguments" << std::endl;
return (1);
}
Db db(argv[2]);a pathEl segundo argumento es el fichero.
db.load();Each time your program starts, it will look for the presence of this file and load it.Antes de atender a nadie.
signal(SIGINT, on_sigint);When your program receives the SIGINT signalCtrl-C ya no mata el programa: levanta la bandera.
int sock = listen_on(atoi(argv[1]));a portEl primer argumento es el puerto.
std::cout << "ready" << std::endl;When your program is ready to accept incoming connections, it will print "ready\n" on the standard output.Justo después de listen: ya se puede conectar. endl hace flush: el que espera lo ve ya.
serve(sock, db);Hasta SIGINT.
db.save();it must save the current database (i.e. all the key-value pairs) to the file path given as an argument and exit.Al salir del bucle, con calma, fuera del handler.
close(sock);
return (0);
}