Дар ин мақола, мо ба василаҳои реактори I/O ва чӣ тавр кор кардани он дида мебароем, татбиқро дар камтар аз 200 сатри код нависед ва раванди оддии сервери HTTP-ро беш аз 40 миллион дархост дар як дақиқа эҷод кунед.
Пешгуфтор
Мақола барои фаҳмидани кори реактори I/O ва аз ин рӯ фаҳмидани хатарҳо ҳангоми истифодаи он навишта шудааст.
Барои фаҳмидани мақола дониши асосҳо лозим аст. Забони C ва баъзе таҷриба дар таҳияи барномаҳои шабакавӣ.
Ҳама кодҳо бо забони C ба таври қатъӣ мувофиқи (Огоҳӣ: PDF дароз) ба стандарти C11 барои Linux ва дастрас GitHub.
Чаро ин зарур аст?
Бо маъруфияти афзояндаи Интернет, веб-серверҳо лозим омад, ки шумораи зиёди пайвастҳоро дар як вақт идора кунанд ва аз ин рӯ, ду равиш санҷида шуданд: бастани I/O дар шумораи зиёди риштаҳои ОС ва баста нашудани I/O дар якҷоягӣ бо системаи огоҳии рӯйдодҳо, ки онро "интихоби система" низ меноманд (epoll/навбат/IOCP/ва ғайра).
Равиши аввал эҷоди риштаи нави OS барои ҳар як пайвасти воридотӣ иборат буд. Камбудии он миқёспазирии суст аст: системаи оператсионӣ бояд бисёр чизҳоро амалӣ кунад гузаришҳои контекстӣ и зангҳои системавӣ. Онҳо амалиётҳои гаронбаҳо мебошанд ва метавонанд ба нарасидани RAM-и ройгон бо шумораи таъсирбахши пайвастҳо оварда расонанд.
Коркарди рӯйдодҳо функсияест, ки ҳангоми қабули ҳодиса аз ҷониби реактори I/O даъват карда мешавад, ки пас аз он амалиёти воридот ва баромади бебандро иҷро мекунад.
Қайд кардан муҳим аст, ки реактори I/O аз рӯи таърифи як ришта аст, аммо ҳеҷ чиз барои истифодаи консепсия дар муҳити чанд ришта бо таносуби 1 ришта: 1 реактор монеъ намешавад ва ба ин васила ҳамаи ядроҳои CPU коркард карда мешавад.
Реализация
Мо интерфейси умумиро дар файл ҷойгир мекунем reactor.h, ва татбики - дар reactor.c. reactor.h аз эълонхои зерин иборат аст:
Нишон додани эъломияҳо дар reactor.h
typedef struct reactor Reactor;
/*
* Указатель на функцию, которая будет вызываться I/O реактором при поступлении
* события от системного селектора.
*/
typedef void (*Callback)(void *arg, int fd, uint32_t events);
/*
* Возвращает `NULL` в случае ошибки, не-`NULL` указатель на `Reactor` в
* противном случае.
*/
Reactor *reactor_new(void);
/*
* Освобождает системный селектор, все зарегистрированные сокеты в данный момент
* времени и сам I/O реактор.
*
* Следующие функции возвращают -1 в случае ошибки, 0 в случае успеха.
*/
int reactor_destroy(Reactor *reactor);
int reactor_register(const Reactor *reactor, int fd, uint32_t interest,
Callback callback, void *callback_arg);
int reactor_deregister(const Reactor *reactor, int fd);
int reactor_reregister(const Reactor *reactor, int fd, uint32_t interest,
Callback callback, void *callback_arg);
/*
* Запускает цикл событий с тайм-аутом `timeout`.
*
* Эта функция передаст управление вызывающему коду если отведённое время вышло
* или/и при отсутствии зарегистрированных сокетов.
*/
int reactor_run(const Reactor *reactor, time_t timeout);
Сохтори реактори I/O иборат аст аз тавсифкунандаи файл интихобкунанда epoll и мизҳои ҳашGHashTable, ки хар як розеткаро ба CallbackData (сохтори коркардкунандаи ҳодиса ва далели корбар барои он).
Функсияҳо reactor_register, reactor_deregister и reactor_reregister рӯйхати розеткаҳои таваҷҷӯҳ ва коркардкунандагони рӯйдодҳои мувофиқро дар селектори система ва ҷадвали hash навсозӣ кунед.
файл http_server.c (сервери риштаи ягона) файлро дар бар мегирад common.h, ки прототипҳои функсияҳои зеринро дар бар мегирад:
Нишон додани прототипҳои функсия дар умумӣ.h
/*
* Обработчик событий, который вызовется после того, как сокет будет
* готов принять новое соединение.
*/
static void on_accept(void *arg, int fd, uint32_t events);
/*
* Обработчик событий, который вызовется после того, как сокет будет
* готов отправить HTTP ответ.
*/
static void on_send(void *arg, int fd, uint32_t events);
/*
* Обработчик событий, который вызовется после того, как сокет будет
* готов принять часть HTTP запроса.
*/
static void on_recv(void *arg, int fd, uint32_t events);
/*
* Переводит входящее соединение в неблокирующий режим.
*/
static void set_nonblocking(int fd);
/*
* Печатает переданные аргументы в stderr и выходит из процесса с
* кодом `EXIT_FAILURE`.
*/
static noreturn void fail(const char *format, ...);
/*
* Возвращает файловый дескриптор сокета, способного принимать новые
* TCP соединения.
*/
static int new_server(bool reuse_port);
Макроси функсионалӣ низ тавсиф шудааст SAFE_CALL() ва функсия муайян карда мешавад fail(). Макрос арзиши ифодаро бо хато муқоиса мекунад ва агар шарт дуруст бошад, функсияро даъват мекунад fail():
#define SAFE_CALL(call, error)
do {
if ((call) == error) {
fail("%s", #call);
}
} while (false)
функсия fail() далелҳои гузаштаро ба терминал чоп мекунад (масалан printf()) ва барномаро бо код қатъ мекунад EXIT_FAILURE:
on_recv() маълумотро аз пайвастшавӣ то пурра қабул шудани дархости HTTP мехонад ва сипас коркардкунандаро сабт мекунад on_send() барои фиристодани посухи HTTP. Агар муштарӣ пайвастро вайрон кунад, розетка аз қайд хориҷ карда мешавад ва бо истифода аз он баста мешавад close().
Нишон додани функсия on_recv()
static void on_recv(void *arg, int fd, uint32_t events) {
RequestBuffer *buffer = arg;
// Принимаем входные данные до тех пор, что recv возвратит 0 или ошибку
ssize_t nread;
while ((nread = recv(fd, buffer->data + buffer->size,
REQUEST_BUFFER_CAPACITY - buffer->size, 0)) > 0)
buffer->size += nread;
// Клиент оборвал соединение
if (nread == 0) {
SAFE_CALL(reactor_deregister(reactor, fd), -1);
SAFE_CALL(close(fd), -1);
request_buffer_destroy(buffer);
return;
}
// read вернул ошибку, отличную от ошибки, при которой вызов заблокирует
// поток
if (errno != EAGAIN && errno != EWOULDBLOCK) {
request_buffer_destroy(buffer);
fail("read");
}
// Получен полный HTTP запрос от клиента. Теперь регистрируем обработчика
// событий для отправки данных
if (request_buffer_is_complete(buffer)) {
request_buffer_clear(buffer);
SAFE_CALL(reactor_reregister(reactor, fd, EPOLLOUT, on_send, buffer),
-1);
}
}
Дастури рӯйдодҳо on_send() пас аз тавлиди OS ҳодиса даъват карда мешавад EPOLLOUT, маънои онро дорад, ки пайвастшавӣ ба қайд гирифта шудааст on_recv(), барои фиристодани маълумот омода аст. Ин функсия посухи HTTP-ро бо HTML бо тасвир ба муштарӣ мефиристад ва сипас коркардкунандаи рӯйдодро ба он бармегардонад on_recv().
Ва ниҳоят, дар файл http_server.c, дар вазифа main() мо бо истифода аз реактори I/O эҷод мекунем reactor_new(), васлаки сервер эҷод кунед ва онро сабт кунед, реакторро истифода баред reactor_run() маҳз як дақиқа, ва он гоҳ мо захираҳоро озод мекунем ва аз барнома мебароем.
Сервери як риштаи мо тавонист беш аз 11 миллион дархостро дар як дақиқа аз 100 пайваст коркард кунад. Натичаи бад нест, аммо онро бехтар кардан мумкин аст?
Сервери бисёрсоҳавӣ
Тавре ки дар боло зикр гардид, реактори I/O-ро дар риштаҳои алоҳида сохтан мумкин аст ва ба ин васила ҳамаи ядроҳои CPU-ро истифода мебарад. Биёед ин равишро дар амал татбиқ кунем:
Амалия нишон медихад, ки дар аксар мавридхо сарборй якранг аст, ки ин метавонад боиси канда шудани як ришта ва дигаре бо кор банд бошад.
Агар як коркардкунандаи рӯйдод риштаро маҳкам кунад, худи селектори система низ баста мешавад, ки ин метавонад боиси пайдо шудани хатогиҳои душвор гардад.