POSIX
Multiplexación de entrada / salida
Buscar..
Introducción
IO puede estar bloqueando / no bloqueando y sincrónico / asíncrono. La API de POSIX proporciona una API de bloqueo síncrono (p. Ej., Lectura clásica, escritura, envío, recepción de llamadas), una API sin bloqueo síncrona (mismas funciones, descriptores de archivo abiertos con el indicador O_NONBLOCK
y llamadas multiplexación IO) y una API asíncrona (funciones que comienzan con aio_
).
La API síncrona se usa generalmente con el estilo "un subproceso / proceso por fd". Esto es terrible para los recursos. La API sin bloqueo permite operar con un conjunto de fds en un hilo.
Encuesta
En este ejemplo, creamos un par de sockets conectados y enviamos 4 cadenas de una a otra e imprimimos las cadenas recibidas en la consola. Tenga en cuenta que la cantidad de veces que llamaremos el envío puede no ser igual a la cantidad de veces que llamamos recv
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <poll.h>
#define BUFSIZE 512
int main()
{
#define CKERR(msg) {if(ret < 0) { perror(msg); \
close(sockp[0]); close(sockp[1]); exit(EXIT_FAILURE); } }
const char* strs_to_write[] = {"hello ", "from ", "other ", "side "};
int sockp[2] = {-1, -1};
ssize_t ret = socketpair (AF_UNIX, SOCK_STREAM, 0, sockp);
CKERR("Socket pair creation error")
struct pollfd pfds[2];
for(int i=0; i<2; ++i) {
pfds[i] = (struct pollfd){sockp[i], POLLIN|POLLOUT, 0};
fcntl(sockp[i], F_SETFL|O_NONBLOCK); // nonblocking fds are
// literally mandatory for IO multiplexing; non-portable
}
char buf[BUFSIZE];
size_t snt = 0, msgs = sizeof(strs_to_write)/sizeof(char*);
while(1) {
int ret = poll(pfds,
2 /*length of pollfd array*/,
5 /*milliseconds to wait*/);
CKERR("Poll error")
if (pfds[0].revents & POLLOUT && snt < msgs) {
// Checking POLLOUT before writing to ensure there is space
// available in socket's kernel buffer to write, otherwise we
// may face EWOULDBLOCK / EAGAIN error
ssize_t ret = send(sockp[0], strs_to_write[snt], strlen(strs_to_write[snt]), 0);
if(++snt >= msgs)
close(sockp[0]);
CKERR("send error")
if (ret == 0) {
puts("Connection closed");
break;
}
if (ret > 0) {
// assuming that all bytes were written
// if ret != %sent bytes number%, send other bytes later
}
}
if (pfds[1].revents & POLLIN) {
// There is something to read
ssize_t ret = recv(sockp[1], buf, BUFSIZE, 0);
CKERR("receive error")
if (ret == 0) {
puts("Connection closed");
break;
}
if (ret > 0) {
printf("received str: %.*s\n", (int)ret, buf);
}
}
}
close(sockp[1]);
return EXIT_SUCCESS;
}
Seleccionar
Seleccionar es otra forma de realizar multiplexación de E / S. Una de sus ventajas es la existencia en la API de winsock. Además, en Linux, select () modifica el tiempo de espera para reflejar la cantidad de tiempo no dormido; La mayoría de las otras implementaciones no hacen esto. (POSIX.1 permite cualquier comportamiento).
Tanto poll y select tienen alternativas ppoll y pselect, que permiten manejar las señales entrantes durante la espera del evento. Y ambos se vuelven lentos con una gran cantidad de descriptores de archivos (cien y más), por lo que sería prudente elegir una plataforma específica, por ejemplo, epoll
en Linux y kqueue
en FreeBSD. O cambie a la API asíncrona (POSIX aio
por ejemplo, o algo específico como los puertos de finalización de E / S).
Select Call tiene el siguiente prototipo:
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
fd_set
es una matriz de descriptores de archivo de máscara de bits,
nfds
es el número máximo de todos los descriptores de archivo en set + 1.
Fragmento de trabajar con seleccionar:
fd_set active_fd_set, read_fd_set;
FD_ZERO (&active_fd_set); // set fd_set to zeros
FD_SET (sock, &active_fd_set); // add sock to the set
// # define FD_SETSIZE sock + 1
while (1) {
/* Block until input arrives on one or more active sockets. */
read_fd_set = active_fd_set; // read_fd_set gets overriden each time
if (select (FD_SETSIZE, &read_fd_set, NULL, NULL, NULL) < 0) {
// handle error
}
// Service all file descriptors with input pending.
for (i = 0; i < FD_SETSIZE; ++i) {
if (FD_ISSET (i, &read_fd_set)) {
// there is data for i
}
}
Tenga en cuenta que en la mayoría de las implementaciones POSIX, los descriptores de archivos asociados con los archivos en el disco están bloqueando. Así que escribir en un archivo, incluso si este archivo se configuró en writefds
, se bloquearía hasta que todos los bytes no se vuelquen en el disco