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ódigo | Subject | Por qué |
|---|---|---|
#include <stdlib.h> | malloc, realloc, free, calloc, bzero, atoi, sprintf, strlen, exit | malloc, realloc, free, atoi, exit. |
#include <string.h> | strstr | strlen, strcpy, strstr, bzero. |
#include <unistd.h> | write, close | write, close. |
#include <stdio.h> | sprintf | sprintf. |
#include <sys/select.h> | select | select, fd_set, FD_SET… |
#include <sys/socket.h> | socket, accept, listen, send, recv, bind | socket, bind, listen, accept, send, recv. |
#include <netinet/in.h> | 127.0.0.1 | struct 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 + 1 | El 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 \n | Lo que cada cliente ha mandado y aún no acaba en \n. |
fd_set all, rd, wr; | use select | all: 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 1 | Un solo sitio para el error fatal. |
{ | ||
write(2, "Fatal error\n", 12); | "Fatal error" followed by a \n | 2 = 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 server | Mandar 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); | send | MSG_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 server | A 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); | accept | accept crea un fd nuevo para ese cliente. No necesito su dirección: NULL, NULL. |
| ||
if (fd < 0) | before the program start accepting connection | Si 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 forbidden | Cada 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 + 1 | Primero 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 forbidden | Lo 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 forbidden | Cada 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 1 | Sin 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 server | A 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 \n | Cuánto tenía guardado de antes (nada si NULL). |
char *buf = realloc(bufs[fd], len + n + 1); | realloc | Lo 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 1 | Sin memoria: fatal, aunque ya estemos sirviendo. |
strcpy(buf + len, chunk); | strcpy | Pego 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 \n | Mientras 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 1 | Sin memoria: fatal, aunque ya estemos sirviendo. |
strcpy(bufs[fd], line); | ||
free(buf); | Memory or fd leaks are forbidden | El 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 given | Programa + puerto. |
{ | ||
write(2, "Wrong number of arguments\n", 26); | "Wrong number of arguments" followed by a \n | 26 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 1 | Sin 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.1servaddr.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 toservaddr.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 1 | Sin 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 select | select 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). |
} | ||
} | ||
} |