mini_serv · Optim

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

Otras versiones: Compañero

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_serv.c
CódigoSubjectPor qué
#include <stdlib.h>malloc, realloc, free, calloc, bzero, atoi, sprintf, strlen, exitmalloc, realloc, free, atoi, exit.
#include <string.h>strstrstrlen, strcpy, strstr, bzero.
#include <unistd.h>write, closewrite, close.
#include <stdio.h>sprintfsprintf.
#include <sys/select.h>selectselect, fd_set, FD_SET…
#include <sys/socket.h>socket, accept, listen, send, recv, bindsocket, bind, listen, accept, send, recv.
#include <netinet/in.h>127.0.0.1struct sockaddr_in, htons, htonl.
// ---------- estado: todo indexado por fd ----------
int sock, maxfd, next_id;the first client will receive the id 0 and each new client will received the last client id + 1El socket que escucha, el fd más alto (para select) y el próximo id. Globales: una copia de cada cosa, sin pasarlas por todas las funciones.
int ids[FD_SETSIZE];the client will be given an id.El id de cada cliente, indexado por su fd. FD_SETSIZE (1024) es el máximo que admite select, y no es un #define mío.
char *bufs[FD_SETSIZE];a single message can contains multiple \nLo que cada cliente ha mandado y aún no acaba en \n.
fd_set all, rd, wr;use selectall: todos los fds abiertos. rd/wr: la copia que select rellena con los listos para leer / escribir.
void fatal(void)it should write in stderr "Fatal error" followed by a \n and exit with status 1Un solo sitio para el error fatal.
{
write(2, "Fatal error\n", 12);"Fatal error" followed by a \n2 = stderr. 12 letras con el \n.
exit(1);exit with status 1
}
// msg a todos los clientes listos para escribir, menos a from
void broadcast(int from, char *msg)a message is sent to all the client that was connected to the serverMandar msg a todos menos a from.
{
for (int fd = 0; fd <= maxfd; fd++)Recorro todos los fds posibles: los abiertos están en all.
if (fd != from && fd != sock && FD_ISSET(fd, &wr))if they don't read your message you must NOT disconnect them
The fd that you will receive will already be set to make 'recv' or 'send' to block if select hasn't be called before calling them
Ni al que lo manda, ni al socket de escucha, y solo si select dice que se puede escribir sin bloquear. Si un cliente no lee, lo salto: ni lo bloqueo ni lo echo.
send(fd, msg, strlen(msg), MSG_NOSIGNAL);sendMSG_NOSIGNAL: si el cliente ya se cayó, send devuelve error en vez de mandar SIGPIPE, que mataría el servidor.
}
// "server: client %d just arrived" / "server: client %d just left"
void notify(int fd, char *what)"server: client %d just arrived\n"
"server: client %d just left\n"
Los dos avisos del servidor solo cambian en una palabra.
{
char msg[64];Cabe de sobra: 30 letras más un número.
sprintf(msg, "server: client %d just %s\n", ids[fd], what);%d will be replace by this number%d = el id del cliente, %s = arrived o left.
broadcast(fd, msg);a message is sent to all the client that was connected to the serverA todos menos a él.
}
void add_client(void)When a client connect to the server:Hay alguien llamando al socket de escucha.
{
int fd = accept(sock, NULL, NULL);acceptaccept crea un fd nuevo para ese cliente. No necesito su dirección: NULL, NULL.
if (fd < 0)before the program start accepting connectionSi falla ya estamos aceptando conexiones: no es fatal, lo ignoro.
return ;
if (fd >= FD_SETSIZE)select no puede vigilar fds tan altos: lo cierro y sigo.
{
close(fd);Memory or fd leaks are forbiddenCada accept con su close: fd que no se usa, se cierra.
return ;
}
if (fd > maxfd)select necesita el fd más alto + 1.
maxfd = fd;
ids[fd] = next_id++;the first client will receive the id 0 and each new client will received the last client id + 1Primero 0, luego 1, 2… aunque alguien se vaya, el contador no baja.
bufs[fd] = NULL;Todavía no ha mandado nada.
FD_SET(fd, &all);Entra en el conjunto que vigila select.
notify(fd, "arrived");"server: client %d just arrived\n"Avisar a los demás.
}
void rm_client(int fd)When a client disconnect from the server:recv devolvió 0 (o error): se fue.
{
notify(fd, "left");"server: client %d just left\n"Primero el aviso, mientras aún sé su id.
free(bufs[fd]);Memory or fd leaks are forbiddenLo que dejó a medias sin \n se pierde con él.
bufs[fd] = NULL;Todavía no ha mandado nada.
FD_CLR(fd, &all);select deja de vigilarlo.
close(fd);Memory or fd leaks are forbiddenCada accept con su close: fd que no se usa, se cierra.
}
// "client %d: " delante de una línea ya sin '\n'
void relay(int fd, char *line)it must resend it to all the other client with "client %d: " before every line!Una línea completa (ya sin su \n) con el prefijo delante.
{
char *msg = malloc(strlen(line) + 32);Don't do un-necessary buffer.Justo lo que mide la línea más el prefijo: sin buffers gigantes fijos. 32 cubre client , el número, : , \n y el \0.
if (!msg)If you cant allocate memory
fatal();it should write in stderr "Fatal error" followed by a \n and exit with status 1Sin memoria: fatal, aunque ya estemos sirviendo.
sprintf(msg, "client %d: %s\n", ids[fd], line);"client %d: "Prefijo + línea + el \n que le quité.
broadcast(fd, msg);a message is sent to all the client that was connected to the serverA todos menos a él.
free(msg);Memory or fd leaks are forbidden
}
// pega lo recibido a bufs[fd], reparte cada línea completa y guarda el resto
void read_client(int fd)clients must be able to send messages to your program.select dice que hay algo que leer en este fd.
{
char chunk[4096];Un trozo: recv devuelve lo que haya llegado, no necesariamente una línea entera.
int n = recv(fd, chunk, sizeof(chunk) - 1, 0);recv-1: dejo sitio para el \0.
if (n <= 0)When a client disconnect from the server:0 = el cliente cerró; -1 = error. En ambos casos fuera.
{
rm_client(fd);
return ;
}
chunk[n] = '\0';Ahora es un string y puedo usar strcpy/strstr.
int len = bufs[fd] ? strlen(bufs[fd]) : 0;a single message can contains multiple \nCuánto tenía guardado de antes (nada si NULL).
char *buf = realloc(bufs[fd], len + n + 1);reallocLo viejo + lo nuevo + \0. realloc(NULL, …) es un malloc.
if (!buf)If you cant allocate memory
fatal();it should write in stderr "Fatal error" followed by a \n and exit with status 1Sin memoria: fatal, aunque ya estemos sirviendo.
strcpy(buf + len, chunk);strcpyPego el trozo nuevo al final.
char *line = buf, *nl;"client %d: " before every line!line: donde empieza la línea actual. nl: dónde acaba.
while ((nl = strstr(line, "\n")))a single message can contains multiple \nMientras quede un \n, hay una línea completa que repartir. strstr porque strchr no está permitido.
{
*nl = '\0';Corto la línea ahí: line es ahora solo esa línea.
relay(fd, line);it must resend it to all the other client
line = nl + 1;before every line!La siguiente empieza justo después del \n.
}
bufs[fd] = malloc(strlen(line) + 1);Lo que sobra sin \n (normalmente nada) se guarda para la próxima vez. memmove no está permitido: copia nueva.
if (!bufs[fd])If you cant allocate memory
fatal();it should write in stderr "Fatal error" followed by a \n and exit with status 1Sin memoria: fatal, aunque ya estemos sirviendo.
strcpy(bufs[fd], line);
free(buf);Memory or fd leaks are forbiddenEl buffer viejo ya no hace falta.
}
int main(int argc, char **argv)This program will take as first argument the port to bind to
{
if (argc != 2)If no argument is givenPrograma + puerto.
{
write(2, "Wrong number of arguments\n", 26);"Wrong number of arguments" followed by a \n26 letras con el \n.
exit(1);exit with status 1
}
sock = socket(AF_INET, SOCK_STREAM, 0);sockfd = socket(AF_INET, SOCK_STREAM, 0);IPv4 + TCP. Igual que en el main.c.
if (sock < 0)If a System Calls returns an error before the program start accepting connection
fatal();it should write in stderr "Fatal error" followed by a \n and exit with status 1Sin memoria: fatal, aunque ya estemos sirviendo.
struct sockaddr_in addr;struct sockaddr_in servaddr, cli;La dirección donde escucho.
bzero(&addr, sizeof(addr));bzero(&servaddr, sizeof(servaddr));A ceros antes de rellenar.
addr.sin_family = AF_INET;servaddr.sin_family = AF_INET;IPv4.
addr.sin_addr.s_addr = htonl(2130706433);Your program must only listen to 127.0.0.1
servaddr.sin_addr.s_addr = htonl(2130706433);
2130706433 = 0x7F000001 = 127.0.0.1. htonl: a orden de red. Copiado del main.c.
addr.sin_port = htons(atoi(argv[1]));the port to bind to
servaddr.sin_port = htons(8081);
El puerto del argumento, a orden de red con htons.
if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0 || listen(sock, 128) < 0)If a System Calls returns an error before the program start accepting connection, it should write in stderr "Fatal error"bind: me quedo esa dirección. listen: empiezo a aceptar (128 en cola). Cualquiera falla, fatal.
fatal();it should write in stderr "Fatal error" followed by a \n and exit with status 1Sin memoria: fatal, aunque ya estemos sirviendo.
maxfd = sock;Por ahora el único fd.
FD_ZERO(&all);Conjunto vacío…
FD_SET(sock, &all);…con el socket de escucha dentro.
while (1)El servidor no acaba nunca.
{
rd = wr = all;use selectselect modifica los conjuntos: le paso copias y conservo all.
if (select(maxfd + 1, &rd, &wr, NULL, NULL) < 0)Your program must be non-blocking
use select and NEVER check EAGAIN
Espera hasta que algún fd esté listo para leer o escribir. Sin timeout (NULL): puede esperar para siempre.
continue ;Siguiente vuelta: tras un error de select, vuelvo a esperar; dentro de un for, paso al siguiente.
for (int fd = 0; fd <= maxfd; fd++)Recorro todos los fds posibles: los abiertos están en all.
{
if (!FD_ISSET(fd, &rd))Este fd no tiene nada que leer.
continue ;Siguiente vuelta: tras un error de select, vuelvo a esperar; dentro de un for, paso al siguiente.
if (fd == sock)When a client connect to the server:Lectura en el socket de escucha = una conexión nueva.
add_client();
else
read_client(fd);clients must be able to send messages to your program.En un cliente = datos (o se ha ido).
}
}
}