POSIX
Multipleksowanie wejścia / wyjścia
Szukaj…
Wprowadzenie
We / Wy może być blokujące / nieblokujące i synchroniczne / asynchroniczne. POSIX API zapewnia synchroniczne API blokujące (np. Klasyczne wywołania odczytu, zapisu, wysyłania, recv), synchroniczne nieblokujące API (te same funkcje, deskryptory plików otwarte z flagą O_NONBLOCK
i wywołania multipleksowania IO) oraz asynchroniczne API (funkcje zaczynające się od aio_
).
Synchroniczne API jest zwykle używane ze stylem „jeden wątek / proces na fd”. To jest straszne dla zasobów. Nieblokujący interfejs API pozwala na pracę z zestawem fds w jednym wątku.
Głosowanie
W tym przykładzie tworzymy parę połączonych gniazd i wysyłamy 4 ciągi znaków między sobą i drukujemy otrzymane ciągi do konsoli. Pamiętaj, że liczba wywołań wysyłania może nie być równa liczbie wywołań 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;
}
Wybierz
Select to kolejny sposób na multipleksowanie I / O. Jedną z jego zalet jest istnienie interfejsu API winsock. Ponadto w Linuksie select () modyfikuje limit czasu, aby odzwierciedlić ilość czasu, który nie spał; większość innych implementacji tego nie robi. (POSIX.1 zezwala na oba zachowania).
Zarówno poll, jak i select mają alternatywy ppoll i pselect, które umożliwiają obsługę sygnałów przychodzących podczas oczekiwania na zdarzenie. Oba stają się wolne z ogromną ilością deskryptorów plików (sto i więcej), więc rozsądnie byłoby wybrać wywołanie specyficzne dla platformy, np. epoll
na Linuksie i kqueue
na FreeBSD. Lub przejdź do asynchronicznego interfejsu API (POSIX aio
np. Lub czegoś konkretnego, takiego jak porty IO Completion).
Wybierz połączenie ma następujący prototyp:
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
fd_set
to tablica deskryptorów plików z fd_set
,
nfds
to maksymalna liczba wszystkich deskryptorów plików w zestawie + 1.
Fragment pracy z select:
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
}
}
Zauważ, że w większości implementacji POSIX deskryptory plików powiązane z plikami na dysku blokują. Więc zapis do pliku, nawet jeśli ten plik został ustawiony w writefds
, blokowałby, dopóki wszystkie bajty nie zostaną zrzucone na dysk