Ninu àpilẹkọ yii, a yoo wo awọn ins ati awọn ita ti I/O reactor ati bii o ṣe n ṣiṣẹ, kọ imuse ni o kere ju awọn laini koodu 200, ati ṣe ilana olupin HTTP ti o rọrun lori awọn ibeere 40 million / min.
Ọrọ iṣaaju
A kọ nkan naa lati ṣe iranlọwọ lati loye iṣẹ ṣiṣe ti riakito I / O, ati nitorinaa loye awọn eewu nigba lilo rẹ.
Oye awọn ipilẹ ni a nilo lati ni oye nkan naa. C ede ati iriri diẹ ninu idagbasoke ohun elo nẹtiwọki.
Gbogbo koodu ti kọ ni ede C ni muna ni ibamu si (ṣọra: gun PDF) to C11 bošewa fun Linux ati ki o wa lori GitHub.
Kini idi ti eyi nilo?
Pẹlu olokiki ti Intanẹẹti ti ndagba, awọn olupin wẹẹbu bẹrẹ lati nilo lati mu nọmba nla ti awọn asopọ ni nigbakannaa, ati nitorinaa awọn ọna meji ni a gbiyanju: didi I / O lori nọmba nla ti awọn okun OS ati ti kii ṣe idilọwọ I / O ni apapo pẹlu eto ifitonileti iṣẹlẹ kan, ti a tun pe ni “oluyan eto” (epoll/kqueue/IOCP/ati be be lo).
Ọna akọkọ jẹ pẹlu ṣiṣẹda okun OS tuntun fun asopọ ti nwọle kọọkan. Alailanfani rẹ jẹ iwọn ti ko dara: ẹrọ ṣiṣe yoo ni lati ṣe ọpọlọpọ awọn iyipada ti o tọ и awọn ipe eto. Wọn jẹ awọn iṣẹ ṣiṣe gbowolori ati pe o le ja si aini ti Ramu ọfẹ pẹlu nọmba iwunilori ti awọn asopọ.
Awọn títúnṣe version ifojusi ti o wa titi nọmba ti awon (adagun odo okun), nitorinaa idilọwọ eto naa lati iṣẹyun ipaniyan, ṣugbọn ni akoko kanna ti n ṣafihan iṣoro tuntun kan: ti adagun okun kan ti dina lọwọlọwọ nipasẹ awọn iṣẹ ṣiṣe kika gigun, lẹhinna awọn iho miiran ti o ni anfani lati gba data tẹlẹ kii yoo ni anfani lati ṣe bẹ.
Ọna keji lo eto iwifunni iṣẹlẹ (oluyan eto) ti a pese nipasẹ OS. Nkan yii n jiroro lori iru yiyan eto ti o wọpọ julọ, ti o da lori awọn titaniji (awọn iṣẹlẹ, awọn iwifunni) nipa imurasilẹ fun awọn iṣẹ I / O, kuku ju lori awọn iwifunni nipa ipari wọn. Apeere irọrun ti lilo rẹ le jẹ aṣoju nipasẹ aworan atọka bulọọki atẹle:
Iyatọ laarin awọn ọna wọnyi jẹ bi atẹle:
Ìdènà I/O mosi da duro olumulo sisan titititi OS jẹ daradara defragments ti nwọle IP awọn apo-iwe si ṣiṣan baiti (TCP, gbigba data) tabi kii yoo ni aaye to wa ninu awọn ifibu kikọ inu fun fifiranṣẹ atẹle nipasẹ NIC (firanṣẹ data).
Aṣayan eto asiko lehin asiko leti awọn eto ti awọn OS tẹlẹ defragmented IP awọn apo-iwe (TCP, data gbigba) tabi to aaye ninu ti abẹnu kikọ buffers tẹlẹ wa (fifiranṣẹ data).
Lati ṣe akopọ, fifipamọ okun OS kan fun I / O kọọkan jẹ egbin ti agbara iširo, nitori ni otitọ, awọn okun ko ṣe iṣẹ ti o wulo (eyi ni ibiti ọrọ naa ti wa. "Idaduro software"). Aṣayan eto yanju iṣoro yii, gbigba eto olumulo laaye lati lo awọn orisun Sipiyu pupọ diẹ sii ni ọrọ-aje.
Mo / Eyin riakito awoṣe
Awọn riakito I/O n ṣiṣẹ bi Layer laarin oluyan eto ati koodu olumulo. Ilana ti iṣiṣẹ rẹ jẹ apejuwe nipasẹ aworan atọka wọnyi:
Jẹ ki n ran ọ leti pe iṣẹlẹ kan jẹ ifitonileti kan pe iho kan ni anfani lati ṣe iṣẹ I/O ti kii ṣe dina.
Olutọju iṣẹlẹ jẹ iṣẹ ti a npe ni nipasẹ I/O reactor nigbati iṣẹlẹ ba ti gba, eyiti o ṣe iṣẹ I/O ti kii ṣe idilọwọ.
O ṣe pataki lati ṣe akiyesi pe ohun riakito I / O jẹ nipasẹ asọye nikan-asapo, ṣugbọn ko si ohun ti o dẹkun ero naa lati lo ni agbegbe ti o ni asapo pupọ ni ipin ti okun 1: 1 reactor, nitorinaa atunlo gbogbo awọn ohun kohun Sipiyu.
Imuse
A yoo gbe wiwo gbogbo eniyan sinu faili kan reactor.h, ati imuse - ni reactor.c. reactor.h yoo ni awọn ikede wọnyi:
Ṣe afihan awọn ikede ni 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 / Eyin riakito be oriširiši faili apejuwe yiyan epoll и elile tabiliGHashTable, eyi ti maapu kọọkan iho to CallbackData (ẹya ti oluṣakoso iṣẹlẹ ati ariyanjiyan olumulo fun rẹ).
Jọwọ ṣe akiyesi pe a ti mu agbara lati mu ṣiṣẹ iru ti ko pe gẹgẹ bi atọka. IN reactor.h a kede awọn be reactorati ninu reactor.c a ṣe alaye rẹ, nitorinaa idilọwọ olumulo lati yi awọn aaye rẹ pada ni gbangba. Eyi jẹ ọkan ninu awọn awoṣe nọmbafoonu data, eyi ti o ni ṣoki ni ibamu si imọ-ọrọ C.
Awọn iṣẹ reactor_register, reactor_deregister и reactor_reregister ṣe imudojuiwọn atokọ ti awọn iho ti iwulo ati awọn olutọju iṣẹlẹ ti o baamu ni yiyan eto ati tabili hash.
Lẹhin ti I/O riakito ti intercepted awọn iṣẹlẹ pẹlu awọn apejuwe fd, o pe oluṣakoso iṣẹlẹ ti o baamu, eyiti o kọja fd, bit boju ti ipilẹṣẹ iṣẹlẹ ati olumulo ijuboluwole si void.
Ṣe afihan iṣẹ reactor_run ().
int reactor_run(const Reactor *reactor, time_t timeout) {
int result;
struct epoll_event *events;
if ((events = calloc(MAX_EVENTS, sizeof(*events))) == NULL)
abort();
time_t start = time(NULL);
while (true) {
time_t passed = time(NULL) - start;
int nfds =
epoll_wait(reactor->epoll_fd, events, MAX_EVENTS, timeout - passed);
switch (nfds) {
// Ошибка
case -1:
perror("epoll_wait");
result = -1;
goto cleanup;
// Время вышло
case 0:
result = 0;
goto cleanup;
// Успешная операция
default:
// Вызвать обработчиков событий
for (int i = 0; i < nfds; i++) {
int fd = events[i].data.fd;
CallbackData *callback =
g_hash_table_lookup(reactor->table, &fd);
callback->callback(callback->arg, fd, events[i].events);
}
}
}
cleanup:
free(events);
return result;
}
Lati ṣe akopọ, ẹwọn awọn ipe iṣẹ ni koodu olumulo yoo gba fọọmu atẹle:
Nikan asapo olupin
Lati le ṣe idanwo riakito I/O labẹ ẹru giga, a yoo kọ olupin wẹẹbu HTTP ti o rọrun ti o dahun si eyikeyi ibeere pẹlu aworan kan.
Itọkasi iyara si ilana HTTP
HTTP - Eyi ni ilana naa ipele ohun elo, ni akọkọ ti a lo fun ibaraenisepo olupin-kiri.
HTTP le ni irọrun lo lori gbigbe Ilana TCP, fifiranṣẹ ati gbigba awọn ifiranṣẹ ni ọna kika kan pato sipesifikesonu.
CRLF jẹ ọkọọkan awọn ohun kikọ meji: r и n, yiya sọtọ laini akọkọ ti ibeere, awọn akọle ati data.
<КОМАНДА> - ọkan ninu CONNECT, DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT, TRACE. Ẹrọ aṣawakiri yoo fi aṣẹ ranṣẹ si olupin wa GET, itumo "Firanṣẹ awọn akoonu inu faili naa."
<URI> - aṣọ awọn oluşewadi idamo. Fun apẹẹrẹ, ti URI = /index.html, lẹhinna alabara beere oju-iwe akọkọ ti aaye naa.
<ВЕРСИЯ HTTP> - ẹya ti Ilana HTTP ni ọna kika HTTP/X.Y. Awọn julọ commonly lo version loni ni HTTP/1.1.
<ЗАГОЛОВОК N> jẹ bata-iye bọtini ni ọna kika <КЛЮЧ>: <ЗНАЧЕНИЕ>, ranṣẹ si olupin fun itupalẹ siwaju sii.
<ДАННЫЕ> - data ti o nilo nipasẹ olupin lati ṣe iṣẹ naa. Nigbagbogbo o rọrun JSON tabi eyikeyi miiran kika.
<КОД СТАТУСА> jẹ nọmba ti o nsoju abajade iṣẹ naa. Olupin wa yoo da ipo 200 pada nigbagbogbo (isẹ aṣeyọri).
<ОПИСАНИЕ СТАТУСА> - oniduro okun ti koodu ipo. Fun koodu ipo 200 eyi ni OK.
<ЗАГОЛОВОК N> - akọsori ti ọna kika kanna bi ninu ibeere naa. A yoo da awọn akọle pada Content-Length (iwọn faili) ati Content-Type: text/html (pada data iru).
<ДАННЫЕ> - data ti olumulo beere. Ninu ọran wa, eyi ni ọna si aworan ni HTML.
Ọna http_server.c (Olupin ti o tẹle nikan) pẹlu faili common.h, eyiti o ni awọn apẹrẹ iṣẹ wọnyi:
Ṣe afihan awọn apẹrẹ iṣẹ ni wọpọ.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);
Makiro iṣẹ-ṣiṣe tun ṣe apejuwe SAFE_CALL() ati awọn iṣẹ ti wa ni telẹ fail(). Makiro ṣe afiwe iye ti ikosile pẹlu aṣiṣe, ati pe ti ipo naa ba jẹ otitọ, pe iṣẹ naa fail():
#define SAFE_CALL(call, error)
do {
if ((call) == error) {
fail("%s", #call);
}
} while (false)
Išẹ fail() tẹjade awọn ariyanjiyan ti o kọja si ebute (bii printf()) ati ki o fopin si eto pẹlu koodu EXIT_FAILURE:
Išẹ new_server() da pada apejuwe faili ti iho "olupin" ti a ṣẹda nipasẹ awọn ipe eto socket(), bind() и listen() ati agbara lati gba awọn asopọ ti nwọle ni ipo ti kii ṣe idinamọ.
Ṣe akiyesi pe iho ti wa ni ipilẹṣẹ ni ibẹrẹ ti kii ṣe idinamọ nipa lilo asia SOCK_NONBLOCKnitorinaa ninu iṣẹ naa on_accept() (ka siwaju) eto ipe accept() ko da awọn o tẹle ipaniyan.
ti o ba ti reuse_port dogba true, lẹhinna iṣẹ yii yoo tunto iho pẹlu aṣayan SO_REUSEPORT nipasẹ setsockopt()lati lo ibudo kanna ni agbegbe olona-asapo (wo apakan "Olupin-asapo olupin").
Iṣẹlẹ Handler on_accept() ti a npe ni lẹhin ti awọn OS ipilẹṣẹ iṣẹlẹ EPOLLIN, ninu apere yi afipamo pe awọn titun asopọ le ti wa ni gba. on_accept() gba asopọ tuntun kan, yipada si ipo ti kii ṣe idinamọ ati forukọsilẹ pẹlu oluṣakoso iṣẹlẹ on_recv() ninu ohun I/O riakito.
Iṣẹlẹ Handler on_recv() ti a npe ni lẹhin ti awọn OS ipilẹṣẹ iṣẹlẹ EPOLLIN, ninu apere yi afipamo pe awọn asopọ ti forukọsilẹ on_accept(), setan lati gba data.
on_recv() ka data lati asopọ titi ti ibeere HTTP ti gba patapata, lẹhinna o forukọsilẹ oluṣakoso kan on_send() lati fi esi HTTP ranṣẹ. Ti alabara ba fọ asopọ naa, iho naa ti kọ silẹ ati pipade ni lilo close().
Ṣafihan iṣẹ lori_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);
}
}
Iṣẹlẹ Handler on_send() ti a npe ni lẹhin ti awọn OS ipilẹṣẹ iṣẹlẹ EPOLLOUT, afipamo pe asopọ ti forukọsilẹ on_recv(), setan lati fi data. Iṣẹ yii nfi esi HTTP ranṣẹ ti o ni HTML pẹlu aworan si alabara lẹhinna yi oluṣakoso iṣẹlẹ pada si on_recv().
Ati nikẹhin, ninu faili naa http_server.c, ni iṣẹ main() a ṣẹda I / O riakito lilo reactor_new(), ṣẹda iho olupin ati forukọsilẹ, bẹrẹ riakito nipa lilo reactor_run() fun iṣẹju kan gangan, ati lẹhinna a tu awọn orisun silẹ ati jade kuro ni eto naa.
Jẹ ki a ṣayẹwo pe ohun gbogbo n ṣiṣẹ bi o ti ṣe yẹ. Iṣakojọpọ (chmod a+x compile.sh && ./compile.sh ninu root ise agbese) ki o si ṣe ifilọlẹ olupin ti ara ẹni, ṣii http://127.0.0.1:18470 ninu ẹrọ aṣawakiri ati wo ohun ti a nireti:
Jẹ ki a wọn iṣẹ ṣiṣe ti olupin ala-ẹyọkan. Jẹ ki a ṣii awọn ebute meji: ninu ọkan a yoo ṣiṣẹ ./http_server, ni oriṣiriṣi - wrk. Lẹhin iṣẹju kan, awọn iṣiro atẹle yoo han ni ebute keji:
Olupin alasopo ẹyọkan ni anfani lati ṣiṣẹ lori awọn ibeere miliọnu 11 fun iṣẹju kan ti o bẹrẹ lati awọn asopọ 100. Kii ṣe abajade buburu, ṣugbọn ṣe o le ni ilọsiwaju bi?
Multithreaded olupin
Gẹgẹbi a ti sọ loke, riakito I / O le ṣẹda ni awọn okun lọtọ, nitorinaa lilo gbogbo awọn ohun kohun Sipiyu. Jẹ ki a fi ọna yii si iṣe:
Jọwọ ṣe akiyesi pe ariyanjiyan iṣẹ new_server() alagbawi true. Eyi tumọ si pe a yan aṣayan si iho olupin naa SO_REUSEPORTlati lo ni agbegbe olona-asapo. O le ka diẹ ẹ sii nibi.
Ṣiṣe keji
Bayi jẹ ki a wọn iṣẹ ṣiṣe ti olupin ti o ni asapo pupọ:
Nọmba awọn ibeere ti a ṣe ilana ni iṣẹju 1 pọ si nipasẹ ~ 3.28 igba! Ṣugbọn a jẹ ~ XNUMX milionu kukuru ti nọmba iyipo, nitorinaa jẹ ki a gbiyanju lati ṣatunṣe iyẹn.
Ni akọkọ jẹ ki a wo awọn iṣiro ti ipilẹṣẹ lofinda:
Lilo Sipiyu Affinity, akopo pẹlu -march=native, PGO, ilosoke ninu awọn nọmba ti deba kaṣe, alekun MAX_EVENTS ati lilo EPOLLET ko fun a significant ilosoke ninu išẹ. Ṣugbọn kini yoo ṣẹlẹ ti o ba pọ si nọmba awọn asopọ nigbakanna?
Abajade ti o fẹ ni a gba, ati pẹlu rẹ ayaworan ti o nifẹ ti n fihan igbẹkẹle ti nọmba awọn ibeere ti a ṣe ilana ni iṣẹju 1 lori nọmba awọn asopọ:
A rii pe lẹhin tọkọtaya ọgọọgọrun awọn isopọ, nọmba awọn ibeere ti a ṣe ilana fun awọn olupin mejeeji ṣubu silẹ (ninu ẹya-ara-asapo pupọ eyi jẹ akiyesi diẹ sii). Ṣe eyi ni ibatan si imuse akopọ Linux TCP/IP? Lero ọfẹ lati kọ awọn arosinu rẹ nipa ihuwasi yiyaya ati awọn iṣapeye fun asapo-pupọ ati awọn aṣayan asapo ẹyọkan ninu awọn asọye.
Bawo ni woye ninu awọn asọye, idanwo iṣẹ ṣiṣe ko ṣe afihan ihuwasi ti riakito I / O labẹ awọn ẹru gidi, nitori pe nigbagbogbo olupin n ṣepọ pẹlu ibi ipamọ data, awọn iwejade ti n jade, lo cryptography pẹlu TLS ati be be lo, bi awọn kan abajade ti awọn fifuye di ti kii-aṣọ (ìmúdàgba). Awọn idanwo papọ pẹlu awọn paati ẹnikẹta yoo ṣee ṣe ninu nkan naa nipa olutọpa I/O.
Alailanfani ti mo ti / O riakito
O nilo lati ni oye pe reactor I/O kii ṣe laisi awọn apadabọ rẹ, eyun:
Lilo ohun I/O riakito ni a olona-asapo ayika ni itumo diẹ soro, nitori iwọ yoo ni lati ṣakoso awọn ṣiṣan pẹlu ọwọ.
Iwa ṣe fihan pe ni ọpọlọpọ igba fifuye kii ṣe aṣọ-aṣọ, eyiti o le ja si gige igi okun kan nigba ti omiiran n ṣiṣẹ pẹlu iṣẹ.
Ti oluṣakoso iṣẹlẹ kan ba di okun kan, lẹhinna yiyan eto funrararẹ yoo tun dina, eyiti o le ja si awọn idun lile-lati-wa.
Yanju awọn iṣoro wọnyi I/O proactor, eyiti o nigbagbogbo ni oluṣeto ti o pin kaakiri fifuye si adagun awọn okun, ati tun ni API ti o rọrun diẹ sii. A yoo sọrọ nipa rẹ nigbamii, ninu nkan mi miiran.
ipari
Eyi ni ibiti irin-ajo wa lati imọ-jinlẹ taara sinu eefi profaili ti de opin.
O yẹ ki o ko gbe lori eyi, nitori ọpọlọpọ awọn ọna iwunilori deede miiran wa si kikọ sọfitiwia nẹtiwọọki pẹlu awọn ipele oriṣiriṣi ti irọrun ati iyara. O yanilenu, ninu ero mi, awọn ọna asopọ ni a fun ni isalẹ.