mini_serv · Compañero
▶ Aprender mini_serv (Compañero) paso a paso · explicación de mini_serv
Otras versiones: Optim
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 <netinet/in.h> | 127.0.0.1 | struct sockaddr_in, htons, htonl. |
#include <stdio.h> | sprintf | sprintf. |
#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 <sys/socket.h> | socket, accept, listen, send, recv, bind | socket, bind, listen, accept, send, recv. |
#include <unistd.h> | write, close | write, close. |
| ||
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[1024], lens[1024]; | the client will be given an id. | Versión compañero, por fd: el id del cliente y cuántas letras lleva de la línea a medias. 1024 = FD_SETSIZE, lo máximo que vigila select. |
char msgs[1024][400000]; | a single message can contains multiple \n | La línea a medias de cada cliente, fija y sin malloc. Las globales empiezan a 0 y el sistema solo da memoria a lo que se toca: arranca con ~1 MB. |
char out[400100], in[4096]; | out: el mensaje a repartir (línea + prefijo). in: lo que trae cada recv. | |
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(char *msg) | it should write in stderr "Fatal error" followed by a \n and exit with status 1 | Un solo sitio para los dos errores: recibe el texto ya con su \n. |
{ | ||
write(2, msg, strlen(msg)); | write in stderr | 2 = stderr. |
exit(1); | exit with status 1 | |
} | ||
| ||
void send_all(int from) | a message is sent to all the client that was connected to the server | Manda out 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_ISSET(fd, &wr)) | if they don't read your message you must NOT disconnect them | Ni al que lo manda, y solo si select dice que se puede escribir sin bloquear. El de escucha nunca está en wr. |
send(fd, out, strlen(out), MSG_NOSIGNAL); | send | MSG_NOSIGNAL: si el cliente ya se cayó, send falla sin SIGPIPE y el servidor sigue. El error se ignora: tras listen solo es fatal quedarse sin memoria. |
} | ||
| ||
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 >= 1024) | No cabe en los arrays ni en el fd_set: 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. |
lens[fd] = 0; | Empieza sin nada a medias (el fd puede venir de un cliente que se fue). | |
FD_SET(fd, &all); | Entra en el conjunto que vigila select. | |
sprintf(out, "server: client %d just arrived\n", ids[fd]); | "server: client %d just arrived\n" | %d = el id, no el fd. |
send_all(fd); | a message is sent to all the client that was connected to the server | A todos menos a fd. |
} | ||
| ||
void remove_client(int fd) | When a client disconnect from the server: | recv devolvió 0 (o error): se fue. |
{ | ||
sprintf(out, "server: client %d just left\n", ids[fd]); | "server: client %d just left\n" | El aviso antes de cerrar, mientras sé su id. |
send_all(fd); | a message is sent to all the client that was connected to the server | A todos menos a fd. |
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. |
} | ||
| ||
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. |
{ | ||
int n = recv(fd, in, sizeof(in), 0); | recv | Lo que haya llegado: media línea o varias. |
if (n <= 0) | When a client disconnect from the server: | 0 = el cliente cerró; -1 = error. En ambos casos fuera. |
{ | ||
remove_client(fd); | ||
return ; | ||
} | ||
for (int i = 0; i < n; i++) | a single message can contains multiple \n | Letra a letra lo recibido. |
{ | ||
if (in[i] != '\n') | Aún no acaba la línea: la guardo. | |
{ | ||
if (lens[fd] < 399999) | Tope: una línea gigante se corta en vez de salirse del array (deja sitio al \0). | |
msgs[fd][lens[fd]++] = in[i]; | ||
continue ; | Siguiente vuelta: tras un error de select, vuelvo a esperar; dentro de un for, paso al siguiente. | |
} | ||
msgs[fd][lens[fd]] = '\0'; | Llegó el \n: cierro el string. | |
sprintf(out, "client %d: %s\n", ids[fd], msgs[fd]); | "client %d: " before every line! | Prefijo + línea + el \n que no guardé. |
send_all(fd); | a message is sent to all the client that was connected to the server | A todos menos a fd. |
lens[fd] = 0; | Empieza sin nada a medias (el fd puede venir de un cliente que se fue). | |
} | ||
} | ||
| ||
int main(int ac, char **av) | This program will take as first argument the port to bind to | |
{ | ||
if (ac != 2) | If no argument is given | Programa + puerto. |
fatal("Wrong number of arguments\n"); | "Wrong number of arguments" followed by a \n | |
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("Fatal error\n"); | If a System Calls returns an error before the program start accepting connection | socket, bind o listen fallan: fatal. |
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(av[1])); | the port to bind to | El puerto del argumento, a orden de red. |
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("Fatal error\n"); | If a System Calls returns an error before the program start accepting connection | socket, bind o listen fallan: fatal. |
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). |
} | ||
} | ||
} |