Vladislav Shpilevoy PRO
Database C developer at Tarantool. Backend C++ developer at VirtualMinds.
Системное программирование
Лекция 9:
Advanced IO. Неблокирующие IO операции. Блокировка файла. Мультиплексирование: select, poll, kqueue.
The presentation is outdated and not maintained anymore. Please, refer to the English version for the most actual slides.
Дедлайны:
Анонимные
int
pipe2(int pipefd[2], int flags);
void *
mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
int
mkfifo(const char *pathname, mode_t mode);
Именованные, стандарт XSI
int
semget(key_t key, int nsems, int semflg);
int
msgget(key_t key, int msgflg);
int
shmget(key_t key, size_t size, int shmflg);
Именованные, стандарт POSIX
sem_t *
sem_open(const char *name, int oflag, mode_t mode, unsigned int value);
int
socket(int domain, int type, int protocol);
int
bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
int
listen(int sockfd, int backlog);
int
connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
int
accept(int sockfd, struct sockaddr *addr,
socklen_t *addrlen);
Способы использования
int fd = socket();
connect(fd, remote_addr);
/* Ready to read/write fd. */
Подключение к уже именованному
Создание именованного без соединения
int fd = socket();
bind(fd, addr);
/** Ready to read/write fd. */
Создание именованного с соединением
int fd = socket();
bind(fd, addr);
listen(fd);
while(1) {
int remote_fd = accept(fd);
/*
* Ready to read/write
* remote_fd.
*/
}
Connect() создает на сервере парный сокет
int fd2 = socket();
bind(fd2, addr2);
/** Ready to read/write fd2. */
read/write
send/recv
sendto/recvfrom
Без connect() работают только пакетные типы сокетов, и нужно указывать адресатов руками
Server
Client
Client
Client
Client
New client
Как работать со многими клиентами?
int new_client_fd = accept(server_fd);
if (fork() == 0) {
/* Read/write this client. */
...
return 0;
}
/*
* Server continues accepting new
* clients.
*/
Выделять каждому процесс?
Выделять каждому поток?
int new_client_fd = accept(server_fd);
pthread_t tid;
/*
* Put new client into a separate
* thread.
*/
pthread_create(&tid, NULL,
work_with_client_f,
new_client_fd);
/*
* Server continues accepting new
* clients.
*/
Почему нельзя просто брать и читать из каждого клиента как в коде ниже?
Accept(), read(), write() заблокируют поток до появления нового клиента/новых данных. Остальные будут простаивать.
int *client_fds = NULL;
int client_count = 0;
while (1) {
int new_cli_fd = accept(server_fd);
add_new_client(&client_fds, &client_count, new_cli_fd);
for (int i = 0; i < client_count; ++i)
interact_with_client(client_fds[i]);
}
2 балла
O_NONBLOCK
int fd = open(file_name, flags | O_NONBLOCK);
int old_flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, old_flags | O_NONBLOCK);
ssize_t rc = read/write/accept/send/recv(fd, ...);
if (rc == -1 && (errno == EAGAIN || errno == EWOULDBLOCK))
/* No data to read, or space to write. */
Как установить флаг?
Как использовать?
или
Хороший код проверяет обе ошибки
void
make_fd_nonblocking(int fd)
{
int old_flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, old_flags | O_NONBLOCK);
}
int
main(int argc, const char **argv)
{
make_fd_nonblocking(STDIN_FILENO);
int value = 0;
int rc = scanf("%d", &value);
printf("scanf rc = %d\n", rc);
printf("scanf error = %s\n", strerror(errno));
rc = read(STDIN_FILENO, &value, sizeof(value));
printf("read rc = %d\n", rc);
printf("read error = %s\n", strerror(errno));
while (1) {
rc = scanf("%d", &value);
if (rc > 0 || (errno != EAGAIN &&
errno != EWOULDBLOCK))
break;
}
printf("value = %d\n", value);
return 0;
}
Функция делает любой дескриптор неблокирующим
Даже поток ввода можно сделать неблокирующим
Тогда scanf и read будут возвращать ошибку, пока нет ввода
Буду читать в цикле, пока не прочитается
$> gcc 1_nonblock.c
$> ./a.out
scanf rc = -1
scanf error = Resource
temporarily unavailable
read rc = -1
read error = Resource
temporarily unavailable
...
100
value = 100
#define LOCK_SH 1 /* Shared lock. */
#define LOCK_EX 2 /* Exclusive lock. */
#define LOCK_NB 4 /* Don't block when locking. */
#define LOCK_UN 8 /* Unlock. */
int
flock(int fd, int operation);
/* Shared lock. */
flock(fd, LOCK_SH);
/* Exclusive lock. */
flock(fd, LOCK_EX);
/* Do not block on flock(). */
flock(fd, LOCK_SH | LOCK_NB);
flock(fd, LOCK_EX | LOCK_NB);
/* Unlock. */
flock(fd, LOCK_UN);
Блокировка файла целиком
int main()
{
int fd = open("tmp.txt", O_CREAT | O_RDWR, S_IRWXU);
char cmd[100];
while (scanf("%s", cmd) > 0) {
int rc;
if (strcmp(cmd, "excl") == 0) {
rc = flock(fd, LOCK_EX);
} else if (strcmp(cmd, "exclnb") == 0) {
rc = flock(fd, LOCK_EX | LOCK_NB);
} else if (strcmp(cmd, "shared") == 0) {
rc = flock(fd, LOCK_SH);
} else if (strcmp(cmd, "sharednb") == 0) {
rc = flock(fd, LOCK_SH | LOCK_NB);
} else if (strcmp(cmd, "unlock") == 0) {
rc = flock(fd, LOCK_UN);
} else if (strcmp(cmd, "write") == 0) {
rc = write(fd, "data", 4);
} else {
printf("unknown command\n");
continue;
}
if (rc == -1)
printf("error = %s\n", strerror(errno));
else
printf("ok\n");
}
close(fd);
return 0;
}
Открываю/создаю файл
Можно выбрать любой лок, или снять его
$> gcc 2_flock.c
$> ./a.out
$> ./a.out
excl
ok
excl
...
unlock
ok
ok
unlock
ok
shared
ok
shared
ok
exclnb
error = Resource temporarily unavailable
unlock
ok
exclnb
ok
sharednb
error = Resource temporarily unavailable
^D
$>
sharednb
ok
$> ./a.out
write
ok
int
flock(int fd, int operation);
Недостатки:
/* cmd = ... */
#define F_GETLK /* Check if lock exists. */
#define F_SETLK /* Do nonblocking lock. */
#define F_SETLKW /* Do blocking lock. */
int
fcntl(int fd, int cmd, struct flock *lock_def);
struct flock {
off_t l_start;
off_t l_len;
pid_t l_pid;
short l_type;
short l_whence;
};
l_start, l_len
- начало и длина диапазона
l_whence
- откуда отсчитывать начало диапазона? С начала, конца, текущей позиции fd?
l_type
- взять лок на чтение (shared), на запись (exclusive), снять лок с диапазона
l_pid
- PID процесса, который держит лок, если такой есть
Файл
0
100
lock.l_start = 60;
lock.l_whence = SEEK_SET;
lock.l_start = 20;
lock.l_whence = SEEK_CUR;
lock.l_start = -40;
lock.l_whence = SEEK_END;
struct flock lock;
lock.l_len = 30;
lock.l_type = F_WRLCK;
fcntl(fd, F_SETLK, &lock);
start
0
100
fd
start
0
start
60
20
-40
100
40
Файл
100
struct flock lock;
lock.l_len = 30;
lock.l_type = F_WRLCK;
lock.l_start = 60;
lock.l_whence = SEEK_SET;
fcntl(fd, F_SETLK, &lock);
struct flock lock;
lock.l_len = 20;
lock.l_type = F_WRLCK;
lock.l_start = 10;
lock.l_whence = SEEK_SET;
fcntl(fd, F_SETLK, &lock);
struct flock lock;
lock.l_len = 20;
lock.l_type = F_WRLCK;
lock.l_start = 40;
lock.l_whence = SEEK_SET;
fcntl(fd, F_SETLK, &lock);
struct flock lock;
lock.l_len = 20;
lock.l_type = F_UNLCK;
lock.l_start = 50;
lock.l_whence = SEEK_SET;
fcntl(fd, F_SETLK, &lock);
60
90
10
30
40
50
70
int char_to_whence(char whence)
{
if (whence == 's')
return SEEK_SET;
return whence == 'e' ? SEEK_END : SEEK_CUR;
}
int char_to_type(char type)
{
if (type == 'r')
return F_RDLCK;
return type == 'w' ? F_WRLCK : F_UNLCK;
}
int do_lock(int fd, bool block, char *cmd)
{
char whence, type;
int start, len;
sscanf(cmd, "%c %c %d %d", &type, &whence, &start, &len);
printf("type = %c, whence = %c, start = %d, len = %d\n", type, whence,
start, len);
struct flock fl;
fl.l_type = char_to_type(type);
fl.l_whence = char_to_whence(whence);
fl.l_start = start;
fl.l_len = len;
return fcntl(fd, block ? F_SETLKW : F_SETLK, &fl);
}
Пример принимает строки вида:
<cmd> <type> <whence> <start> <len>
whence - s (set), e (end), c (cur)
type - r (read lock), w (write lock), u (unlock)
cmd - lock, lockb, unlock, getlock
Реализация команд установки/снятия лока
int
get_lock(int fd, char *cmd)
{
char whence, type;
int start, len;
sscanf(cmd, "%c %c %d %d", &type, &whence, &start, &len);
printf("type = %c, whence = %c, start = %d, len = %d\n", type, whence,
start, len);
struct flock fl;
fl.l_type = char_to_type(type);
fl.l_whence = char_to_whence(whence);
fl.l_start = start;
fl.l_len = len;
if (fcntl(fd, F_GETLK, &fl) == -1)
return -1;
if (fl.l_type == F_UNLCK)
printf("no lock on this region\n");
else
printf("process %d holds the lock\n", (int)fl.l_pid);
return 0;
}
Проверка, есть ли на диапазоне лок
Выбираю, какой диапазон проверить
Использую fcntl с командой F_GETLK - она в fl.l_pid положит pid процесса, держащего лок
Если диапазон пока свободен - fl.l_type будет изменен ядром на F_UNLCK
Иначе в fl.l_pid лежит валидный pid
int main()
{
printf("my pid = %d\n", (int) getpid());
int fd = open("tmp.txt", O_CREAT | O_RDWR, S_IRWXU);
char *buf = NULL;
size_t size = 0;
while (getline(&buf, &size, stdin) > 0) {
char *line = buf;
line[strlen(line) - 1] = 0;
int rc;
char *cmd = strsep(&line, " \n");
if (strcmp(cmd, "write") == 0) {
rc = write_symbols(fd, line);
} else if (strcmp(cmd, "lock") == 0) {
rc = do_lock(fd, false, line);
} else if (strcmp(cmd, "lockb") == 0) {
rc = do_lock(fd, true, line);
} else if (strcmp(cmd, "getlock") == 0) {
rc = get_lock(fd, line);
}
if (rc == -1)
printf("error = %s\n", strerror(errno));
else
printf("ok\n");
}
close(fd);
return 0;
}
Сама командная строка
Простая реализация write, чтобы чем-то заполнить файл
Локи/анлоки
$> gcc 2_flock.c
$> ./a.out
my pid = 95231
$> ./a.out
my pid = 95229
write 100 a
ok
0
100
lock w(rite) s(et) 10 20
ok
lock r(ead) s 40 20
ok
- write lock
- read lock
lock r s 50 30
ok
lock w s 20 30
error = Resource temporarily unavailable
lockb w s 20 30
...
lock u(nlock) s 20 10
ok
lock u(nlock) s 40 10
ok
ok
lock w s 100 0
ok
write 10 a
ok
110
lock w s 101 9
error = Resource temporarily unavailable
^C
$>
lock w s 100 10
ok
10
20
30
40
50
60
80
#define F_ULOCK /* Unlock locked sections. */
#define F_LOCK /* Lock a section for exclusive use. */
#define F_TLOCK /* Test and lock a section for exclusive use. */
#define F_TEST /* Test a section for locks by other processes. */
int
lockf(int fildes, int function, off_t size);
struct flock fl;
fl.l_start = 0;
fl.l_len = size;
fl.l_whence = SEEK_CUR;
if (function == F_TEST) {
fl.l_type = F_WRLCK;
if (fcntl(fd, F_GETLK, &fl) != 0)
return -1;
if (fl.l_type == F_UNLCK)
return 0;
errno = EAGAIN;
return -1;
}
if (function == F_ULOCK) {
fl.l_type = F_UNLCK;
return fcntl(fd, F_SETLK, &fl);
}
fl.l_type = F_WRLCK;
return fcntl(fd, function == F_LOCK ? F_SETLK : F_SETLKW, &fl);
Возможная реализация:
int
lockf(int fildes, int function, off_t size);
int
fcntl(int fd, int cmd, struct flock *lock_def);
int
flock(int fd, int operation);
Взаимосвязаны
Не наследуются при fork()
Принадлежат процессу, не потоку. В одном процессе можно брать лок много раз
Они все advisory
Как можно использовать эти локи в виде мьютекса?
1) использовать целый файл, как мьютекс, 2) использовать любой один диапазон файла (первый байт, например)
int
lockf(int fildes, int function, off_t size);
int
fcntl(int fd, int cmd, struct flock *lock_def);
int
flock(int fd, int operation);
1 балл
Server
Client
Client
Client
Client
Как понять, из какого сокета можно читать?
События, данные
void make_fd_nonblocking(int fd)
{
int old_flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, old_flags | O_NONBLOCK);
}
/* ... */
/* Make 'accept(server_fd)' non-blocking. */
make_fd_nonblocking(server_fd)
int *client_fds = NULL;
int client_count = 0;
while (1) {
int new_cli_fd = accept(server_fd);
if (new_cli_fd != -1) {
/* Make read/write(new_cli_fd) non-blocking. */
make_fd_nonblocking(new_cli_fd);
add_new_client(&client_fds, &client_count, new_cli_fd);
}
for (int i = 0; i < client_count; ++i)
interact_with_client(client_fds[i]);
}
Опрашивать всех по очереди? - polling
Лишние системные вызовы - трата CPU
Задержка обработки линейна от числа дескрипторов
Сделать все дескрипторы неблокирующими
Обходить по кругу и проверять EAGAIN, EWOULDBLOCK
Сокеты
Файлы
Терминалы
Фьютексы
void FD_CLR(fd, fd_set *fdset);
void FD_COPY(fd_set *fdset_orig, fd_set *fdset_copy);
int FD_ISSET(fd, fd_set *fdset);
void FD_SET(fd, fd_set *fdset);
void FD_ZERO(fd_set *fdset);
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *errorfds, struct timeval *timeout);
Три набора файловых дескрипторов: для отслеживания возможности читать, писать, исключений
Функции для создания и работы с одним fd_set - набором дескрипторов
Значение максимального файлового дескриптора во всех трех наборах
Select() блокируется до возникновения события на одном из дескрипторов
На событие вернется, стерев из fd_set тех, у кого событий нет
fd_set set;
FD_ZERO(&set);
FD_SET(file_desc1, &set);
FD_SET(file_desc2, &set);
select(MAX(file_desc1, file_desc2) + 1, &set,
NULL, NULL, NULL);
if (FD_ISSET(file_desc1, &set)) {
/* file_desc1 has data to read. */
read(file_desc1, buffer, buf_size);
/* ... */
}
if (FD_ISSET(file_desc2, &set)) {
/* ... */
}
int
main(int argc, const char **argv)
{
int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(12345);
inet_aton("127.0.0.1", &addr.sin_addr);
connect(sock, (struct sockaddr *) &addr, sizeof(addr));
int number;
while (scanf("%d", &number) > 0) {
if (write(sock, &number, sizeof(number)) == -1) {
printf("error = %s\n", strerror(errno));
continue;
}
printf("Sent %d\n", number);
number = 0;
int rc = read(sock, &number, sizeof(number));
if (rc == 0) {
printf("Closed connection\n");
break;
}
if (rc == -1)
printf("error = %s\n", strerror(errno));
else
printf("Received %d\n", number);
}
close(sock);
return 0;
}
Обычный клиент. Посылает числа, ему отвечают увеличенными на 1
int fill_fdset(fd_set *set, int *clients, int client_count, int server)
{
int max_fd = server;
FD_ZERO(set);
FD_SET(server, set);
for (int i = 0; i < client_count; ++i) {
FD_SET(clients[i], set);
if (clients[i] > max_fd)
max_fd = clients[i];
}
return max_fd;
}
int main(int argc, const char **argv)
{
int server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(12345);
inet_aton("127.0.0.1", &addr.sin_addr);
bind(server, (struct sockaddr *) &addr, sizeof(addr));
listen(server, 128);
int client_count = 0, *clients = NULL;
fd_set readset;
while(1) {
int max_fd = fill_fdset(&readset, clients, client_count, server);
struct timeval timeval;
timeval.tv_sec = 2;
timeval.tv_usec = 0;
Создание сервера, начало прослушки порта
Заполнение readset перед каждым select() заново
int nfds = select(max_fd + 1, &readset, NULL, NULL, &timeval);
if (nfds == 0) {
printf("Timeout\n");
continue;
}
if (FD_ISSET(server, &readset)) {
int client_sock = accept(server, NULL, NULL);
printf("New client\n");
client_count++;
clients = realloc(clients, client_count * sizeof(int));
clients[client_count - 1] = client_sock;
nfds--;
}
for (int i = 0; i < client_count && nfds > 0; ++i) {
if (! FD_ISSET(clients[i], &readset))
continue;
nfds--;
printf("Interact with fd %d\n", clients[i]);
int rc = interact(clients[i]);
if (rc == 0) {
printf("Client disconnected\n");
remove_client(&clients, &client_count, i);
}
}
}
close(server);
for (int i = 0; i < client_count; ++i)
close(clients[i]);
free(clients);
return 0;
}
Select() вернет, сколько дескрипторов имеют события. 0 - таймаут
Сначала проверю server - он особенный. На серверных сокетах новый клиент срабатывает как read событие
Каждого клиента проверить
Результат select - подсказка, которая позволит не сканировать каждый раз все дескрипторы на FD_ISSET
$> gcc 4_server_select.c -o server
$> ./server
$> gcc 4_client.c -o client
$> ./client
$> ./client
Timeout
Timeout
New client
New client
100
Sent 100
Received 101
Interact with fd 4
Received 100
Sent 101
200
Sent 200
Received 201
Interact with fd 5
Received 200
Sent 201
^C
$>
Interact with fd 5
Client disconnected
^C
$>
Interact with fd 4
Client disconnected
int
poll(struct pollfd fds[], nfds_t nfds, int timeout);
struct pollfd {
int fd; /* File descriptor. */
short events; /* Events to look for. */
short revents; /* Events returned. */
};
#define POLLERR /* Wait for exceptions. */
#define POLLIN /* Wait for ability to read. */
#define POLLOUT /* Wait for ability to write. */
/* POLLHUP, POLLNVAL, POLLPRI, POLLRDBAND, POLLRDNORM,
POLLWRBAND, POLLWRNORM */
struct pollfd fds[2];
fds[0].fd = file_desc1;
fds[0].events = POLLIN;
fds[1].fd = file_desc2;
fds[1].events = POLLIN | POLLOUT;
poll(fds, 2, 2000);
if (fds[0].revents | POLLIN)
/* Can safely read from file_desc1. */
if (fds[1].revents | POLLIN)
/* Can safely read from file_desc2. */
if (fds[1].revents | POLLOUT)
/* Can safely write to file_desc2. */
В каждом pollfd задается дескриптор, и что в нем хочется слушать
После возврата poll положит сюда реально произошедшие события
struct pollfd *fds = malloc(sizeof(fds[0]));
int fd_count = 1;
fds[0].fd = server;
fds[0].events = POLLIN;
while(1) {
int nfds = poll(fds, fd_count, 2000);
if (nfds == 0) {
printf("Timeout\n");
continue;
}
if ((fds[0].revents & POLLIN) != 0) {
int client_sock = accept(server, NULL, NULL);
printf("New client\n");
fd_count++;
fds = realloc(fds, fd_count * sizeof(fds[0]));
fds[fd_count - 1].fd = client_sock;
fds[fd_count - 1].events = POLLIN;
nfds--;
}
for (int i = 0; i < fd_count && nfds > 0; ++i) {
if ((fds[i].revents & POLLIN) == 0)
continue;
nfds--;
printf("Interact with fd %d\n", fds[i].fd);
if (interact(fds[i].fd) == 0) {
printf("Client disconnected\n");
remove_client(&fds, &fd_count, i);
}
}
}
Вместо массива int теперь массив pollfd
Poll, как и select, вернет, сколько pollfd имеют события
Добавление, как и раньше, через добавку в массив pollfd
У клиентов проверяем, у кого есть событие чтения
int
kqueue(void);
int
kevent(int kq,
const struct kevent *changelist, int nchanges,
struct kevent *eventlist, int nevents,
const struct timespec *timeout);
struct kevent {
uintptr_t ident; /* Identifier for this event. */
int16_t filter; /* Filter for event. */
uint16_t flags; /* General flags. */
uint32_t fflags; /* Filter-specific flags. */
intptr_t data; /* Filter-specific data. */
void *udata; /* Opaque user data identifier. */
};
EV_SET(&kev, ident, filter, flags, fflags, data, udata);
Kernel Events Queue
Создает очередь и возвращает ее "файловый" дескриптор
Управление очередью и извлечение событий
Список изменений вида "отслеживать новое событие", "перестать отслеживать другое"
Сюда будут положены случившиеся события
kevent может следить за многими системными событиями. В случае IO тут файловый дескриптор
Что отслеживать? Чтение, запись, исключения, закрытие ... ?
Действие - удалить, добавить, поменять события
Сюда можно положить любые данные, и они будут без изменений появляться вместе с этим событием
int kq = kqueue();
Создание очереди
struct kevent new_ev;
EV_SET(&new_ev, fd, EVFILT_READ/WRITE/..., EV_ADD, 0, 0, 0);
kevent(kq, &new_ev, 1, 0, 0, NULL);
Отслеживание нового события на дескрипторе fd. На каждое событие (read, write) нужен свой kevent
Удаление события из отслеживания
struct kevent old_ev;
EV_SET(&old_ev, fd, EVFILT_READ/WRITE/..., EV_DELETE, 0, 0, 0);
kevent(kq, &old_ev, 1, 0, 0, NULL);
Получение случившихся событий
struct kevent happened_ev;
kevent(kq, NULL, 0, &happened_ev, 1, NULL);
if (happened_ev.filter == EVFILT_READ)
/* Can safely read from happened_ev.ident. */
if (happened_ev.filter == EVFILT_WRITE)
/* Can safely write to happened_ev.ident. */
int kq = kqueue();
struct kevent new_ev;
EV_SET(&new_ev, server, EVFILT_READ, EV_ADD, 0, 0, 0);
kevent(kq, &new_ev, 1, 0, 0, NULL);
struct timespec timeout;
timeout.tv_sec = 2;
timeout.tv_nsec = 0;
while(1) {
if (kevent(kq, NULL, 0, &new_ev, 1, &timeout) == 0) {
printf("Timeout\n");
continue;
}
if (new_ev.ident == server) {
int client_sock = accept(server, NULL, NULL);
printf("New client\n");
EV_SET(&new_ev, client_sock, EVFILT_READ, EV_ADD, 0, 0, 0);
kevent(kq, &new_ev, 1, 0, 0, NULL);
} else {
printf("Interact with fd %d\n", (int)new_ev.ident);
interact(new_ev.ident);
if ((new_ev.flags & EV_EOF) != 0) {
printf("Client disconnected\n");
close(new_ev.ident);
}
}
}
close(kq);
Массива больше нет, теперь очередь в ядре
Добавляю на прослушку "чтения" с серверного сокета
События буду получать по одному. Можно было передать массив, но можно и одно событие
Так как возвращается не массив, по индексу больше не понятно, какое событие к кому относится. Здесь использую сравнение с new_ev.ident
Регистрирую прослушку чтений с нового клиента
В отличие от select, poll, на закрытие дескриптора явно выдается флаг EV_EOF
Очередь уничтожается обычным close()
int
epoll_create(int size);
int
epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int
epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event {
uint32_t events; /* Epoll events. */
epoll_data_t data; /* User data variable. */
};
Создание очереди событий
Управление очередью: добавление дескрипторов, удаление, изменение
Ожидание событий
С каждым файловым дескриптором ассоциируется набор нужных от него событий
И произвольные пользовательские данные, как в kevent
int ep = epoll_create(12345);
Создание очереди. Ее размер сейчас игнорируется, а в API остался с давних времен, когда было не так
struct epoll_event new_ev;
new_ev.events = EPOLLIN | EPOLLOUT;
new_ev.data.fd/u32/u64/ptr = my_any_data;
epoll_ctl(ep, EPOLL_CTL_ADD, file_desc, &new_ev);
Добавление дескриптора на отслеживание событий
Удаление дескриптора из очереди
epoll_ctl(ep, EPOLL_CTL_DEL, file_desc, NULL);
int ep = epoll_create(1);
struct epoll_event new_ev;
new_ev.data.fd = server;
new_ev.events = EPOLLIN;
epoll_ctl(ep, EPOLL_CTL_ADD, server, &new_ev);
while(1) {
if (epoll_wait(ep, &new_ev, 1, 2000) == 0) {
printf("Timeout\n");
continue;
}
if (new_ev.data.fd == server) {
int client_sock = accept(server, NULL, NULL);
printf("New client\n");
new_ev.data.fd = client_sock;
new_ev.events = EPOLLIN;
epoll_ctl(ep, EPOLL_CTL_ADD, client_sock, &new_ev);
} else {
printf("Interact with fd %d\n", (int)new_ev.data.fd);
if (interact(new_ev.data.fd) == 0) {
printf("Client disconnected\n");
close(new_ev.data.fd);
epoll_ctl(ep, EPOLL_CTL_DEL, new_ev.data.fd,
NULL);
}
}
}
close(ep);
Создается очередь, по аналогии с kqueue
Регистрируем сервер с ожиданием событий "чтения". Пользовательские данные - сам сокет
Ждем события
Использую пользовательские данные, куда я положил дескриптор, чтобы определить источник события
Каждый новый клиент тоже регистрируется
Закрытые дескрипторы приходится самому удалять через epoll_ctl
Уничтожается тоже через close()
int aio_read(struct aiocb *aiocbp);
int aio_write(struct aiocb *aiocbp);
int aio_error(const struct aiocb *aiocbp);
ssize_t aio_return(struct aiocb *aiocbp);
int aio_suspend(const struct aiocb *const list[],
int nent, const struct timespec *timeout);
int aio_cancel(int fildes, struct aiocb *aiocbp);
int lio_listio(int mode, struct aiocb *const aiocb_list[],
int nitems, struct sigevent *sevp);
struct aiocb {
int aio_fildes; /* File descriptor. */
off_t aio_offset; /* File offset. */
volatile void *aio_buf; /* Location of buffer. */
size_t aio_nbytes; /* Length of transfer. */
int aio_reqprio; /* Request priority. */
struct sigevent aio_sigevent; /* Notification method. */
int aio_lio_opcode; /* lio_listio specific. */
};
char buffer[1024];
struct aiocb cb;
memset(&cb, 0, sizeof(cb));
cb.aio_fieldes = fd;
cb.aio_offset = lseek(fd, 0, SEEK_CUR);
cb.aio_buf = buffer;
cb.aio_nbytes = sizeof(buffer);
aio_read(&cb);
/* Do some non-related work ... */
while (aio_error(&cb) == EINPROGRESS) {};
int result = aio_return(&cb);
char buffer[1024];
struct aiocb cb;
memset(&cb, 0, sizeof(cb));
cb.aio_fieldes = fd;
cb.aio_offset = lseek(fd, 0, SEEK_CUR);
cb.aio_buf = buffer;
cb.aio_nbytes = sizeof(buffer);
aio_read(&cb);
/* Do some non-related work ... */
aio_suspend(&cb, 1, NULL);
int result = aio_return(&cb);
=
char buffer[1024];
read(fd, buffer, sizeof(buffer));
const int chunk_size = 1024;
char buffer[chunk_size * 3];
struct aiocb cb[3];
memset(cb, 0, sizeof(cb));
for (int i = 0; i < 3; ++i) {
cb[i].aio_fieldes = fd;
cb[i].aio_nbytes = chunk_size;
cb[i].aio_lio_opcode = LIO_READ;
}
cb[1].aio_offset = lseek(fd, 0, SEEK_CUR);
cb[2].aio_offset =
cb[1].aio_offset + chunk_size;
cb[3].aio_offset =
cb[2].aio_offset + chunk_size;
cb[1].aio_buf = buffer;
cb[2].aio_buf = buffer + chunk_size;
cb[3].aio_buf = buffer + chunk_size * 2;
lio_listio(LIO_NOWAIT, cb, 3, NULL);
/* Do some non-related work ... */
aio_suspend(cb, 3, NULL);
int result1 = aio_return(&cb[1]);
int result2 = aio_return(&cb[2]);
int result3 = aio_return(&cb[3]);
const int chunk_size = 1024;
char buffer[chunk_size * 3];
read(fd, buffer, chunk_size);
read(fd, buffer + chunk_size,
chunk_size);
read(fd, buffer + 2 * chunk_size,
chunk_size);
=
Почему не надо использовать?
Что использовать вместо?
ssize_t
writev(int fildes, const struct iovec *iov, int iovcnt);
ssize_t
readv(int d, const struct iovec *iov, int iovcnt);
struct iovec {
char *iov_base;
size_t iov_len;
};
char buffer[2][512];
struct iovec vec[2];
vec[0].iov_base = buffer[0];
vec[0].iov_len = sizeof(buffer[0]);
vec[1].iov_base = buffer[1];
vec[1].iov_len = sizeof(buffer[1]);
writev(fd, vec, 2);
=
char buffer[2][512];
write(fd, buffer[0], sizeof(buffer[0]));
write(fd, buffer[1], sizeof(buffer[1]));
Экономия числа системных вызовов, и не нужно копировать все в один буфер
В следующий раз:
Пользователи и группы. Вход в систему. Real and effective user. Права доступа у процессов, файлов. Сессии. Демонизация процесса.
By Vladislav Shpilevoy
Advanced IO. Неблокирующие IO операции. Блокировка файла: flock, lockf, fcntl. Multiplexed IO: select, poll, kqueue. Async IO: aio_read/write.
Database C developer at Tarantool. Backend C++ developer at VirtualMinds.